Wie ruft man "Scoped Services" in einem "Singleton Service" auf?

Wie ruft man "Scoped Services" in einem "Singleton Service" auf?

19. März 2024 | Gregor Koletzki | 2 Min. Lesezeit

Dotnet Core Dependency Injection im Detail

In Dotnet Core ist Dependency Injection ein integriertes Feature, das Entwicklern erlaubt, Abhängigkeiten in ihren Anwendungen zu verwalten, ohne auf externe Bibliotheken zurückgreifen zu müssen. Das Microsoft.Extensions.DependencyInjection-Paket bietet die notwendigen Werkzeuge, um DI in Dotnet Core-Anwendungen zu implementieren.

Verstehen der Lebenszyklusarten

In Dotnet Core gibt es drei Hauptarten von Lebenszyklen für die Registrierung von Diensten:

Singleton: Es wird nur eine Instanz des Dienstes erstellt, die während der gesamten Lebensdauer der Anwendung verwendet wird.
Scoped: Es wird eine Instanz pro Anforderung erstellt. Das bedeutet, dass für jede HTTP-Anforderung oder Transaktionsumfang eine neue Instanz erstellt wird.
Transient: Es wird jedes Mal eine neue Instanz des Dienstes erstellt, wenn dieser angefordert wird.

Implementierung

Wenn man in einem Singleton-Service den Scoped-Service einfach per Konstruktor verwenden will, bekommt man von dotNet eine Exception mit der Fehlermeldung dass der Singleton-Service nicht instanziiert werden kann.

public class ScopedService(ILogger<ScopedService> logger)
{
    public void LogValues()
    {
        logger.LogInformation("LogValues is called");
    }
}

public class SingletonService(ScopedService scopedService)
{
    public void CallScopedService()
    {
        scopedService.LogValues();
    }
}

Registrierung an der DI:

builder.Services.AddSingleton<SingletonService>();
builder.Services.AddScoped<ScopedService>();

Wenn man die Applikation jetzt so hochfährt. Wird ein Fehler beim Instanziieren von dem SingletonService geworfen der wie folgt aussieht, und der bedeutet das man ein ScopedService nicht in einem Singleton Service Injection kann.

System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: SingletonInScope.SingletonService Lifetime: Singleton ImplementationType: SingletonInScope.SingletonService': Cannot consume scoped service 'SingletonInScope.ScopedService' from singleton 'SingletonInScope.SingletonService'.)

Damit man den Scoped Service in einem Singleton Service benutzen kann, müssen wir einen eigenen IServiceScope aufmachen. Dies können wir indem man die IServiceScopeFactory sich Injected und durch diese einen eigenen IServiceScope aufmacht und über diesen dann einen ScopedService benutzen kann.

public class SingletonService(IServiceScopeFactory serviceScopeFactory)
{
    public void CallScopedService()
    {
        using var scope = serviceScopeFactory.CreateScope();
        var scopedService = scope.ServiceProvider.GetRequiredService<ScopedService>();
        scopedService.LogValues();
    }
}

Es ist auch möglich einen IServiceScope über den IServiceProvider zu erstellen. Dieser macht aber genau das gleiche, sich den IServiceScope von der IServiceScopeFactory zu holen. Hier die Implementierung von der Extension Methode:

public static IServiceScope CreateScope(this IServiceProvider provider)
{
     return provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
}

Scoped Service in einer Middleware benutzen

Wenn man ein Scoped-Service in einer Middleware benutzen will, ist es abhängig auch davon wie die Middleware in der Applikation registriert ist.

Hier findet ihr die drei Möglichkeiten wie man eine Middleware registriert.

Benutzt man das IMiddleware Interface, dann ist es entscheidend wie die Middleware an der DI registriert wurde. Wenn man die Middleware per Transiant oder Scoped registriert, so kann man den Scoped-Service wie gewohnt über den Konstruktor in die Klasse reinholen. Benutzt man die Middleware Registrierung mit der Methodenkonvention, dann wird die Middleware nur einmal Instanziiert und für jeden HTTP-Anfrage wieder benutzt. In diesem Fall kann man den Scoped Service über die Methoden Injection sich reinholen, da dotNet um den Methodenaufruf ein scope aufmacht.

Beispiele für beide Möglichkeiten der Middleware

public class LoggingMiddleware : IMiddleware
{
    private readonly MyDbContext _dbContext;
    
    public LoggingMiddleware(MyDbContext dbContext)
    {
        this._dbContext = dbContext;
    }
    
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        _dbContext.RequestInfos.Add(new RequestInfo(context.TraceIdentifier));
        await _dbContext.SaveChangesAsync();
        await next(context);
    }
}

public class ConventionLoggingMiddleware
{
    private readonly RequestDelegate _next;

    public ConventionLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task InvokeAsync(HttpContext context, MyDbContext dbContext)
    {
        dbContext.RequestInfos.Add(new RequestInfo(context.TraceIdentifier));
        await dbContext.SaveChangesAsync();
        await _next(context);
    }
}

Registrierung der Middleware an der Applikation

builder.Services.AddDbContext<MyDbContext>(options => options.UseInMemoryDatabase("db"));
builder.Services.AddTransient<LoggingMiddleware>();

app.UseMiddleware<LoggingMiddleware>();
app.UseMiddleware<ConventionLoggingMiddleware>();

Weiterführende Artikel

Kategorien

Kontaktieren Sie uns:

Harksheider Weg 60H,
25451 Quickborn
+49 1520 40 73 253
info@gk-itsolutions.de

Schnellzugriff:

Folgt uns auf: