Injektáž závislostí v ASP.NET Core

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Kirk Larkin, Steve Smith a Brandon Dahler

ASP.NET Core podporuje vzor návrhu softwaru pro injektáž závislostí (DI), což je technika pro dosažení inverze řízení (IoC) mezi třídami a jejich závislostmi.

Další informace specifické pro injektáž závislostí v řadičích MVC najdete v tématu Injektáž závislostí do kontrolerů v ASP.NET Core.

Informace o použití injektáže závislostí v jiných aplikacích než ve webových aplikacích naleznete v tématu Injektáž závislostí v .NET.

Další informace o injektáži závislostí možností najdete v tématu Vzor možnosti v ASP.NET Core.

Toto téma obsahuje informace o injektáži závislostí v ASP.NET Core. Primární dokumentace k použití injektáže závislostí je obsažena v injektáži závislostí v .NET.

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Přehled injektáže závislostí

Závislost je objekt, na který závisí jiný objekt. Prozkoumejte následující MyDependency třídu pomocí WriteMessage metody, na které závisí jiné třídy:

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

Třída může vytvořit instanci MyDependency třídy, aby využívala její WriteMessage metodu. V následujícím příkladu MyDependency je třída závislostí IndexModel třídy:


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

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

Třída vytvoří a přímo závisí na MyDependency třídě. Závislosti kódu, například v předchozím příkladu, jsou problematické a měly by se vyhnout z následujících důvodů:

  • Chcete-li nahradit MyDependency jinou implementací, IndexModel musí být třída změněna.
  • Pokud MyDependency obsahuje závislosti, musí je také nakonfigurovat IndexModel třída. Ve velkém projektu s více třídami v závislosti na MyDependencytom se konfigurační kód rozdělí v aplikaci.
  • Tato implementace je obtížná pro testování jednotek.

Injektáž závislostí řeší tyto problémy prostřednictvím:

  • Použití rozhraní nebo základní třídy k abstrakci implementace závislostí.
  • Registrace závislosti v kontejneru služby ASP.NET Core poskytuje integrovaný kontejner služby . IServiceProvider Služby se obvykle registrují v souboru aplikace Program.cs .
  • Injektáž služby do konstruktoru třídy, ve které se používá. Architektura přebírá odpovědnost za vytvoření instance závislosti a její likvidaci, když už ji nepotřebujete.

V ukázkové aplikaciIMyDependency rozhraní definuje metoduWriteMessage:

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

Toto rozhraní je implementováno konkrétním typem: MyDependency

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

Ukázková aplikace zaregistruje IMyDependency službu s konkrétním typem MyDependency. Metoda AddScoped zaregistruje službu s vymezenou životností, životností jednoho požadavku. Životnosti služeb jsou popsány dále v tomto tématu.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

V ukázkové aplikaci IMyDependency se služba vyžádá a použije se k volání WriteMessage metody:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Pomocí vzoru DI kontroler nebo Razor stránka:

  • Nepoužívá konkrétní typ MyDependency, pouze rozhraní, které IMyDependency implementuje. To usnadňuje změnu implementace beze změny kontroleru nebo Razor stránky.
  • Nevytvoří instanci MyDependency, je vytvořená kontejnerem DI.

Implementaci rozhraní je možné vylepšit pomocí integrovaného IMyDependency rozhraní API pro protokolování:

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}");
    }
}

Aktualizovaná Program.cs registrace nové IMyDependency implementace:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

MyDependency2 závisí na tom , který ILogger<TCategoryName>požaduje v konstruktoru. ILogger<TCategoryName>je služba poskytovaná architekturou.

Není neobvyklé používat injektáž závislostí zřetězeným způsobem. Každá požadovaná závislost zase vyžaduje vlastní závislosti. Kontejner vyřeší závislosti v grafu a vrátí plně vyřešenou službu. Souhrnná sada závislostí, které je potřeba vyřešit, se obvykle označuje jako strom závislostí, graf závislostí nebo objektový graf.

Kontejner se ILogger<TCategoryName> vyřeší tím, že využívá (obecné) otevřené typy a eliminuje potřebu registrovat každý (obecný) vytvořený typ.

V terminologii injektáže závislostí služba:

  • Je obvykle objekt, který poskytuje službu jiným objektům, jako IMyDependency je služba.
  • Nesouvisí s webovou službou, i když služba může používat webovou službu.

Architektura poskytuje robustní systém protokolování . Implementace IMyDependency uvedené v předchozích příkladech byly napsány tak, aby demonstrovaly základní DI, nikoli k implementaci protokolování. Většina aplikací by neměla zapisovat protokolovací nástroje. Následující kód ukazuje použití výchozího protokolování, které nevyžaduje registraci žádné služby:

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);
    }
}

Použití předchozího kódu není nutné aktualizovat Program.cs, protože protokolování je poskytováno architekturou.

Registrace skupin služeb pomocí rozšiřujících metod

Architektura ASP.NET Core používá konvenci pro registraci skupiny souvisejících služeb. Konvence spočívá v použití jedné Add{GROUP_NAME} rozšiřující metody k registraci všech služeb vyžadovaných funkcí architektury. AddControllers Například metoda rozšíření registruje služby vyžadované pro kontrolery MVC.

Následující kód je generován šablonou Razor Pages pomocí jednotlivých uživatelských účtů a ukazuje, jak přidat další služby do kontejneru pomocí rozšiřujících metod AddDbContext a AddDefaultIdentity:

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();

Vezměme například následující kód, který registruje služby a konfiguruje možnosti:

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();

Související skupiny registrací lze přesunout do rozšiřující metody pro registraci služeb. Například konfigurační služby jsou přidány do následující třídy:

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;
        }
    }
}

Ostatní služby jsou registrovány v podobné třídě. Následující kód používá nové rozšiřující metody k registraci služeb:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddRazorPages();

var app = builder.Build();

Poznámka Každá rozšiřující metoda services.Add{GROUP_NAME} přidává a potenciálně konfiguruje služby. AddControllersWithViews například přidá kontrolery služeb MVC s požadavkem zobrazení a AddRazorPages přidá požadavky Razor Pages pro služby.

Životnost služeb

Zobrazení životnosti služby v injektáži závislostí v .NET

Pokud chcete v middlewaru používat omezené služby, použijte jeden z následujících přístupů:

  • Vložte službu do middlewaru Invoke nebo InvokeAsync metody. Použití injektáže konstruktoru vyvolá výjimku modulu runtime, protože vynutí, aby se vymezená služba chovala jako singleton. Ukázka v části Možnosti životnosti a registrace ukazuje InvokeAsync přístup.
  • Použijte middleware založený na továrně. Middleware zaregistrovaný pomocí tohoto přístupu se aktivuje na požadavek klienta (připojení), který umožňuje vkládání vymezených služeb do konstruktoru middlewaru.

Další informace najdete v tématu Psaní vlastního middlewaru ASP.NET Core.

Metody registrace služby

Viz Metody registrace služby v injektáži závislostí v .NET

Při testování je běžné používat více implementací.

Registrace služby pouze s typem implementace je ekvivalentní registraci této služby se stejným typem implementace a služby. To je důvod, proč více implementací služby nelze zaregistrovat pomocí metod, které nepoužívají explicitní typ služby. Tyto metody mohou zaregistrovat více instancí služby, ale všechny budou mít stejný typ implementace .

K registraci více instancí služby stejného typu služby je možné použít některou z výše uvedených metod registrace služby. V následujícím příkladu AddSingleton se jako typ služby volá dvakrát IMyDependency . Druhé volání, které AddSingleton přepíše předchozí, když je vyřešeno jako IMyDependency a přidá do předchozího volání při vyřešení více služeb prostřednictvím IEnumerable<IMyDependency>. Služby se zobrazují v pořadí, v jakém byly zaregistrovány při vyřešení prostřednictvím IEnumerable<{SERVICE}>.

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);
    }
}

Služby s klíči

Služby s klíči odkazují na mechanismus registrace a načítání služeb injektáže závislostí (DI) pomocí klíčů. Služba je přidružena ke klíči voláním AddKeyedSingleton (nebo AddKeyedScopedAddKeyedTransient) k jeho registraci. Přístup k registrované službě zadáním klíče s atributem [FromKeyedServices] . Následující kód ukazuje, jak používat služby s klíči:

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"));
    }
}

Chování injektáže konstruktoru

Viz Chování injektáže konstruktoru v injektáži závislostí v .NET

Kontexty Entity Frameworku

Ve výchozím nastavení se kontexty Entity Framework přidají do kontejneru služby pomocí doby života s vymezeným oborem, protože databázové operace webové aplikace jsou obvykle vymezeny na požadavek klienta. Pokud chcete použít jinou životnost, zadejte životnost pomocí AddDbContext přetížení. Služby daného životního cyklu by neměly používat kontext databáze s dobou života kratší než životnost služby.

Možnosti životnosti a registrace

Abychom si ukázali rozdíl mezi životnostmi služeb a možnostmi jejich registrace, zvažte následující rozhraní, která představují úlohu jako operaci s identifikátorem. OperationId V závislosti na tom, jak je pro následující rozhraní nakonfigurovaná doba života služby operace, kontejner při vyžádání třídou poskytuje buď stejné, nebo různé instance služby:

public interface IOperation
{
    string OperationId { get; }
}

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

Následující Operation třída implementuje všechna předchozí rozhraní. Konstruktor Operation vygeneruje identifikátor GUID a uloží poslední 4 znaky ve OperationId vlastnosti:

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

    public string OperationId { get; }
}

Následující kód vytvoří více registrací Operation třídy podle pojmenovaných životností:

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();

Ukázková aplikace ukazuje životnost objektů v rámci požadavků i mezi požadavky. Middleware IndexModel a požadavek na každý typ IOperation a protokolovat OperationId pro každý 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);
    }
}

IndexModelPodobně jako middleware řeší stejné služby:

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>();
    }
}

V metodě musí být vyřešeny InvokeAsync omezené a přechodné služby:

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);
}

Výstup protokolovacího modulu ukazuje:

  • Přechodné objekty se vždy liší. Přechodná OperationId hodnota se v middlewaru IndexModel a v prostředním prostředí liší.
  • Objekty s vymezeným oborem jsou pro daný požadavek stejné, ale liší se v rámci každého nového požadavku.
  • Jednoúčelové objekty jsou pro každý požadavek stejné.

Pokud chcete snížit výstup protokolování, nastavte v appsettings.Development.json souboru "Logging:LogLevel:Microsoft:Error":

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

Řešení potíží se službou při spuštění aplikace

Následující kód ukazuje, jak vyřešit omezenou dobu trvání služby s vymezeným oborem při spuštění aplikace:

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();

Ověření oboru

Viz Chování injektáže konstruktoru v injektáži závislostí v .NET

Další informace najdete v tématu Ověření oboru.

Vyžádat služby

Služby a jejich závislosti v rámci požadavku ASP.NET Core jsou zpřístupněny prostřednictvím HttpContext.RequestServices.

Architektura vytvoří obor na požadavek a RequestServices zpřístupňuje vymezeného poskytovatele služeb. Všechny služby s vymezeným oborem jsou platné, pokud je požadavek aktivní.

Poznámka:

Upřednostňujte vyžádání závislostí jako parametrů konstruktoru před překladem služeb z RequestServices. Vyžádání závislostí jako parametrů konstruktoru poskytuje třídy, které jsou snadnější testovat.

Návrh služeb pro injektáž závislostí

Při navrhování služeb pro injektáž závislostí:

  • Vyhněte se stavovým, statickým třídám a členům. Vyhněte se vytváření globálního stavu návrhem aplikací tak, aby místo toho používaly jednoúčelové služby.
  • Vyhněte se přímé instanci závislých tříd v rámci služeb. Přímé vytvoření instance přiřadí kód ke konkrétní implementaci.
  • Zpřístupnit služby malými, dobře faktorovanými a snadno otestovanými.

Pokud má třída velké množství vloženého závislostí, může to být znamení, že třída má příliš mnoho zodpovědností a porušuje zásadu SRP (Single Responsibility Principle). Pokuste se refaktorovat třídu přesunutím některých svých zodpovědností do nových tříd. Mějte na paměti, že Razor třídy modelu stránky Pages a třídy kontroleru MVC by se měly zaměřit na otázky uživatelského rozhraní.

Likvidace služeb

Kontejner volá Dispose typy, které IDisposable vytvoří. Služby vyřešené z kontejneru by nikdy neměly být uvolněny vývojářem. Pokud je typ nebo továrna registrována jako singleton, kontejner automaticky odstraní jednoton.

V následujícím příkladu jsou služby vytvořeny kontejnerem služby a automaticky uvolněny: 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");
    }
}

Po každé aktualizaci stránky indexu se v konzole ladění zobrazí následující výstup:

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

Služby, které se nevytvořily kontejnerem služby

Uvažujte následující kód:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

V předchozím kódu:

  • Instance služby se nevytvořily kontejnerem služby.
  • Architektura nelikviduje služby automaticky.
  • Vývojář zodpovídá za likvidaci služeb.

Pokyny pro IDisposable pro přechodné a sdílené instance

Viz pokyny pro IDisposable pro přechodné a sdílené instance v injektáži závislostí v .NET.

Nahrazení výchozího kontejneru služby

Viz Nahrazení výchozího kontejneru služby v injektáži závislostí v .NET

Doporučení

Viz doporučení injektáž závislostí v .NET

  • Nepoužívejte vzor lokátoru služby. Například nevyvolávejte, GetService abyste získali instanci služby, když místo toho můžete použít distanci:

    Nesprávně:

    Nesprávný kód

    Správně:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Jinou variantou lokátoru služby, která se vyhne, je vložení továrny, která řeší závislosti za běhu. Obě tyto postupy se směšují s inverzí strategií řízení .

  • Vyhněte se statickému HttpContext přístupu (například IHttpContextAccessor.HttpContext).

Di je alternativou ke vzorům statického nebo globálního přístupu k objektům. Pokud ho kombinujete se statickým přístupem k objektům, možná nebudete moct realizovat výhody DI.

Orchard Core je aplikační architektura pro vytváření modulárních aplikací s více tenanty na ASP.NET Core. Další informace najdete v dokumentaci k sadovému jádru.

Příklady vytváření modulárních a multiklientních aplikací pomocí architektury Orchard Core bez jakýchkoli funkcí specifických pro CMS najdete v ukázkách Sad Core.

Služby poskytované architekturou

Program.cs zaregistruje služby, které aplikace používá, včetně funkcí platformy, jako jsou Entity Framework Core a ASP.NET Core MVC. Na začátku má poskytované Program.cs služby definované architekturou v závislosti na tom, IServiceCollection jak byl hostitel nakonfigurován. Pro aplikace založené na šablonách ASP.NET Core zaregistruje architektura více než 250 služeb.

Následující tabulka uvádí malou ukázku těchto služeb registrovaných architekturou:

Typ služby Životnost
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Přechodná
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Přechodná
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Přechodná
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Přechodná
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Další materiály

Kirk Larkin, Steve Smith a Brandon Dahler

ASP.NET Core podporuje vzor návrhu softwaru pro injektáž závislostí (DI), což je technika pro dosažení inverze řízení (IoC) mezi třídami a jejich závislostmi.

Další informace specifické pro injektáž závislostí v řadičích MVC najdete v tématu Injektáž závislostí do kontrolerů v ASP.NET Core.

Informace o použití injektáže závislostí v jiných aplikacích než ve webových aplikacích naleznete v tématu Injektáž závislostí v .NET.

Další informace o injektáži závislostí možností najdete v tématu Vzor možnosti v ASP.NET Core.

Toto téma obsahuje informace o injektáži závislostí v ASP.NET Core. Primární dokumentace k použití injektáže závislostí je obsažena v injektáži závislostí v .NET.

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Přehled injektáže závislostí

Závislost je objekt, na který závisí jiný objekt. Prozkoumejte následující MyDependency třídu pomocí WriteMessage metody, na které závisí jiné třídy:

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

Třída může vytvořit instanci MyDependency třídy, aby využívala její WriteMessage metodu. V následujícím příkladu MyDependency je třída závislostí IndexModel třídy:


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

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

Třída vytvoří a přímo závisí na MyDependency třídě. Závislosti kódu, například v předchozím příkladu, jsou problematické a měly by se vyhnout z následujících důvodů:

  • Chcete-li nahradit MyDependency jinou implementací, IndexModel musí být třída změněna.
  • Pokud MyDependency obsahuje závislosti, musí je také nakonfigurovat IndexModel třída. Ve velkém projektu s více třídami v závislosti na MyDependencytom se konfigurační kód rozdělí v aplikaci.
  • Tato implementace je obtížná pro testování jednotek.

Injektáž závislostí řeší tyto problémy prostřednictvím:

  • Použití rozhraní nebo základní třídy k abstrakci implementace závislostí.
  • Registrace závislosti v kontejneru služby ASP.NET Core poskytuje integrovaný kontejner služby . IServiceProvider Služby se obvykle registrují v souboru aplikace Program.cs .
  • Injektáž služby do konstruktoru třídy, ve které se používá. Architektura přebírá odpovědnost za vytvoření instance závislosti a její likvidaci, když už ji nepotřebujete.

V ukázkové aplikaciIMyDependency rozhraní definuje metoduWriteMessage:

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

Toto rozhraní je implementováno konkrétním typem: MyDependency

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

Ukázková aplikace zaregistruje IMyDependency službu s konkrétním typem MyDependency. Metoda AddScoped zaregistruje službu s vymezenou životností, životností jednoho požadavku. Životnosti služeb jsou popsány dále v tomto tématu.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

V ukázkové aplikaci IMyDependency se služba vyžádá a použije se k volání WriteMessage metody:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Pomocí vzoru DI kontroler nebo Razor stránka:

  • Nepoužívá konkrétní typ MyDependency, pouze rozhraní, které IMyDependency implementuje. To usnadňuje změnu implementace beze změny kontroleru nebo Razor stránky.
  • Nevytvoří instanci MyDependency, je vytvořená kontejnerem DI.

Implementaci rozhraní je možné vylepšit pomocí integrovaného IMyDependency rozhraní API pro protokolování:

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}");
    }
}

Aktualizovaná Program.cs registrace nové IMyDependency implementace:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

MyDependency2 závisí na tom , který ILogger<TCategoryName>požaduje v konstruktoru. ILogger<TCategoryName>je služba poskytovaná architekturou.

Není neobvyklé používat injektáž závislostí zřetězeným způsobem. Každá požadovaná závislost zase vyžaduje vlastní závislosti. Kontejner vyřeší závislosti v grafu a vrátí plně vyřešenou službu. Souhrnná sada závislostí, které je potřeba vyřešit, se obvykle označuje jako strom závislostí, graf závislostí nebo objektový graf.

Kontejner se ILogger<TCategoryName> vyřeší tím, že využívá (obecné) otevřené typy a eliminuje potřebu registrovat každý (obecný) vytvořený typ.

V terminologii injektáže závislostí služba:

  • Je obvykle objekt, který poskytuje službu jiným objektům, jako IMyDependency je služba.
  • Nesouvisí s webovou službou, i když služba může používat webovou službu.

Architektura poskytuje robustní systém protokolování . Implementace IMyDependency uvedené v předchozích příkladech byly napsány tak, aby demonstrovaly základní DI, nikoli k implementaci protokolování. Většina aplikací by neměla zapisovat protokolovací nástroje. Následující kód ukazuje použití výchozího protokolování, které nevyžaduje registraci žádné služby:

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);
    }
}

Použití předchozího kódu není nutné aktualizovat Program.cs, protože protokolování je poskytováno architekturou.

Registrace skupin služeb pomocí rozšiřujících metod

Architektura ASP.NET Core používá konvenci pro registraci skupiny souvisejících služeb. Konvence spočívá v použití jedné Add{GROUP_NAME} rozšiřující metody k registraci všech služeb vyžadovaných funkcí architektury. AddControllers Například metoda rozšíření registruje služby vyžadované pro kontrolery MVC.

Následující kód je generován šablonou Razor Pages pomocí jednotlivých uživatelských účtů a ukazuje, jak přidat další služby do kontejneru pomocí rozšiřujících metod AddDbContext a AddDefaultIdentity:

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();

Vezměme například následující kód, který registruje služby a konfiguruje možnosti:

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();

Související skupiny registrací lze přesunout do rozšiřující metody pro registraci služeb. Například konfigurační služby jsou přidány do následující třídy:

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;
        }
    }
}

Ostatní služby jsou registrovány v podobné třídě. Následující kód používá nové rozšiřující metody k registraci služeb:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddRazorPages();

var app = builder.Build();

Poznámka Každá rozšiřující metoda services.Add{GROUP_NAME} přidává a potenciálně konfiguruje služby. AddControllersWithViews například přidá kontrolery služeb MVC s požadavkem zobrazení a AddRazorPages přidá požadavky Razor Pages pro služby.

Životnost služeb

Zobrazení životnosti služby v injektáži závislostí v .NET

Pokud chcete v middlewaru používat omezené služby, použijte jeden z následujících přístupů:

  • Vložte službu do middlewaru Invoke nebo InvokeAsync metody. Použití injektáže konstruktoru vyvolá výjimku modulu runtime, protože vynutí, aby se vymezená služba chovala jako singleton. Ukázka v části Možnosti životnosti a registrace ukazuje InvokeAsync přístup.
  • Použijte middleware založený na továrně. Middleware zaregistrovaný pomocí tohoto přístupu se aktivuje na požadavek klienta (připojení), který umožňuje vkládání vymezených služeb do konstruktoru middlewaru.

Další informace najdete v tématu Psaní vlastního middlewaru ASP.NET Core.

Metody registrace služby

Viz Metody registrace služby v injektáži závislostí v .NET

Při testování je běžné používat více implementací.

Registrace služby pouze s typem implementace je ekvivalentní registraci této služby se stejným typem implementace a služby. To je důvod, proč více implementací služby nelze zaregistrovat pomocí metod, které nepoužívají explicitní typ služby. Tyto metody mohou zaregistrovat více instancí služby, ale všechny budou mít stejný typ implementace .

K registraci více instancí služby stejného typu služby je možné použít některou z výše uvedených metod registrace služby. V následujícím příkladu AddSingleton se jako typ služby volá dvakrát IMyDependency . Druhé volání, které AddSingleton přepíše předchozí, když je vyřešeno jako IMyDependency a přidá do předchozího volání při vyřešení více služeb prostřednictvím IEnumerable<IMyDependency>. Služby se zobrazují v pořadí, v jakém byly zaregistrovány při vyřešení prostřednictvím IEnumerable<{SERVICE}>.

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);
    }
}

Chování injektáže konstruktoru

Viz Chování injektáže konstruktoru v injektáži závislostí v .NET

Kontexty Entity Frameworku

Ve výchozím nastavení se kontexty Entity Framework přidají do kontejneru služby pomocí doby života s vymezeným oborem, protože databázové operace webové aplikace jsou obvykle vymezeny na požadavek klienta. Pokud chcete použít jinou životnost, zadejte životnost pomocí AddDbContext přetížení. Služby daného životního cyklu by neměly používat kontext databáze s dobou života kratší než životnost služby.

Možnosti životnosti a registrace

Abychom si ukázali rozdíl mezi životnostmi služeb a možnostmi jejich registrace, zvažte následující rozhraní, která představují úlohu jako operaci s identifikátorem. OperationId V závislosti na tom, jak je pro následující rozhraní nakonfigurovaná doba života služby operace, kontejner při vyžádání třídou poskytuje buď stejné, nebo různé instance služby:

public interface IOperation
{
    string OperationId { get; }
}

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

Následující Operation třída implementuje všechna předchozí rozhraní. Konstruktor Operation vygeneruje identifikátor GUID a uloží poslední 4 znaky ve OperationId vlastnosti:

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

    public string OperationId { get; }
}

Následující kód vytvoří více registrací Operation třídy podle pojmenovaných životností:

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();

Ukázková aplikace ukazuje životnost objektů v rámci požadavků i mezi požadavky. Middleware IndexModel a požadavek na každý typ IOperation a protokolovat OperationId pro každý 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);
    }
}

IndexModelPodobně jako middleware řeší stejné služby:

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>();
    }
}

V metodě musí být vyřešeny InvokeAsync omezené a přechodné služby:

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);
}

Výstup protokolovacího modulu ukazuje:

  • Přechodné objekty se vždy liší. Přechodná OperationId hodnota se v middlewaru IndexModel a v prostředním prostředí liší.
  • Objekty s vymezeným oborem jsou pro daný požadavek stejné, ale liší se v rámci každého nového požadavku.
  • Jednoúčelové objekty jsou pro každý požadavek stejné.

Pokud chcete snížit výstup protokolování, nastavte v appsettings.Development.json souboru "Logging:LogLevel:Microsoft:Error":

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

Řešení potíží se službou při spuštění aplikace

Následující kód ukazuje, jak vyřešit omezenou dobu trvání služby s vymezeným oborem při spuštění aplikace:

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();

Ověření oboru

Viz Chování injektáže konstruktoru v injektáži závislostí v .NET

Další informace najdete v tématu Ověření oboru.

Vyžádat služby

Služby a jejich závislosti v rámci požadavku ASP.NET Core jsou zpřístupněny prostřednictvím HttpContext.RequestServices.

Architektura vytvoří obor na požadavek a RequestServices zpřístupňuje vymezeného poskytovatele služeb. Všechny služby s vymezeným oborem jsou platné, pokud je požadavek aktivní.

Poznámka:

Upřednostňujte vyžádání závislostí jako parametrů konstruktoru před překladem služeb z RequestServices. Vyžádání závislostí jako parametrů konstruktoru poskytuje třídy, které jsou snadnější testovat.

Návrh služeb pro injektáž závislostí

Při navrhování služeb pro injektáž závislostí:

  • Vyhněte se stavovým, statickým třídám a členům. Vyhněte se vytváření globálního stavu návrhem aplikací tak, aby místo toho používaly jednoúčelové služby.
  • Vyhněte se přímé instanci závislých tříd v rámci služeb. Přímé vytvoření instance přiřadí kód ke konkrétní implementaci.
  • Zpřístupnit služby malými, dobře faktorovanými a snadno otestovanými.

Pokud má třída velké množství vloženého závislostí, může to být znamení, že třída má příliš mnoho zodpovědností a porušuje zásadu SRP (Single Responsibility Principle). Pokuste se refaktorovat třídu přesunutím některých svých zodpovědností do nových tříd. Mějte na paměti, že Razor třídy modelu stránky Pages a třídy kontroleru MVC by se měly zaměřit na otázky uživatelského rozhraní.

Likvidace služeb

Kontejner volá Dispose typy, které IDisposable vytvoří. Služby vyřešené z kontejneru by nikdy neměly být uvolněny vývojářem. Pokud je typ nebo továrna registrována jako singleton, kontejner automaticky odstraní jednoton.

V následujícím příkladu jsou služby vytvořeny kontejnerem služby a automaticky uvolněny: 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");
    }
}

Po každé aktualizaci stránky indexu se v konzole ladění zobrazí následující výstup:

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

Služby, které se nevytvořily kontejnerem služby

Uvažujte následující kód:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

V předchozím kódu:

  • Instance služby se nevytvořily kontejnerem služby.
  • Architektura nelikviduje služby automaticky.
  • Vývojář zodpovídá za likvidaci služeb.

Pokyny pro IDisposable pro přechodné a sdílené instance

Viz pokyny pro IDisposable pro přechodné a sdílené instance v injektáži závislostí v .NET.

Nahrazení výchozího kontejneru služby

Viz Nahrazení výchozího kontejneru služby v injektáži závislostí v .NET

Doporučení

Viz doporučení injektáž závislostí v .NET

  • Nepoužívejte vzor lokátoru služby. Například nevyvolávejte, GetService abyste získali instanci služby, když místo toho můžete použít distanci:

    Nesprávně:

    Nesprávný kód

    Správně:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Jinou variantou lokátoru služby, která se vyhne, je vložení továrny, která řeší závislosti za běhu. Obě tyto postupy se směšují s inverzí strategií řízení .

  • Vyhněte se statickému HttpContext přístupu (například IHttpContextAccessor.HttpContext).

Di je alternativou ke vzorům statického nebo globálního přístupu k objektům. Pokud ho kombinujete se statickým přístupem k objektům, možná nebudete moct realizovat výhody DI.

Orchard Core je aplikační architektura pro vytváření modulárních aplikací s více tenanty na ASP.NET Core. Další informace najdete v dokumentaci k sadovému jádru.

Příklady vytváření modulárních a multiklientních aplikací pomocí architektury Orchard Core bez jakýchkoli funkcí specifických pro CMS najdete v ukázkách Sad Core.

Služby poskytované architekturou

Program.cs zaregistruje služby, které aplikace používá, včetně funkcí platformy, jako jsou Entity Framework Core a ASP.NET Core MVC. Na začátku má poskytované Program.cs služby definované architekturou v závislosti na tom, IServiceCollection jak byl hostitel nakonfigurován. Pro aplikace založené na šablonách ASP.NET Core zaregistruje architektura více než 250 služeb.

Následující tabulka uvádí malou ukázku těchto služeb registrovaných architekturou:

Typ služby Životnost
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Přechodná
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Přechodná
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Přechodná
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Přechodná
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Další materiály

Kirk Larkin, Steve Smith, Scott Addie a Brandon Dahler

ASP.NET Core podporuje vzor návrhu softwaru pro injektáž závislostí (DI), což je technika pro dosažení inverze řízení (IoC) mezi třídami a jejich závislostmi.

Další informace specifické pro injektáž závislostí v řadičích MVC najdete v tématu Injektáž závislostí do kontrolerů v ASP.NET Core.

Informace o použití injektáže závislostí v jiných aplikacích než ve webových aplikacích naleznete v tématu Injektáž závislostí v .NET.

Další informace o injektáži závislostí možností najdete v tématu Vzor možnosti v ASP.NET Core.

Toto téma obsahuje informace o injektáži závislostí v ASP.NET Core. Primární dokumentace k použití injektáže závislostí je obsažena v injektáži závislostí v .NET.

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Přehled injektáže závislostí

Závislost je objekt, na který závisí jiný objekt. Prozkoumejte následující MyDependency třídu pomocí WriteMessage metody, na které závisí jiné třídy:

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

Třída může vytvořit instanci MyDependency třídy, aby využívala její WriteMessage metodu. V následujícím příkladu MyDependency je třída závislostí IndexModel třídy:

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

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

Třída vytvoří a přímo závisí na MyDependency třídě. Závislosti kódu, například v předchozím příkladu, jsou problematické a měly by se vyhnout z následujících důvodů:

  • Chcete-li nahradit MyDependency jinou implementací, IndexModel musí být třída změněna.
  • Pokud MyDependency obsahuje závislosti, musí je také nakonfigurovat IndexModel třída. Ve velkém projektu s více třídami v závislosti na MyDependencytom se konfigurační kód rozdělí v aplikaci.
  • Tato implementace je obtížná pro testování jednotek. Aplikace by měla používat napodobení nebo třídu zástupných procedur MyDependency , která s tímto přístupem není možná.

Injektáž závislostí řeší tyto problémy prostřednictvím:

  • Použití rozhraní nebo základní třídy k abstrakci implementace závislostí.
  • Registrace závislosti v kontejneru služby ASP.NET Core poskytuje integrovaný kontejner služby . IServiceProvider Služby se obvykle registrují v metodě aplikace Startup.ConfigureServices .
  • Injektáž služby do konstruktoru třídy, ve které se používá. Architektura přebírá odpovědnost za vytvoření instance závislosti a její likvidaci, když už ji nepotřebujete.

V ukázkové aplikaciIMyDependency rozhraní definuje metoduWriteMessage:

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

Toto rozhraní je implementováno konkrétním typem: MyDependency

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

Ukázková aplikace zaregistruje IMyDependency službu s konkrétním typem MyDependency. Metoda AddScoped zaregistruje službu s vymezenou životností, životností jednoho požadavku. Životnosti služeb jsou popsány dále v tomto tématu.

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

    services.AddRazorPages();
}

V ukázkové aplikaci IMyDependency se služba vyžádá a použije se k volání WriteMessage metody:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Pomocí vzoru DI kontroler:

  • Nepoužívá konkrétní typ MyDependency, pouze rozhraní, které IMyDependency implementuje. To usnadňuje změnu implementace, kterou kontroler používá beze změny kontroleru.
  • Nevytvoří instanci MyDependency, je vytvořená kontejnerem DI.

Implementaci rozhraní je možné vylepšit pomocí integrovaného IMyDependency rozhraní API pro protokolování:

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}");
    }
}

Aktualizovaná ConfigureServices metoda zaregistruje novou IMyDependency implementaci:

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

    services.AddRazorPages();
}

MyDependency2 závisí na tom , který ILogger<TCategoryName>požaduje v konstruktoru. ILogger<TCategoryName>je služba poskytovaná architekturou.

Není neobvyklé používat injektáž závislostí zřetězeným způsobem. Každá požadovaná závislost zase vyžaduje vlastní závislosti. Kontejner vyřeší závislosti v grafu a vrátí plně vyřešenou službu. Souhrnná sada závislostí, které je potřeba vyřešit, se obvykle označuje jako strom závislostí, graf závislostí nebo objektový graf.

Kontejner se ILogger<TCategoryName> vyřeší tím, že využívá (obecné) otevřené typy a eliminuje potřebu registrovat každý (obecný) vytvořený typ.

V terminologii injektáže závislostí služba:

  • Je obvykle objekt, který poskytuje službu jiným objektům, jako IMyDependency je služba.
  • Nesouvisí s webovou službou, i když služba může používat webovou službu.

Architektura poskytuje robustní systém protokolování . Implementace IMyDependency uvedené v předchozích příkladech byly napsány tak, aby demonstrovaly základní DI, nikoli k implementaci protokolování. Většina aplikací by neměla zapisovat protokolovací nástroje. Následující kód ukazuje použití výchozího protokolování, které nevyžaduje registraci žádné služby:ConfigureServices

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);
    }
}

Použití předchozího kódu není nutné aktualizovat ConfigureServices, protože protokolování je poskytováno architekturou.

Služby vložené do spuštění

Služby lze vložit do Startup konstruktoru Startup.Configure a metody.

Při použití obecného hostitele () lze do konstruktoru Startup vložit pouze následující služby:IHostBuilder

Do metody lze vložit jakoukoli službu zaregistrovanou v kontejneru Startup.Configure DI:

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

Další informace najdete v tématu Spuštění aplikace v konfiguraci ASP.NET Core a Access při spuštění.

Registrace skupin služeb pomocí rozšiřujících metod

Architektura ASP.NET Core používá konvenci pro registraci skupiny souvisejících služeb. Konvence spočívá v použití jedné Add{GROUP_NAME} rozšiřující metody k registraci všech služeb vyžadovaných funkcí architektury. AddControllers Například metoda rozšíření registruje služby vyžadované pro kontrolery MVC.

