Dependency Injection in ASP.NET Core

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Von Kirk Larkin, Steve Smith und Brandon Dahler

ASP.NET Core unterstützt das Softwareentwurfsmuster Abhängigkeitsinjektion. Damit kann eine Umkehrung der Steuerung (Inversion of Control, IoC) zwischen Klassen und ihren Abhängigkeiten erreicht werden.

Ausführlichere Informationen zur Abhängigkeitsinjektion innerhalb von MVC-Controllern finden Sie unter Abhängigkeitsinjektion im Controller in ASP.NET Core.

Informationen zum Verwenden der Abhängigkeitsinjektion in anderen Apps als Web-Apps finden Sie unter Abhängigkeitsinjektion in .NET.

Weitere Informationen zur Abhängigkeitsinjektion für Optionen finden Sie unter Optionsmuster in ASP.NET Core.

Dieses Thema enthält Informationen zur Dependency Injection in ASP.NET Core. Die primäre Dokumentation zur Verwendung der Abhängigkeitsinjektion ist in Abhängigkeitsinjektion in .NET enthalten.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)

Übersicht über Abhängigkeitsinjektion

Eine Abhängigkeit ist ein Objekt, von dem ein anderes Objekt abhängig ist. Überprüfen Sie die folgende MyDependency-Klasse mit einer WriteMessage-Methode, von der andere Klassen abhängig sind:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Eine Klasse kann eine Instanz der MyDependency-Klasse erstellen, um die WriteMessage-Methode zu nutzen. Im folgenden Beispiel ist die MyDependency-Klasse eine Abhängigkeit der IndexModel-Klasse:


public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

Die Klasse erstellt die MyDependency-Klasse und weist eine direkte Abhängigkeit von dieser auf. Codeabhängigkeiten (wie im vorherigen Beispiel) sind problematisch und sollten aus folgenden Gründen vermieden werden:

  • Die IndexModel-Klasse muss geändert werden, um MyDependency durch eine andere Implementierung zu ersetzen.
  • Wenn MyDependency über Abhängigkeiten verfügt, müssen diese ebenfalls von der IndexModel-Klasse konfiguriert werden. In einem großen Projekt mit mehreren Klassen, die von MyDependency abhängig sind, wird der Konfigurationscode über die App verteilt.
  • Diese Implementierung ist nicht für Komponententests geeignet.

Die Abhängigkeitsinjektion löst dieses Problem mithilfe der folgenden Schritte:

  • Die Verwendung einer Schnittstelle oder Basisklasse zur Abstraktion der Abhängigkeitsimplementierung.
  • Registrierung der Abhängigkeit in einem Dienstcontainer. ASP.NET Core stellt einen integrierten Dienstcontainer (IServiceProvider) bereit. Dienste werden üblicherweise in der Program.cs-Datei der App registriert.
  • Die Injektion des Diensts in den Konstruktor der Klasse, wo er verwendet wird. Das Framework erstellt eine Instanz der Abhängigkeit und entfernt diese, wenn sie nicht mehr benötigt wird.

In der Beispiel-App definiert die IMyDependency-Schnittstelle die WriteMessage-Methode:

public interface IMyDependency
{
    void WriteMessage(string message);
}

Diese Schnittstelle wird durch einen konkreten Typ (MyDependency) implementiert:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

Die Beispiel-App registriert den IMyDependency-Dienst mit dem konkreten Typ MyDependency. Die AddScoped-Methode registriert den Dienst mit der Lebensdauer einer einzelnen Anforderung. Auf die Dienstlebensdauer wird später in diesem Artikel eingegangen.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

In der Beispiel-App wird der IMyDependency-Dienst angefordert und zum Aufrufen der WriteMessage-Methode verwendet:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Bei Verwendung des DI-Musters (Dependency Injection) gehen der Controller oder die Razor-Seite wie folgt vor:

  • Er verwendet nicht den konkreten Typ MyDependency, nur die von diesem implementierte IMyDependency-Schnittstelle. Dadurch kann die Implementierung ohne das Bearbeiten des Controllers oder der Razor-Seite geändert werden.
  • Er erstellt keine Instanz von MyDependency. Diese wird vom DI-Container erstellt.

Die Implementierung der IMyDependency-Schnittstelle kann mithilfe der integrierten Protokollierungs-API verbessert werden:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

Die aktualisierte Datei Program.cs registriert die neue IMyDependency-Implementierung:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

MyDependency2 hängt von der ILogger<TCategoryName>-Schnittstelle ab, die im Konstruktor angefordert wird. ILogger<TCategoryName> ist ein vom Framework bereitgestellter Dienst.

Die Abhängigkeitsinjektion wird häufig als Verkettung verwendet. Jede angeforderte Abhängigkeit fordert wiederum ihre eigenen Abhängigkeiten an. Der Container löst die Abhängigkeiten im Diagramm auf und gibt den vollständig aufgelösten Dienst zurück. Die gesammelten aufzulösenden Abhängigkeiten werden als Abhängigkeitsstruktur, Abhängigkeitsdiagramm oder Objektdiagramm bezeichnet.

Der Container löst ILogger<TCategoryName> unter Verwendung der (generischen) offenen Typen auf, wodurch nicht mehr jeder (generische) konstruierte Typ registriert werden muss:

Im Dependency-Injection-Kontext ist ein Dienst:

  • in der Regel ein Objekt, das einen Dienst für andere Objekte bereitstellt, z. B. den IMyDependency-Dienst.
  • kein Webdienst, obwohl ein Dienst einen Webdienst verwenden kann

Das Framework bietet eine stabiles Protokollierungssystem. Die in den vorherigen Beispielen veranschaulichten IMyDependency-Implementierungen wurden geschrieben, um die grundlegende Dependency Injection zu demonstrieren, nicht zum Implementieren der Protokollierung. Die meisten Apps müssen keine Protokollierungen schreiben. Der folgende Code veranschaulicht die Verwendung der Standardprotokollierung, bei der keine Dienste registriert werden müssen:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Bei Verwendung des vorangehenden Codes muss Program.cs nicht aktualisiert werden, weil die Protokollierung vom Framework bereitgestellt wird.

Registrieren von Dienstgruppen mit Erweiterungsmethoden

Das ASP.NET Core-Framework verwendet eine Konvention zum Registrieren einer Gruppe verwandter Dienste. Die Konvention besteht darin, eine einzelne Add{GROUP_NAME}-Erweiterungsmethode zum Registrieren aller Dienste zu verwenden, die von einem Frameworkfeature benötigt werden. Beispielsweise registriert die AddControllers-Erweiterungsmethode die für MVC-Controller erforderlichen Dienste.

Der folgende Code wird von der Razor Pages-Vorlage auf Grundlage einzelner Benutzerkonten generiert. Er veranschaulicht, wie mit den Erweiterungsmethoden AddDbContext und AddDefaultIdentity zusätzliche Dienste zum Container hinzugefügt werden können:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Folgendermaßen können Sie Dienste registrieren und Optionen konfigurieren:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Ähnliche Registrierungsgruppen können in eine Erweiterungsmethode verschoben werden, um Dienste zu registrieren. Die Konfigurationsdienste werden beispielsweise folgender Klasse hinzugefügt:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Die verbleibenden Dienste werden in einer ähnlichen Klasse registriert. Der folgende Code verwendet die neuen Erweiterungsmethoden, um die Dienste zu registrieren:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Hinweis: Jede services.Add{GROUP_NAME}-Erweiterungsmethode fügt Dienste hinzu und konfiguriert diese möglicherweise. Beispielsweise fügt AddControllersWithViews den MVC-Controller für Dienste mit den erforderlichen Ansichten hinzu, und AddRazorPages fügt die für Razor Pages benötigten Dienste hinzu.

Dienstlebensdauer

Weitere Informationen finden Sie unter Dienstlebensdauer in Abhängigkeitsinjektion in .NET.

Zum Verwenden bereichsbezogener Dienste in Middleware, verwenden Sie einen der folgenden Ansätze:

  • Fügen Sie den Dienst in die Invoke- oder InvokeAsync-Methode der Middleware ein. Bei Verwendung der Constructor Injection wird eine Runtimeausnahme ausgelöst, weil dem bereichsbezogenen Dienst das Verhalten eines Singletons aufgezwungen wird. Im Beispiel des Abschnitts Lebensdauer und Registrierungsoptionen wird der InvokeAsync-Ansatz veranschaulicht.
  • Verwenden Sie factorybezogene Middleware. Middleware, die mit diesem Ansatz registriert wurde, wird pro Clientanforderung (Verbindung) aktiviert, wodurch bereichsbezogene Dienste in den Konstruktor der Middleware eingefügt werden können.

Weitere Informationen finden Sie unter Schreiben von benutzerdefinierter ASP.NET Core Middleware.

Dienstregistrierungsmethoden

Weitere Informationen finden Sie unter Dienstregistrierungsmethoden in Abhängigkeitsinjektion in .NET.

Es ist üblich, mehrere Implementierungen zu verwenden, wenn Typen zu Testzecken simuliert werden.

Das Registrieren eines Diensts mit nur einem Implementierungstyp entspricht dem Registrieren dieses Diensts mit demselben Implementierungs- und Diensttyp. Aus diesem Grund können nicht mehrere Implementierungen eines Diensts mithilfe von Methoden registriert werden, die keinen expliziten Diensttyp erwarten. Solche Methoden können mehrere Instanzen eines Diensts registrieren, die dann jedoch alle denselben Implementierungstyp aufweisen.

Alle oben genannten Dienstregistrierungsmethoden können zum Registrieren mehrerer Dienstinstanzen desselben Diensttyps verwendet werden. Im folgenden Beispiel wird AddSingleton zweimal mit IMyDependency als Diensttyp aufgerufen. Mit dem zweiten Aufruf von AddSingleton wird der vorhandene Singleton überschrieben, wenn er als IMyDependency aufgelöst wurde. Wenn mehrere Dienste über IEnumerable<IMyDependency> aufgelöst werden, wird der neue Singleton hinzugefügt und der alte beibehalten. Dienste werden beim Auflösen mit IEnumerable<{SERVICE}> in der Reihenfolge angezeigt, in der sie registriert wurden.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Schlüsseldienste

Schlüsseldienste bezieht sich auf einen Mechanismus für die Registrierung und den Abruf von Dependency Injection (DI) Services unter Verwendung von Schlüsseln. Ein Dienst wird mit einem Schlüssel verknüpft, indem Sie AddKeyedSingleton (oder AddKeyedScoped oder AddKeyedTransient) aufrufen, um ihn zu registrieren. Greifen Sie auf einen registrierten Dienst zu, indem Sie den Schlüssel mit dem [FromKeyedServices] Attribut angeben. Der folgende Code zeigt, wie Sie Schlüsseldienste verwenden können:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
                                                               smallCache.Get("date"));

app.MapControllers();

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
    [HttpGet("big-cache")]
    public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
    {
        return cache.Get("data-mvc");
    }
}