Následující kód je generován šablonou Razor Pages pomocí jednotlivých uživatelských účtů a ukazuje, jak přidat další služby do kontejneru pomocí rozšiřujících metod AddDbContext a AddDefaultIdentity:

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();
}

Vezměme například následující metodu ConfigureServices, která registruje služby a konfiguruje možnosti:

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();
}

Související skupiny registrací lze přesunout do rozšiřující metody pro registraci služeb. Například konfigurační služby jsou přidány do následující třídy:

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;
        }
    }
}

Ostatní služby jsou registrovány v podobné třídě. Následující metoda ConfigureServices používá nové rozšiřující metody k registraci služeb:

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

    services.AddRazorPages();
}

Poznámka Každá rozšiřující metoda services.Add{GROUP_NAME} přidává a potenciálně konfiguruje služby. AddControllersWithViews například přidá kontrolery služeb MVC s požadavkem zobrazení a AddRazorPages přidá požadavky Razor Pages pro služby. Doporučujeme, aby aplikace dodržovaly zásady vytváření názvů pro vytváření rozšiřujících metod v oboru názvů Microsoft.Extensions.DependencyInjection. Vytváření rozšiřujících metod v oboru názvů Microsoft.Extensions.DependencyInjection:

  • Zapouzdřuje skupiny registrací služby.
  • Poskytuje pohodlný přístup IntelliSense ke službě.

Životnost služeb

Zobrazení životnosti služby v injektáži závislostí v .NET

Pokud chcete v middlewaru používat omezené služby, použijte jeden z následujících přístupů:

  • Vložte službu do middlewaru Invoke nebo InvokeAsync metody. Použití injektáže konstruktoru vyvolá výjimku modulu runtime, protože vynutí, aby se vymezená služba chovala jako singleton. Ukázka v části Možnosti životnosti a registrace ukazuje InvokeAsync přístup.
  • Použijte middleware založený na továrně. Middleware zaregistrovaný pomocí tohoto přístupu se aktivuje podle požadavku klienta (připojení), což umožňuje vkládání vymezených služeb do metody middlewaru InvokeAsync .

Další informace najdete v tématu Psaní vlastního middlewaru ASP.NET Core.

Metody registrace služby

Viz Metody registrace služby v injektáži závislostí v .NET

Při testování je běžné používat více implementací.

Registrace služby pouze s typem implementace je ekvivalentní registraci této služby se stejným typem implementace a služby. To je důvod, proč více implementací služby nelze zaregistrovat pomocí metod, které nepoužívají explicitní typ služby. Tyto metody mohou zaregistrovat více instancí služby, ale všechny budou mít stejný typ implementace .

K registraci více instancí služby stejného typu služby je možné použít některou z výše uvedených metod registrace služby. V následujícím příkladu AddSingleton se jako typ služby volá dvakrát IMyDependency . Druhé volání, které AddSingleton přepíše předchozí, když je vyřešeno jako IMyDependency a přidá do předchozího volání při vyřešení více služeb prostřednictvím IEnumerable<IMyDependency>. Služby se zobrazují v pořadí, v jakém byly zaregistrovány při vyřešení prostřednictvím IEnumerable<{SERVICE}>.

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);
    }
}

Chování injektáže konstruktoru

Viz Chování injektáže konstruktoru v injektáži závislostí v .NET

Kontexty Entity Frameworku

Ve výchozím nastavení se kontexty Entity Framework přidají do kontejneru služby pomocí doby života s vymezeným oborem, protože databázové operace webové aplikace jsou obvykle vymezeny na požadavek klienta. Pokud chcete použít jinou životnost, zadejte životnost pomocí AddDbContext přetížení. Služby daného životního cyklu by neměly používat kontext databáze s dobou života kratší než životnost služby.

Možnosti životnosti a registrace

Abychom si ukázali rozdíl mezi životnostmi služeb a možnostmi jejich registrace, zvažte následující rozhraní, která představují úlohu jako operaci s identifikátorem. OperationId V závislosti na tom, jak je pro následující rozhraní nakonfigurovaná doba života služby operace, kontejner při vyžádání třídou poskytuje buď stejné, nebo různé instance služby:

public interface IOperation
{
    string OperationId { get; }
}

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

Následující Operation třída implementuje všechna předchozí rozhraní. Konstruktor Operation vygeneruje identifikátor GUID a uloží poslední 4 znaky ve OperationId vlastnosti:

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

    public string OperationId { get; }
}

Metoda Startup.ConfigureServices vytvoří více registrací Operation třídy podle pojmenovaných životností:

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

    services.AddRazorPages();
}

Ukázková aplikace ukazuje životnost objektů v rámci požadavků i mezi požadavky. Middleware IndexModel a požadavek na každý typ IOperation a protokolovat OperationId pro každý 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);
    }
}

IndexModelPodobně jako middleware řeší stejné služby:

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>();
    }
}

Služby s vymezeným oborem InvokeAsync musí být vyřešeny v metodě:

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);
}

Výstup protokolovacího modulu ukazuje:

  • Přechodné objekty se vždy liší. Přechodná OperationId hodnota se v middlewaru IndexModel a v prostředním prostředí liší.
  • Objekty s vymezeným oborem jsou pro daný požadavek stejné, ale liší se v rámci každého nového požadavku.
  • Jednoúčelové objekty jsou pro každý požadavek stejné.

Pokud chcete snížit výstup protokolování, nastavte v appsettings.Development.json souboru "Logging:LogLevel:Microsoft:Error":

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

Volání služeb z hlavního

Vytvořte s IServiceScope IServiceScopeFactory.CreateScope k vyřešení vymezené služby v rámci oboru aplikace. Tento přístup je užitečný pro přístup ke službě s vymezeným oborem při spuštění, aby bylo možné spouštět úlohy inicializace.

Následující příklad ukazuje, jak přistupovat k vymezené IMyDependency službě a volat její WriteMessage metodu v Program.Main:

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>();
            });
}

Ověření oboru

Viz Chování injektáže konstruktoru v injektáži závislostí v .NET