public class MyHub : Hub
{
    public void Method([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

Verhalten von Constructor Injection

Weitere Informationen finden Sie unter Konstruktorinjektionsverhalten in Abhängigkeitsinjektion in .NET.

Entity Framework-Kontexte

Entity Framework-Kontexte werden einem Dienstcontainer standardmäßig mithilfe der bereichsbezogenen Lebensdauer hinzugefügt, da Datenbankvorgänge von Web-Apps normalerweise auf den Clientanforderungsbereich bezogen werden. Legen Sie die Lebensdauer mithilfe einer AddDbContext-Überladung fest, um eine andere Lebensdauer zu verwenden. Dienste einer festgelegten Lebensdauer sollten keinen Datenbankkontext mit einer Lebensdauer verwenden, die kürzer als die Lebensdauer des Diensts ist.

Lebensdauer und Registrierungsoptionen

In den folgenden Schnittstellen, die einen Task als Vorgang mit einem Bezeichner (OperationId) darstellen, wird der Unterschied zwischen Dienstlebensdauern und ihren Registrierungsoptionen veranschaulicht. Je nachdem, wie die Dienstlebensdauer für die folgenden Schnittstellen konfiguriert ist, stellt der Container auf Anforderung einer Klasse entweder die gleiche oder eine andere Instanz des Diensts zur Verfügung:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Die folgende Operation-Klasse implementiert alle vorangehenden Schnittstellen. Der Operation-Konstruktor generiert einen eindeutigen Bezeichner (GUID) und speichert die letzten 4 Zeichen in der OperationId-Eigenschaft:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Der folgende Code erstellt mehrere Registrierungen der Operation-Klasse entsprechend den benannten Lebensdauern:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Die Beispiel-App veranschaulicht die Objektlebensdauer sowohl in als auch zwischen Anforderungen. IndexModel und die Middleware fordern jede Art von IOperation-Typ an und protokollieren OperationId für jeden Typ:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Ähnlich wie IndexModel löst die Middleware dieselben Dienste auf:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Bereichsbezogene und vorübergehende Dienste müssen in der InvokeAsync-Methode aufgelöst werden:

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

Die Protokollierungsausgabe zeigt Folgendes an:

  • Objekte vom Typ Vorübergehend sind immer unterschiedlich. Der vorübergehende OperationId-Wert unterscheidet sich in IndexModel und der Middleware.
  • Bereichsbezogene Objekte sind für jede angegebene Anforderung identisch, unterscheiden sich jedoch für jede neue Anforderung.
  • Singletonobjekte sind für jede Anforderung identisch.

Sie können "Logging:LogLevel:Microsoft:Error" in der Datei appsettings.Development.json festlegen, um die Protokollierungsausgabe zu reduzieren:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Auflösen eines Diensts beim Starten der App

Der folgende Code zeigt, wie Sie einen auf einen Bereich begrenzten Dienst für einen begrenzten Zeitraum auflösen, wenn die App gestartet wird:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Bereichsvalidierung

Weitere Informationen finden Sie unter Konstruktorinjektionsverhalten in Abhängigkeitsinjektion in .NET.

Weitere Informationen finden Sie unter Bereichsvalidierung.

Anfordern von Diensten

Dienste und ihre Abhängigkeiten innerhalb einer ASP.NET Core-Anforderung werden über HttpContext.RequestServices verfügbar gemacht.

Das Framework erstellt einen Bereich pro Anforderung, und RequestServices stellt den bereichsbezogenen Dienstanbieter zur Verfügung. Alle bereichsbezogenen Dienste sind gültig, solange die Anforderung aktiv ist.

Hinweis

Sie sollten das Anfordern von Abhängigkeiten als Konstruktorparameter zum Auflösen von Diensten aus RequestServices bevorzugen. Aus dem Anfordern von Abhängigkeiten als Konstruktorparameter resultieren Klassen, die einfacher zu testen sind.

Entwerfen von Diensten für die Abhängigkeitsinjektion

Beachten Sie Folgendes beim Entwerfen von Diensten für Dependency Injection:

  • Vermeiden Sie zustandsbehaftete statische Klassen und Member. Vermeiden Sie das Erstellen eines globalen Zustands, indem Sie Apps stattdessen zur Verwendung von Singletondiensten entwerfen.
  • Vermeiden Sie die direkte Instanziierung abhängiger Klassen innerhalb von Diensten. Die direkte Instanziierung koppelt den Code an eine bestimmte Implementierung.
  • Erstellen Sie kleine, gut gestaltete und einfach zu testende Dienste.

Wenn eine Klasse viele eingefügte Abhängigkeiten aufweist, ist dies möglicherweise ein Zeichen dafür, dass die Klasse zu viele Aufgaben hat und gegen das Prinzip der einzelnen Verantwortung (SRP, Single Responsibility Principle) verstößt. Versuchen Sie, die Klasse umzugestalten, indem Sie einige ihrer Verantwortung in neue Klassen verschieben. Beachten Sie, dass der Fokus der Razor Pages-Seitenmodellklassen und MVC-Controllerklassen auf der Benutzeroberfläche liegt.

Löschen von Diensten

Der Container ruft Dispose für die erstellten IDisposable-Typen auf. Dienste, die aus dem Container aufgelöst werden, sollten nie vom Entwickler gelöscht werden. Wenn ein Typ oder eine Factory als Singleton registriert ist, wird das Singleton automatisch vom Container verworfen.

Im folgenden Beispiel werden die Dienste vom Dienstcontainer erstellt und automatisch verworfen: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

In der Debuggingkonsole wird nach jeder Aktualisierung der Indexseite die folgende Ausgabe angezeigt:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Nicht vom Dienstcontainer erstellte Dienste

Betrachten Sie folgenden Code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

Für den Code oben gilt:

  • werden die Dienstinstanzen nicht vom Dienstcontainer erstellt.
  • verwirft das Framework die Dienste nicht automatisch.
  • Der Entwickler ist für das Löschen der Dienste verantwortlich.

IDisposable-Anleitung für vorübergehende and freigegebene Instanzen

Weitere Informationen finden Sie unter IDisposable-Leitfaden für vorübergehende und freigegebene Instanzen in Abhängigkeitsinjektion in .NET.

Ersetzen von Standarddienstcontainern

Weitere Informationen finden Sie unter Ersetzen von Standarddienstcontainern in Abhängigkeitsinjektion in .NET.

Empfehlungen

Weitere Informationen finden Sie unter Empfehlungen in Abhängigkeitsinjektion in .NET.

  • Vermeiden Sie die Verwendung von Dienstlocator-Mustern. Rufen Sie beispielsweise nicht GetService auf, um eine Dienstinstanz zu erhalten, wenn Sie stattdessen Dependency Injection verwenden können:

    Falsch:

    Falscher Code

    Richtig:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Eine andere Dienstlocator-Variante, die Sie vermeiden sollten, ist die Injektion einer Factory, die zur Laufzeit Abhängigkeiten auflöst. Beide Vorgehensweisen kombinieren Strategien zur Umkehrung der Steuerung.

  • Vermeiden Sie den statischen Zugriff auf HttpContext (z. B. IHttpContextAccessor.HttpContext).

Dependency Injection stellt eine Alternative zu statischen bzw. globalen Objektzugriffsmustern dar. Sie werden keinen Nutzen aus der Dependency Injection ziehen können, wenn Sie diese mit dem Zugriff auf statische Objekte kombinieren.

Orchard Core ist ein Anwendungsframework zum Erstellen modularer Anwendung mit mehreren Mandanten in ASP.NET Core. Weitere Informationen finden Sie in der Orchard Core-Dokumentation.

Beispiele zum Erstellen modularer Apps und Apps mit mehreren Mandanten nur mit dem Orchard Core-Framework und ohne den CMS-spezifischen Features finden Sie in den Orchard Core-Beispielen.

Von Frameworks bereitgestellte Dienste

Program.cs registriert Dienste, die von der App verwendet werden, einschließlich Plattformfeatures wie Entity Framework Core und ASP.NET Core MVC. Zunächst enthält die in Program.cs bereitgestellte IServiceCollection die vom Framework definierten Dienste, abhängig davon, wie der Host konfiguriert wurde. Für Apps, die auf ASP.NET Core-Vorlagen basieren, registriert das Framework mehr als 250 Dienste.

In der folgenden Tabelle finden Sie eine kleine Stichprobe der vom Framework registrierten Dienste:

Diensttyp Lebensdauer
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transient (vorübergehend)
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transient (vorübergehend)
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transient (vorübergehend)
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Transient (vorübergehend)
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Zusätzliche Ressourcen

Von Kirk Larkin, Steve Smith und Brandon Dahler

ASP.NET Core unterstützt das Softwareentwurfsmuster Abhängigkeitsinjektion. Damit kann eine Umkehrung der Steuerung (Inversion of Control, IoC) zwischen Klassen und ihren Abhängigkeiten erreicht werden.

Ausführlichere Informationen zur Abhängigkeitsinjektion innerhalb von MVC-Controllern finden Sie unter Abhängigkeitsinjektion im Controller in ASP.NET Core.

Informationen zum Verwenden der Abhängigkeitsinjektion in anderen Apps als Web-Apps finden Sie unter Abhängigkeitsinjektion in .NET.

Weitere Informationen zur Abhängigkeitsinjektion für Optionen finden Sie unter Optionsmuster in ASP.NET Core.

Dieses Thema enthält Informationen zur Dependency Injection in ASP.NET Core. Die primäre Dokumentation zur Verwendung der Abhängigkeitsinjektion ist in Abhängigkeitsinjektion in .NET enthalten.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)

Übersicht über Abhängigkeitsinjektion

Eine Abhängigkeit ist ein Objekt, von dem ein anderes Objekt abhängig ist. Überprüfen Sie die folgende MyDependency-Klasse mit einer WriteMessage-Methode, von der andere Klassen abhängig sind:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Eine Klasse kann eine Instanz der MyDependency-Klasse erstellen, um die WriteMessage-Methode zu nutzen. Im folgenden Beispiel ist die MyDependency-Klasse eine Abhängigkeit der IndexModel-Klasse:


public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

Die Klasse erstellt die MyDependency-Klasse und weist eine direkte Abhängigkeit von dieser auf. Codeabhängigkeiten (wie im vorherigen Beispiel) sind problematisch und sollten aus folgenden Gründen vermieden werden:

  • Die IndexModel-Klasse muss geändert werden, um MyDependency durch eine andere Implementierung zu ersetzen.
  • Wenn MyDependency über Abhängigkeiten verfügt, müssen diese ebenfalls von der IndexModel-Klasse konfiguriert werden. In einem großen Projekt mit mehreren Klassen, die von MyDependency abhängig sind, wird der Konfigurationscode über die App verteilt.
  • Diese Implementierung ist nicht für Komponententests geeignet.

Die Abhängigkeitsinjektion löst dieses Problem mithilfe der folgenden Schritte:

  • Die Verwendung einer Schnittstelle oder Basisklasse zur Abstraktion der Abhängigkeitsimplementierung.
  • Registrierung der Abhängigkeit in einem Dienstcontainer. ASP.NET Core stellt einen integrierten Dienstcontainer (IServiceProvider) bereit. Dienste werden üblicherweise in der Program.cs-Datei der App registriert.
  • Die Injektion des Diensts in den Konstruktor der Klasse, wo er verwendet wird. Das Framework erstellt eine Instanz der Abhängigkeit und entfernt diese, wenn sie nicht mehr benötigt wird.

In der Beispiel-App definiert die IMyDependency-Schnittstelle die WriteMessage-Methode:

public interface IMyDependency
{
    void WriteMessage(string message);
}

Diese Schnittstelle wird durch einen konkreten Typ (MyDependency) implementiert:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

Die Beispiel-App registriert den IMyDependency-Dienst mit dem konkreten Typ MyDependency. Die AddScoped-Methode registriert den Dienst mit der Lebensdauer einer einzelnen Anforderung. Auf die Dienstlebensdauer wird später in diesem Artikel eingegangen.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

In der Beispiel-App wird der IMyDependency-Dienst angefordert und zum Aufrufen der WriteMessage-Methode verwendet:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Bei Verwendung des DI-Musters (Dependency Injection) gehen der Controller oder die Razor-Seite wie folgt vor:

  • Er verwendet nicht den konkreten Typ MyDependency, nur die von diesem implementierte IMyDependency-Schnittstelle. Dadurch kann die Implementierung ohne das Bearbeiten des Controllers oder der Razor-Seite geändert werden.
  • Er erstellt keine Instanz von MyDependency. Diese wird vom DI-Container erstellt.

Die Implementierung der IMyDependency-Schnittstelle kann mithilfe der integrierten Protokollierungs-API verbessert werden:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

Die aktualisierte Datei Program.cs registriert die neue IMyDependency-Implementierung:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

MyDependency2 hängt von der ILogger<TCategoryName>-Schnittstelle ab, die im Konstruktor angefordert wird. ILogger<TCategoryName> ist ein vom Framework bereitgestellter Dienst.

Die Abhängigkeitsinjektion wird häufig als Verkettung verwendet. Jede angeforderte Abhängigkeit fordert wiederum ihre eigenen Abhängigkeiten an. Der Container löst die Abhängigkeiten im Diagramm auf und gibt den vollständig aufgelösten Dienst zurück. Die gesammelten aufzulösenden Abhängigkeiten werden als Abhängigkeitsstruktur, Abhängigkeitsdiagramm oder Objektdiagramm bezeichnet.

Der Container löst ILogger<TCategoryName> unter Verwendung der (generischen) offenen Typen auf, wodurch nicht mehr jeder (generische) konstruierte Typ registriert werden muss:

Im Dependency-Injection-Kontext ist ein Dienst:

  • in der Regel ein Objekt, das einen Dienst für andere Objekte bereitstellt, z. B. den IMyDependency-Dienst.
  • kein Webdienst, obwohl ein Dienst einen Webdienst verwenden kann

Das Framework bietet eine stabiles Protokollierungssystem. Die in den vorherigen Beispielen veranschaulichten IMyDependency-Implementierungen wurden geschrieben, um die grundlegende Dependency Injection zu demonstrieren, nicht zum Implementieren der Protokollierung. Die meisten Apps müssen keine Protokollierungen schreiben. Der folgende Code veranschaulicht die Verwendung der Standardprotokollierung, bei der keine Dienste registriert werden müssen:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Bei Verwendung des vorangehenden Codes muss Program.cs nicht aktualisiert werden, weil die Protokollierung vom Framework bereitgestellt wird.

Registrieren von Dienstgruppen mit Erweiterungsmethoden

Das ASP.NET Core-Framework verwendet eine Konvention zum Registrieren einer Gruppe verwandter Dienste. Die Konvention besteht darin, eine einzelne Add{GROUP_NAME}-Erweiterungsmethode zum Registrieren aller Dienste zu verwenden, die von einem Frameworkfeature benötigt werden. Beispielsweise registriert die AddControllers-Erweiterungsmethode die für MVC-Controller erforderlichen Dienste.

Der folgende Code wird von der Razor Pages-Vorlage auf Grundlage einzelner Benutzerkonten generiert. Er veranschaulicht, wie mit den Erweiterungsmethoden AddDbContext und AddDefaultIdentity zusätzliche Dienste zum Container hinzugefügt werden können:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Folgendermaßen können Sie Dienste registrieren und Optionen konfigurieren:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Ähnliche Registrierungsgruppen können in eine Erweiterungsmethode verschoben werden, um Dienste zu registrieren. Die Konfigurationsdienste werden beispielsweise folgender Klasse hinzugefügt:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Die verbleibenden Dienste werden in einer ähnlichen Klasse registriert. Der folgende Code verwendet die neuen Erweiterungsmethoden, um die Dienste zu registrieren:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Hinweis: Jede services.Add{GROUP_NAME}-Erweiterungsmethode fügt Dienste hinzu und konfiguriert diese möglicherweise. Beispielsweise fügt AddControllersWithViews den MVC-Controller für Dienste mit den erforderlichen Ansichten hinzu, und AddRazorPages fügt die für Razor Pages benötigten Dienste hinzu.

Dienstlebensdauer

Weitere Informationen finden Sie unter Dienstlebensdauer in Abhängigkeitsinjektion in .NET.

Zum Verwenden bereichsbezogener Dienste in Middleware, verwenden Sie einen der folgenden Ansätze:

  • Fügen Sie den Dienst in die Invoke- oder InvokeAsync-Methode der Middleware ein. Bei Verwendung der Constructor Injection wird eine Runtimeausnahme ausgelöst, weil dem bereichsbezogenen Dienst das Verhalten eines Singletons aufgezwungen wird. Im Beispiel des Abschnitts Lebensdauer und Registrierungsoptionen wird der InvokeAsync-Ansatz veranschaulicht.
  • Verwenden Sie factorybezogene Middleware. Middleware, die mit diesem Ansatz registriert wurde, wird pro Clientanforderung (Verbindung) aktiviert, wodurch bereichsbezogene Dienste in den Konstruktor der Middleware eingefügt werden können.

Weitere Informationen finden Sie unter Schreiben von benutzerdefinierter ASP.NET Core Middleware.

Dienstregistrierungsmethoden

Weitere Informationen finden Sie unter Dienstregistrierungsmethoden in Abhängigkeitsinjektion in .NET.

Es ist üblich, mehrere Implementierungen zu verwenden, wenn Typen zu Testzecken simuliert werden.

Das Registrieren eines Diensts mit nur einem Implementierungstyp entspricht dem Registrieren dieses Diensts mit demselben Implementierungs- und Diensttyp. Aus diesem Grund können nicht mehrere Implementierungen eines Diensts mithilfe von Methoden registriert werden, die keinen expliziten Diensttyp erwarten. Solche Methoden können mehrere Instanzen eines Diensts registrieren, die dann jedoch alle denselben Implementierungstyp aufweisen.

Alle oben genannten Dienstregistrierungsmethoden können zum Registrieren mehrerer Dienstinstanzen desselben Diensttyps verwendet werden. Im folgenden Beispiel wird AddSingleton zweimal mit IMyDependency als Diensttyp aufgerufen. Mit dem zweiten Aufruf von AddSingleton wird der vorhandene Singleton überschrieben, wenn er als IMyDependency aufgelöst wurde. Wenn mehrere Dienste über IEnumerable<IMyDependency> aufgelöst werden, wird der neue Singleton hinzugefügt und der alte beibehalten. Dienste werden beim Auflösen mit IEnumerable<{SERVICE}> in der Reihenfolge angezeigt, in der sie registriert wurden.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Verhalten von Constructor Injection

Weitere Informationen finden Sie unter Konstruktorinjektionsverhalten in Abhängigkeitsinjektion in .NET.

Entity Framework-Kontexte

Entity Framework-Kontexte werden einem Dienstcontainer standardmäßig mithilfe der bereichsbezogenen Lebensdauer hinzugefügt, da Datenbankvorgänge von Web-Apps normalerweise auf den Clientanforderungsbereich bezogen werden. Legen Sie die Lebensdauer mithilfe einer AddDbContext-Überladung fest, um eine andere Lebensdauer zu verwenden. Dienste einer festgelegten Lebensdauer sollten keinen Datenbankkontext mit einer Lebensdauer verwenden, die kürzer als die Lebensdauer des Diensts ist.

Lebensdauer und Registrierungsoptionen

In den folgenden Schnittstellen, die einen Task als Vorgang mit einem Bezeichner (OperationId) darstellen, wird der Unterschied zwischen Dienstlebensdauern und ihren Registrierungsoptionen veranschaulicht. Je nachdem, wie die Dienstlebensdauer für die folgenden Schnittstellen konfiguriert ist, stellt der Container auf Anforderung einer Klasse entweder die gleiche oder eine andere Instanz des Diensts zur Verfügung:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Die folgende Operation-Klasse implementiert alle vorangehenden Schnittstellen. Der Operation-Konstruktor generiert einen eindeutigen Bezeichner (GUID) und speichert die letzten 4 Zeichen in der OperationId-Eigenschaft:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Der folgende Code erstellt mehrere Registrierungen der Operation-Klasse entsprechend den benannten Lebensdauern:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Die Beispiel-App veranschaulicht die Objektlebensdauer sowohl in als auch zwischen Anforderungen. IndexModel und die Middleware fordern jede Art von IOperation-Typ an und protokollieren OperationId für jeden Typ:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Ähnlich wie IndexModel löst die Middleware dieselben Dienste auf:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Bereichsbezogene und vorübergehende Dienste müssen in der InvokeAsync-Methode aufgelöst werden:

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

Die Protokollierungsausgabe zeigt Folgendes an:

  • Objekte vom Typ Vorübergehend sind immer unterschiedlich. Der vorübergehende OperationId-Wert unterscheidet sich in IndexModel und der Middleware.
  • Bereichsbezogene Objekte sind für jede angegebene Anforderung identisch, unterscheiden sich jedoch für jede neue Anforderung.
  • Singletonobjekte sind für jede Anforderung identisch.

Sie können "Logging:LogLevel:Microsoft:Error" in der Datei appsettings.Development.json festlegen, um die Protokollierungsausgabe zu reduzieren:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Auflösen eines Diensts beim Starten der App

Der folgende Code zeigt, wie Sie einen auf einen Bereich begrenzten Dienst für einen begrenzten Zeitraum auflösen, wenn die App gestartet wird:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Bereichsvalidierung

Weitere Informationen finden Sie unter Konstruktorinjektionsverhalten in Abhängigkeitsinjektion in .NET.

Weitere Informationen finden Sie unter Bereichsvalidierung.

Anfordern von Diensten

Dienste und ihre Abhängigkeiten innerhalb einer ASP.NET Core-Anforderung werden über HttpContext.RequestServices verfügbar gemacht.

Das Framework erstellt einen Bereich pro Anforderung, und RequestServices stellt den bereichsbezogenen Dienstanbieter zur Verfügung. Alle bereichsbezogenen Dienste sind gültig, solange die Anforderung aktiv ist.

Hinweis

Sie sollten das Anfordern von Abhängigkeiten als Konstruktorparameter zum Auflösen von Diensten aus RequestServices bevorzugen. Aus dem Anfordern von Abhängigkeiten als Konstruktorparameter resultieren Klassen, die einfacher zu testen sind.

Entwerfen von Diensten für die Abhängigkeitsinjektion

Beachten Sie Folgendes beim Entwerfen von Diensten für Dependency Injection:

  • Vermeiden Sie zustandsbehaftete statische Klassen und Member. Vermeiden Sie das Erstellen eines globalen Zustands, indem Sie Apps stattdessen zur Verwendung von Singletondiensten entwerfen.
  • Vermeiden Sie die direkte Instanziierung abhängiger Klassen innerhalb von Diensten. Die direkte Instanziierung koppelt den Code an eine bestimmte Implementierung.
  • Erstellen Sie kleine, gut gestaltete und einfach zu testende Dienste.

Wenn eine Klasse viele eingefügte Abhängigkeiten aufweist, ist dies möglicherweise ein Zeichen dafür, dass die Klasse zu viele Aufgaben hat und gegen das Prinzip der einzelnen Verantwortung (SRP, Single Responsibility Principle) verstößt. Versuchen Sie, die Klasse umzugestalten, indem Sie einige ihrer Verantwortung in neue Klassen verschieben. Beachten Sie, dass der Fokus der Razor Pages-Seitenmodellklassen und MVC-Controllerklassen auf der Benutzeroberfläche liegt.

Löschen von Diensten

Der Container ruft Dispose für die erstellten IDisposable-Typen auf. Dienste, die aus dem Container aufgelöst werden, sollten nie vom Entwickler gelöscht werden. Wenn ein Typ oder eine Factory als Singleton registriert ist, wird das Singleton automatisch vom Container verworfen.

Im folgenden Beispiel werden die Dienste vom Dienstcontainer erstellt und automatisch verworfen: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

In der Debuggingkonsole wird nach jeder Aktualisierung der Indexseite die folgende Ausgabe angezeigt:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Nicht vom Dienstcontainer erstellte Dienste

Betrachten Sie folgenden Code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

Für den Code oben gilt:

  • werden die Dienstinstanzen nicht vom Dienstcontainer erstellt.
  • verwirft das Framework die Dienste nicht automatisch.
  • Der Entwickler ist für das Löschen der Dienste verantwortlich.

IDisposable-Anleitung für vorübergehende and freigegebene Instanzen

Weitere Informationen finden Sie unter IDisposable-Leitfaden für vorübergehende und freigegebene Instanzen in Abhängigkeitsinjektion in .NET.

Ersetzen von Standarddienstcontainern

Weitere Informationen finden Sie unter Ersetzen von Standarddienstcontainern in Abhängigkeitsinjektion in .NET.

Empfehlungen

Weitere Informationen finden Sie unter Empfehlungen in Abhängigkeitsinjektion in .NET.

  • Vermeiden Sie die Verwendung von Dienstlocator-Mustern. Rufen Sie beispielsweise nicht GetService auf, um eine Dienstinstanz zu erhalten, wenn Sie stattdessen Dependency Injection verwenden können:

    Falsch:

    Falscher Code

    Richtig:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Eine andere Dienstlocator-Variante, die Sie vermeiden sollten, ist die Injektion einer Factory, die zur Laufzeit Abhängigkeiten auflöst. Beide Vorgehensweisen kombinieren Strategien zur Umkehrung der Steuerung.

  • Vermeiden Sie den statischen Zugriff auf HttpContext (z. B. IHttpContextAccessor.HttpContext).

Dependency Injection stellt eine Alternative zu statischen bzw. globalen Objektzugriffsmustern dar. Sie werden keinen Nutzen aus der Dependency Injection ziehen können, wenn Sie diese mit dem Zugriff auf statische Objekte kombinieren.

Orchard Core ist ein Anwendungsframework zum Erstellen modularer Anwendung mit mehreren Mandanten in ASP.NET Core. Weitere Informationen finden Sie in der Orchard Core-Dokumentation.

Beispiele zum Erstellen modularer Apps und Apps mit mehreren Mandanten nur mit dem Orchard Core-Framework und ohne den CMS-spezifischen Features finden Sie in den Orchard Core-Beispielen.

Von Frameworks bereitgestellte Dienste

Program.cs registriert Dienste, die von der App verwendet werden, einschließlich Plattformfeatures wie Entity Framework Core und ASP.NET Core MVC. Zunächst enthält die in Program.cs bereitgestellte IServiceCollection die vom Framework definierten Dienste, abhängig davon, wie der Host konfiguriert wurde. Für Apps, die auf ASP.NET Core-Vorlagen basieren, registriert das Framework mehr als 250 Dienste.

In der folgenden Tabelle finden Sie eine kleine Stichprobe der vom Framework registrierten Dienste:

Diensttyp Lebensdauer
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transient (vorübergehend)
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transient (vorübergehend)
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transient (vorübergehend)
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Transient (vorübergehend)
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Zusätzliche Ressourcen

Von Kirk Larkin, Steve Smith, Scott Addie und Brandon Dahler

ASP.NET Core unterstützt das Softwareentwurfsmuster Abhängigkeitsinjektion. Damit kann eine Umkehrung der Steuerung (Inversion of Control, IoC) zwischen Klassen und ihren Abhängigkeiten erreicht werden.

Ausführlichere Informationen zur Abhängigkeitsinjektion innerhalb von MVC-Controllern finden Sie unter Abhängigkeitsinjektion im Controller in ASP.NET Core.

Informationen zum Verwenden der Abhängigkeitsinjektion in anderen Apps als Web-Apps finden Sie unter Abhängigkeitsinjektion in .NET.

Weitere Informationen zur Abhängigkeitsinjektion für Optionen finden Sie unter Optionsmuster in ASP.NET Core.

Dieses Thema enthält Informationen zur Dependency Injection in ASP.NET Core. Die primäre Dokumentation zur Verwendung der Abhängigkeitsinjektion ist in Abhängigkeitsinjektion in .NET enthalten.

Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)

Übersicht über Abhängigkeitsinjektion

Eine Abhängigkeit ist ein Objekt, von dem ein anderes Objekt abhängig ist. Überprüfen Sie die folgende MyDependency-Klasse mit einer WriteMessage-Methode, von der andere Klassen abhängig sind:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Eine Klasse kann eine Instanz der MyDependency-Klasse erstellen, um die WriteMessage-Methode zu nutzen. Im folgenden Beispiel ist die MyDependency-Klasse eine Abhängigkeit der IndexModel-Klasse:

public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet created this message.");
    }
}

Die Klasse erstellt die MyDependency-Klasse und weist eine direkte Abhängigkeit von dieser auf. Codeabhängigkeiten (wie im vorherigen Beispiel) sind problematisch und sollten aus folgenden Gründen vermieden werden:

  • Die IndexModel-Klasse muss geändert werden, um MyDependency durch eine andere Implementierung zu ersetzen.
  • Wenn MyDependency über Abhängigkeiten verfügt, müssen diese ebenfalls von der IndexModel-Klasse konfiguriert werden. In einem großen Projekt mit mehreren Klassen, die von MyDependency abhängig sind, wird der Konfigurationscode über die App verteilt.
  • Diese Implementierung ist nicht für Komponententests geeignet. Die App sollte eine MyDependency-Modell- oder Stubklasse verwenden, was mit diesem Ansatz nicht möglich ist.

Die Abhängigkeitsinjektion löst dieses Problem mithilfe der folgenden Schritte:

  • Die Verwendung einer Schnittstelle oder Basisklasse zur Abstraktion der Abhängigkeitsimplementierung.
  • Registrierung der Abhängigkeit in einem Dienstcontainer. ASP.NET Core stellt einen integrierten Dienstcontainer (IServiceProvider) bereit. Dienste werden üblicherweise in der Startup.ConfigureServices-Methode der App registriert.
  • Die Injektion des Diensts in den Konstruktor der Klasse, wo er verwendet wird. Das Framework erstellt eine Instanz der Abhängigkeit und entfernt diese, wenn sie nicht mehr benötigt wird.