Další informace najdete v tématu Ověření oboru.

Vyžádat služby

Služby a jejich závislosti v rámci požadavku ASP.NET Core jsou zpřístupněny prostřednictvím HttpContext.RequestServices.

Architektura vytvoří obor na požadavek a RequestServices zpřístupňuje vymezeného poskytovatele služeb. Všechny služby s vymezeným oborem jsou platné, pokud je požadavek aktivní.

Poznámka:

Upřednostňujte vyžádání závislostí jako parametrů konstruktoru před překladem služeb z RequestServices. Vyžádání závislostí jako parametrů konstruktoru poskytuje třídy, které jsou snadnější testovat.

Návrh služeb pro injektáž závislostí

Při navrhování služeb pro injektáž závislostí:

  • Vyhněte se stavovým, statickým třídám a členům. Vyhněte se vytváření globálního stavu návrhem aplikací tak, aby místo toho používaly jednoúčelové služby.
  • Vyhněte se přímé instanci závislých tříd v rámci služeb. Přímé vytvoření instance přiřadí kód ke konkrétní implementaci.
  • Zpřístupnit služby malými, dobře faktorovanými a snadno otestovanými.

Pokud má třída velké množství vloženého závislostí, může to být znamení, že třída má příliš mnoho zodpovědností a porušuje zásadu SRP (Single Responsibility Principle). Pokuste se refaktorovat třídu přesunutím některých svých zodpovědností do nových tříd. Mějte na paměti, že Razor třídy modelu stránky Pages a třídy kontroleru MVC by se měly zaměřit na otázky uživatelského rozhraní.

Likvidace služeb

Kontejner volá Dispose typy, které IDisposable vytvoří. Služby vyřešené z kontejneru by nikdy neměly být uvolněny vývojářem. Pokud je typ nebo továrna registrována jako singleton, kontejner automaticky odstraní jednoton.

V následujícím příkladu jsou služby vytvořeny kontejnerem služby a automaticky odstraněny:

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");
    }
}

Po každé aktualizaci stránky indexu se v konzole ladění zobrazí následující výstup:

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

Služby, které se nevytvořily kontejnerem služby

Uvažujte následující kód:

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

    services.AddRazorPages();
}

V předchozím kódu:

  • Instance služby se nevytvořily kontejnerem služby.
  • Architektura nelikviduje služby automaticky.
  • Vývojář zodpovídá za likvidaci služeb.

Pokyny pro IDisposable pro přechodné a sdílené instance

Viz pokyny pro IDisposable pro přechodné a sdílené instance v injektáži závislostí v .NET.

Nahrazení výchozího kontejneru služby

Viz Nahrazení výchozího kontejneru služby v injektáži závislostí v .NET

Doporučení

Viz doporučení injektáž závislostí v .NET

  • Nepoužívejte vzor lokátoru služby. Například nevyvolávejte, GetService abyste získali instanci služby, když místo toho můžete použít distanci:

    Nesprávně:

    Nesprávný kód

    Správně:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Jinou variantou lokátoru služby, která se vyhne, je vložení továrny, která řeší závislosti za běhu. Obě tyto postupy se směšují s inverzí strategií řízení .

  • Vyhněte se statickému HttpContext přístupu (například IHttpContextAccessor.HttpContext).

  • Vyhněte se volání do BuildServiceProviderConfigureServices. Volání BuildServiceProvider obvykle nastane, když vývojář chce vyřešit službu v ConfigureServices. Představte si například případ LoginPath načtení z konfigurace. Vyhněte se následujícímu přístupu:

    Chybný kód volající BuildServiceProvider

    Na předchozím obrázku se po výběru zelené vlnovky pod services.BuildServiceProvider obrázkem zobrazí následující ASP0000 upozornění:

    ASP0000 volání BuildServiceProvider z kódu aplikace vede k vytvoření další kopie jednoúčelových služeb. Zvažte alternativy, jako je například vkládání závislostí, jako jsou parametry konfigurace.

    Volání BuildServiceProvider vytvoří druhý kontejner, který může vytvořit roztrhané jednotony a způsobit odkazy na grafy objektů napříč více kontejnery.

    Správným způsobem, jak získat LoginPath , je použít integrovanou podporu distančního modelu možností:

    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();
    }
    
  • Jednorázové přechodné služby jsou zachyceny kontejnerem pro odstranění. To se může změnit na nevracení paměti, pokud je vyřešeno z kontejneru nejvyšší úrovně.

  • Povolte ověřování oboru, abyste měli jistotu, že aplikace nemá jednotony, které zachycují vymezené služby. Další informace najdete v tématu Ověření oboru.

Stejně jako u všech sad doporučení můžete narazit na situace, kdy se vyžaduje ignorování doporučení. Výjimky jsou vzácné, většinou zvláštní případy v rámci samotné architektury.

Di je alternativou ke vzorům statického nebo globálního přístupu k objektům. Pokud ho kombinujete se statickým přístupem k objektům, možná nebudete moct realizovat výhody DI.

Orchard Core je aplikační architektura pro vytváření modulárních aplikací s více tenanty na ASP.NET Core. Další informace najdete v dokumentaci k sadovému jádru.

Příklady vytváření modulárních a multiklientních aplikací pomocí architektury Orchard Core bez jakýchkoli funkcí specifických pro CMS najdete v ukázkách Sad Core.

Služby poskytované architekturou

Metoda Startup.ConfigureServices registruje služby, které aplikace používá, včetně funkcí platformy, jako jsou Entity Framework Core a ASP.NET Core MVC. Na začátku má poskytované ConfigureServices služby definované architekturou v závislosti na tom, IServiceCollection jak byl hostitel nakonfigurován. Pro aplikace založené na šablonách ASP.NET Core zaregistruje architektura více než 250 služeb.

Následující tabulka uvádí malou ukázku těchto služeb registrovaných architekturou:

Typ služby Životnost
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Přechodná
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Přechodná
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Přechodná
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Přechodná
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Další materiály