In der Beispiel-App definiert die IMyDependency-Schnittstelle die WriteMessage-Methode:

public interface IMyDependency
{
    void WriteMessage(string message);
}

Diese Schnittstelle wird durch einen konkreten Typ (MyDependency) implementiert:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

Die Beispiel-App registriert den IMyDependency-Dienst mit dem konkreten Typ MyDependency. Die AddScoped-Methode registriert den Dienst mit der Lebensdauer einer einzelnen Anforderung. Auf die Dienstlebensdauer wird später in diesem Artikel eingegangen.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency>();

    services.AddRazorPages();
}

In der Beispiel-App wird der IMyDependency-Dienst angefordert und zum Aufrufen der WriteMessage-Methode verwendet:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Bei Verwendung des Dependency-Injection-Musters geht der Controller wie folgt vor:

  • Er verwendet nicht den konkreten Typ MyDependency, nur die von diesem implementierte IMyDependency-Schnittstelle. Dadurch kann die vom Controller verwendete Implementierung einfacher geändert werden, ohne den Controller selbst zu bearbeiten.
  • Er erstellt keine Instanz von MyDependency. Diese wird vom DI-Container erstellt.

Die Implementierung der IMyDependency-Schnittstelle kann mithilfe der integrierten Protokollierungs-API verbessert werden:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

Die aktualisierte ConfigureServices-Methode registriert die neue IMyDependency-Implementierung:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency2>();

    services.AddRazorPages();
}

MyDependency2 hängt von der ILogger<TCategoryName>-Schnittstelle ab, die im Konstruktor angefordert wird. ILogger<TCategoryName> ist ein vom Framework bereitgestellter Dienst.

Die Abhängigkeitsinjektion wird häufig als Verkettung verwendet. Jede angeforderte Abhängigkeit fordert wiederum ihre eigenen Abhängigkeiten an. Der Container löst die Abhängigkeiten im Diagramm auf und gibt den vollständig aufgelösten Dienst zurück. Die gesammelten aufzulösenden Abhängigkeiten werden als Abhängigkeitsstruktur, Abhängigkeitsdiagramm oder Objektdiagramm bezeichnet.

Der Container löst ILogger<TCategoryName> unter Verwendung der (generischen) offenen Typen auf, wodurch nicht mehr jeder (generische) konstruierte Typ registriert werden muss:

Im Dependency-Injection-Kontext ist ein Dienst:

  • in der Regel ein Objekt, das einen Dienst für andere Objekte bereitstellt, z. B. den IMyDependency-Dienst.
  • kein Webdienst, obwohl ein Dienst einen Webdienst verwenden kann

Das Framework bietet eine stabiles Protokollierungssystem. Die in den vorherigen Beispielen veranschaulichten IMyDependency-Implementierungen wurden geschrieben, um die grundlegende Dependency Injection zu demonstrieren, nicht zum Implementieren der Protokollierung. Die meisten Apps müssen keine Protokollierungen schreiben. Der folgende Code veranschaulicht die Verwendung der Standardprotokollierung, bei der keine Dienste in ConfigureServices registriert werden müssen:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; }

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Bei Verwendung des vorangehenden Codes muss ConfigureServices nicht aktualisiert werden, weil die Protokollierung vom Framework bereitgestellt wird.

In den Start eingefügte Dienste

Dienste können in den Startup-Konstruktor und die Startup.Configure-Methode eingefügt werden.

Nur die folgenden Dienste können in den Startup-Konstruktor eingefügt werden, wenn der generische Host (IHostBuilder) verwendet wird:

Jeder Dienst, der mit dem DI-Container registriert wurde, kann in die Startup.Configure-Methode eingefügt werden:

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    ...
}

Weitere Informationen finden Sie unter Anwendungsstart in ASP.NET Core und Zugriffskonfiguration beim Start.

Registrieren von Dienstgruppen mit Erweiterungsmethoden

Das ASP.NET Core-Framework verwendet eine Konvention zum Registrieren einer Gruppe verwandter Dienste. Die Konvention besteht darin, eine einzelne Add{GROUP_NAME}-Erweiterungsmethode zum Registrieren aller Dienste zu verwenden, die von einem Frameworkfeature benötigt werden. Beispielsweise registriert die AddControllers-Erweiterungsmethode die für MVC-Controller erforderlichen Dienste.

Der folgende Code wird von der Razor Pages-Vorlage auf Grundlage einzelner Benutzerkonten generiert. Er veranschaulicht, wie mit den Erweiterungsmethoden AddDbContext und AddDefaultIdentity zusätzliche Dienste zum Container hinzugefügt werden können:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();
}

Mit der folgenden ConfigureServices-Methode können Sie Dienste registrieren und Optionen konfigurieren:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
        Configuration.GetSection(PositionOptions.Position));
    services.Configure<ColorOptions>(
        Configuration.GetSection(ColorOptions.Color));

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddScoped<IMyDependency2, MyDependency2>();

    services.AddRazorPages();
}

Ähnliche Registrierungsgruppen können in eine Erweiterungsmethode verschoben werden, um Dienste zu registrieren. Die Konfigurationsdienste werden beispielsweise folgender Klasse hinzugefügt:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Die verbleibenden Dienste werden in einer ähnlichen Klasse registriert. Die folgende ConfigureServices-Methode verwendet die neuen Erweiterungsmethoden, um die Dienste zu registrieren:

public void ConfigureServices(IServiceCollection services)
{
    services.AddConfig(Configuration)
            .AddMyDependencyGroup();

    services.AddRazorPages();
}

Hinweis: Jede services.Add{GROUP_NAME}-Erweiterungsmethode fügt Dienste hinzu und konfiguriert diese möglicherweise. Beispielsweise fügt AddControllersWithViews den MVC-Controller für Dienste mit den erforderlichen Ansichten hinzu, und AddRazorPages fügt die für Razor Pages benötigten Dienste hinzu. Bei Apps sollte die Namenskonvention zum Erstellen von Erweiterungsmethoden im Microsoft.Extensions.DependencyInjection-Namespace befolgt werden. Erstellen von Erweiterungsmethoden im Microsoft.Extensions.DependencyInjection-Namespace:

  • Kapselt Gruppen von Dienstregistrierungen.
  • Bietet komfortablen IntelliSense-Zugriff auf den Dienst.

Dienstlebensdauer

Weitere Informationen finden Sie unter Dienstlebensdauer in Abhängigkeitsinjektion in .NET.

Zum Verwenden bereichsbezogener Dienste in Middleware, verwenden Sie einen der folgenden Ansätze:

  • Fügen Sie den Dienst in die Invoke- oder InvokeAsync-Methode der Middleware ein. Bei Verwendung der Constructor Injection wird eine Runtimeausnahme ausgelöst, weil dem bereichsbezogenen Dienst das Verhalten eines Singletons aufgezwungen wird. Im Beispiel des Abschnitts Lebensdauer und Registrierungsoptionen wird der InvokeAsync-Ansatz veranschaulicht.
  • Verwenden Sie factorybezogene Middleware. Middleware, die mit diesem Ansatz registriert wurde, wird pro Clientanforderung (Verbindung) aktiviert, wodurch bereichsbezogene Dienste in die InvokeAsync-Methode der Middleware eingefügt werden können.

Weitere Informationen finden Sie unter Schreiben von benutzerdefinierter ASP.NET Core Middleware.

Dienstregistrierungsmethoden

Weitere Informationen finden Sie unter Dienstregistrierungsmethoden in Abhängigkeitsinjektion in .NET.

Es ist üblich, mehrere Implementierungen zu verwenden, wenn Typen zu Testzecken simuliert werden.

Das Registrieren eines Diensts mit nur einem Implementierungstyp entspricht dem Registrieren dieses Diensts mit demselben Implementierungs- und Diensttyp. Aus diesem Grund können nicht mehrere Implementierungen eines Diensts mithilfe von Methoden registriert werden, die keinen expliziten Diensttyp erwarten. Solche Methoden können mehrere Instanzen eines Diensts registrieren, die dann jedoch alle denselben Implementierungstyp aufweisen.

Alle oben genannten Dienstregistrierungsmethoden können zum Registrieren mehrerer Dienstinstanzen desselben Diensttyps verwendet werden. Im folgenden Beispiel wird AddSingleton zweimal mit IMyDependency als Diensttyp aufgerufen. Mit dem zweiten Aufruf von AddSingleton wird der vorhandene Singleton überschrieben, wenn er als IMyDependency aufgelöst wurde. Wenn mehrere Dienste über IEnumerable<IMyDependency> aufgelöst werden, wird der neue Singleton hinzugefügt und der alte beibehalten. Dienste werden beim Auflösen mit IEnumerable<{SERVICE}> in der Reihenfolge angezeigt, in der sie registriert wurden.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Verhalten von Constructor Injection

Weitere Informationen finden Sie unter Konstruktorinjektionsverhalten in Abhängigkeitsinjektion in .NET.

Entity Framework-Kontexte

Entity Framework-Kontexte werden einem Dienstcontainer standardmäßig mithilfe der bereichsbezogenen Lebensdauer hinzugefügt, da Datenbankvorgänge von Web-Apps normalerweise auf den Clientanforderungsbereich bezogen werden. Legen Sie die Lebensdauer mithilfe einer AddDbContext-Überladung fest, um eine andere Lebensdauer zu verwenden. Dienste einer festgelegten Lebensdauer sollten keinen Datenbankkontext mit einer Lebensdauer verwenden, die kürzer als die Lebensdauer des Diensts ist.

Lebensdauer und Registrierungsoptionen

In den folgenden Schnittstellen, die einen Task als Vorgang mit einem Bezeichner (OperationId) darstellen, wird der Unterschied zwischen Dienstlebensdauern und ihren Registrierungsoptionen veranschaulicht. Je nachdem, wie die Dienstlebensdauer für die folgenden Schnittstellen konfiguriert ist, stellt der Container auf Anforderung einer Klasse entweder die gleiche oder eine andere Instanz des Diensts zur Verfügung:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Die folgende Operation-Klasse implementiert alle vorangehenden Schnittstellen. Der Operation-Konstruktor generiert einen eindeutigen Bezeichner (GUID) und speichert die letzten 4 Zeichen in der OperationId-Eigenschaft:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Die Startup.ConfigureServices-Methode erstellt mehrere Registrierungen der Operation-Klasse entsprechend der benannten Lebensdauern:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();

    services.AddRazorPages();
}

Die Beispiel-App veranschaulicht die Objektlebensdauer sowohl in als auch zwischen Anforderungen. IndexModel und die Middleware fordern jede Art von IOperation-Typ an und protokollieren OperationId für jeden Typ:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Ähnlich wie IndexModel löst die Middleware dieselben Dienste auf:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationTransient transientOperation,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Bereichsbezogene Dienste müssen in der InvokeAsync-Methode aufgelöst werden:

public async Task InvokeAsync(HttpContext context,
    IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + _transientOperation.OperationId);
    _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

Die Protokollierungsausgabe zeigt Folgendes an:

  • Objekte vom Typ Vorübergehend sind immer unterschiedlich. Der vorübergehende OperationId-Wert unterscheidet sich in IndexModel und der Middleware.
  • Bereichsbezogene Objekte sind für jede angegebene Anforderung identisch, unterscheiden sich jedoch für jede neue Anforderung.
  • Singletonobjekte sind für jede Anforderung identisch.

Sie können "Logging:LogLevel:Microsoft:Error" in der Datei appsettings.Development.json festlegen, um die Protokollierungsausgabe zu reduzieren:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Abrufen von Diensten aus „Main“

Erstellen Sie IServiceScope mit IServiceScopeFactory.CreateScope, um einen bereichsbezogenen Dienst innerhalb des Anwendungsbereichs aufzulösen. Dieser Ansatz eignet sich gut dafür, beim Start auf einen bereichsbezogenen Dienst zuzugreifen und Initialisierungsaufgaben auszuführen.

Im folgenden Beispiel wird der Zugriff auf den bereichsbezogenen IMyDependency-Dienst und der Aufruf dessen WriteMessage-Methode in Program.Main veranschaulicht:

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myDependency = services.GetRequiredService<IMyDependency>();
                myDependency.WriteMessage("Call services from main");
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred.");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Bereichsvalidierung

Weitere Informationen finden Sie unter Konstruktorinjektionsverhalten in Abhängigkeitsinjektion in .NET.

Weitere Informationen finden Sie unter Bereichsvalidierung.

Anfordern von Diensten

Dienste und ihre Abhängigkeiten innerhalb einer ASP.NET Core-Anforderung werden über HttpContext.RequestServices verfügbar gemacht.

Das Framework erstellt einen Bereich pro Anforderung, und RequestServices stellt den bereichsbezogenen Dienstanbieter zur Verfügung. Alle bereichsbezogenen Dienste sind gültig, solange die Anforderung aktiv ist.

Hinweis

Sie sollten das Anfordern von Abhängigkeiten als Konstruktorparameter zum Auflösen von Diensten aus RequestServices bevorzugen. Aus dem Anfordern von Abhängigkeiten als Konstruktorparameter resultieren Klassen, die einfacher zu testen sind.

Entwerfen von Diensten für die Abhängigkeitsinjektion

Beachten Sie Folgendes beim Entwerfen von Diensten für Dependency Injection:

  • Vermeiden Sie zustandsbehaftete statische Klassen und Member. Vermeiden Sie das Erstellen eines globalen Zustands, indem Sie Apps stattdessen zur Verwendung von Singletondiensten entwerfen.
  • Vermeiden Sie die direkte Instanziierung abhängiger Klassen innerhalb von Diensten. Die direkte Instanziierung koppelt den Code an eine bestimmte Implementierung.
  • Erstellen Sie kleine, gut gestaltete und einfach zu testende Dienste.

Wenn eine Klasse viele eingefügte Abhängigkeiten aufweist, ist dies möglicherweise ein Zeichen dafür, dass die Klasse zu viele Aufgaben hat und gegen das Prinzip der einzelnen Verantwortung (SRP, Single Responsibility Principle) verstößt. Versuchen Sie, die Klasse umzugestalten, indem Sie einige ihrer Verantwortung in neue Klassen verschieben. Beachten Sie, dass der Fokus der Razor Pages-Seitenmodellklassen und MVC-Controllerklassen auf der Benutzeroberfläche liegt.

Löschen von Diensten

Der Container ruft Dispose für die erstellten IDisposable-Typen auf. Dienste, die aus dem Container aufgelöst werden, sollten nie vom Entwickler gelöscht werden. Wenn ein Typ oder eine Factory als Singleton registriert ist, wird das Singleton automatisch vom Container verworfen.

Im folgenden Beispiel werden die Dienste vom Dienstcontainer erstellt und automatisch verworfen:

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    
    var myKey = Configuration["MyKey"];
    services.AddSingleton<IService3>(sp => new Service3(myKey));

    services.AddRazorPages();
}
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

In der Debuggingkonsole wird nach jeder Aktualisierung der Indexseite die folgende Ausgabe angezeigt:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = My Key from config
Service1.Dispose

Nicht vom Dienstcontainer erstellte Dienste

Betrachten Sie folgenden Code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(new Service1());
    services.AddSingleton(new Service2());

    services.AddRazorPages();
}

Für den Code oben gilt:

  • werden die Dienstinstanzen nicht vom Dienstcontainer erstellt.
  • verwirft das Framework die Dienste nicht automatisch.
  • Der Entwickler ist für das Löschen der Dienste verantwortlich.

IDisposable-Anleitung für vorübergehende and freigegebene Instanzen

Weitere Informationen finden Sie unter IDisposable-Leitfaden für vorübergehende und freigegebene Instanzen in Abhängigkeitsinjektion in .NET.

Ersetzen von Standarddienstcontainern

Weitere Informationen finden Sie unter Ersetzen von Standarddienstcontainern in Abhängigkeitsinjektion in .NET.

Empfehlungen

Weitere Informationen finden Sie unter Empfehlungen in Abhängigkeitsinjektion in .NET.

  • Vermeiden Sie die Verwendung von Dienstlocator-Mustern. Rufen Sie beispielsweise nicht GetService auf, um eine Dienstinstanz zu erhalten, wenn Sie stattdessen Dependency Injection verwenden können:

    Falsch:

    Falscher Code

    Richtig:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Eine andere Dienstlocator-Variante, die Sie vermeiden sollten, ist die Injektion einer Factory, die zur Laufzeit Abhängigkeiten auflöst. Beide Vorgehensweisen kombinieren Strategien zur Umkehrung der Steuerung.

  • Vermeiden Sie den statischen Zugriff auf HttpContext (z. B. IHttpContextAccessor.HttpContext).

  • Vermeiden Sie Aufrufe von BuildServiceProvider in ConfigureServices. BuildServiceProvider wird in der Regel aufgerufen, wenn der Entwickler einen Dienst in ConfigureServices auflösen möchte. Berücksichtigen Sie beispielsweise den Fall, wenn LoginPath aus der Konfiguration geladen wird. Vermeiden Sie den folgenden Ansatz:

    Ungültiger Code beim Aufruf von BuildServiceProvider

    Wenn Sie auf die grüne Wellenlinie unter services.BuildServiceProvider auf der vorherigen Abbildung klicken würden, würde folgende ASP0000-Warnung angezeigt werden:

    ASP0000: Der Aufruf von BuildServiceProvider aus dem Anwendungscode führt dazu, dass eine zusätzliche Kopie der Singletondienste erstellt wird. Verwenden Sie Alternativen wie Dependency-Injection-Dienste als Parameter für Configure.

    Durch den Aufruf von BuildServiceProvider wird ein zweiter Container erstellt, der fragmentierte Singletons und Verweise auf Objektgraphen für mehrere Container verursachen kann.

    Eine korrekte Methode zum Abrufen von LoginPath besteht darin, die integrierte Unterstützung von DI des Optionsmusters zu verwenden:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie();
    
        services.AddOptions<CookieAuthenticationOptions>(
                            CookieAuthenticationDefaults.AuthenticationScheme)
            .Configure<IMyService>((options, myService) =>
            {
                options.LoginPath = myService.GetLoginPath();
            });
    
        services.AddRazorPages();
    }
    
  • Löschbare temporäre Dienste werden vom Container für die Löschung erfasst. Dadurch kann es zu Arbeitsspeicherverlusten kommen, wenn diese vom obersten Container aufgelöst werden.

  • Aktivieren Sie die Bereichsüberprüfung, um sicherzustellen, dass die App keine Singletons aufweist, die bereichsbezogene Dienste erfassen. Weitere Informationen finden Sie unter Bereichsvalidierung.

Wie bei allen Empfehlungen treffen Sie möglicherweise auf Situationen, in denen eine Empfehlung ignoriert werden muss. Es gibt nur wenige Ausnahmen, die sich meistens auf besondere Fälle innerhalb des Frameworks beziehen.

Dependency Injection stellt eine Alternative zu statischen bzw. globalen Objektzugriffsmustern dar. Sie werden keinen Nutzen aus der Dependency Injection ziehen können, wenn Sie diese mit dem Zugriff auf statische Objekte kombinieren.

Orchard Core ist ein Anwendungsframework zum Erstellen modularer Anwendung mit mehreren Mandanten in ASP.NET Core. Weitere Informationen finden Sie in der Orchard Core-Dokumentation.

Beispiele zum Erstellen modularer Apps und Apps mit mehreren Mandanten nur mit dem Orchard Core-Framework und ohne den CMS-spezifischen Features finden Sie in den Orchard Core-Beispielen.

Von Frameworks bereitgestellte Dienste

Die Startup.ConfigureServices-Methode registriert Dienste, die von der App verwendet werden, einschließlich Plattformfeatures wie Entity Framework Core und ASP.NET Core MVC. Zunächst enthält die in ConfigureServices bereitgestellte IServiceCollection die vom Framework definierten Dienste, abhängig davon, wie der Host konfiguriert wurde. Für Apps, die auf ASP.NET Core-Vorlagen basieren, registriert das Framework mehr als 250 Dienste.

In der folgenden Tabelle finden Sie eine kleine Stichprobe der vom Framework registrierten Dienste:

Diensttyp Lebensdauer
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Transient (vorübergehend)
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Transient (vorübergehend)
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Transient (vorübergehend)
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Transient (vorübergehend)
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Zusätzliche Ressourcen