Dela via


Beroendeinmatning i ASP.NET Core

Not

Det här är inte den senaste versionen av den här artikeln. För den aktuella versionen se .NET 9-versionen av den här artikeln.

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. För den aktuella versionen, se .NET 9-versionen av den här artikeln.

Viktig

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.

Av Kirk Larkin, Steve Smithoch Brandon Dahler

ASP.NET Core stöder designmönstret för beroendeinjektion (DI), vilket är en teknik för att uppnå Inversion av kontroll (IoC) mellan klasser och deras beroenden.

Vägledning för Blazor DI, som lägger till eller ersätter vägledningen i den här artikeln, finns i ASP.NET Core Blazor beroendeinjektion.

Information som är specifik för beroendeinmatning inom MVC-styrenheter finns i Beroendeinmatning till styrenheter i ASP.NET Core.

Information om hur du använder beroendeinmatning i andra program än webbappar finns i Beroendeinmatning i .NET.

Information om beroendeinmatning av alternativ finns i mönstret Alternativ i ASP.NET Core.

Den här artikeln innehåller information om beroendeinmatning i ASP.NET Core. Den primära dokumentationen om hur du använder beroendeinmatning finns i Beroendeinmatning i .NET.

Visa eller ladda ned exempelkod (hur du laddar ned)

Översikt över beroendeinmatning

Ett beroende är ett objekt som ett annat objekt är beroende av. Granska följande MyDependency-klass med en WriteMessage metod som andra klasser är beroende av:

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

En klass kan skapa en instans av klassen MyDependency för att använda sin WriteMessage-metod. I följande exempel är klassen MyDependency ett beroende av klassen IndexModel:


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

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

Klassen skapar och är direkt beroende av klassen MyDependency. Kodberoenden, till exempel i föregående exempel, är problematiska och bör undvikas av följande skäl:

  • Om du vill ersätta MyDependency med en annan implementering måste klassen IndexModel ändras.
  • Om MyDependency har beroenden måste de också konfigureras av IndexModel-klassen. I ett stort projekt med flera klasser beroende på MyDependencyblir konfigurationskoden utspridd över appen.
  • Den här implementeringen är svår att enhetstesta.

Beroendeinjektion åtgärdar dessa problem genom:

  • Användning av ett gränssnitt eller en basklass för att abstrahera beroendeimplementeringen.
  • Registrering av beroende i en tjänstcontainer. ASP.NET Core tillhandahåller en inbyggd tjänstcontainer IServiceProvider. Tjänster registreras vanligtvis i appens Program.cs-fil.
  • Inmatning av tjänsten i konstruktorn för den klass där den används. Ramverket tar på sig ansvaret att skapa en instans av beroendet och ta bort det när det inte längre behövs.

I exempelappendefinierar IMyDependency-gränssnittet WriteMessage-metoden:

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

Det här gränssnittet implementeras av en konkret typ, MyDependency:

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

Exempelappen registrerar IMyDependency-tjänsten med den konkreta typen MyDependency. Metoden AddScoped registrerar tjänsten med en begränsad livslängd, livslängden för en enskild begäran. Tjänstlivslängder beskrivs senare i den här artikeln.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

I exempelappen begärs IMyDependency-tjänsten och används för att anropa metoden WriteMessage:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Genom att använda DI-mönstret, kontrollern eller sidan Razor

  • Använder inte konkret typen MyDependency, utan endast det gränssnitt IMyDependency som den implementerar. Det gör det enkelt att ändra implementeringen utan att ändra kontrollanten eller Razor sidan.
  • Skapar inte någon instans av MyDependency, den skapas av DI-containern.

Implementeringen av IMyDependency-gränssnittet kan förbättras med hjälp av det inbyggda loggnings-API:et:

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

Den uppdaterade Program.cs registrerar den nya IMyDependency implementeringen:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

MyDependency2 beror på ILogger<TCategoryName>, som begärs i konstruktorn. ILogger<TCategoryName> är en tjänst som tillhandahålls av ramverket.

Det är inte ovanligt att använda beroendeinmatning på ett kedjat sätt. Varje begärt beroende begär i sin tur sina egna beroenden. Containern löser beroendena i diagrammet och returnerar den fullständigt lösta tjänsten. Den kollektiva uppsättningen beroenden som måste lösas kallas vanligtvis för ett beroendeträd, beroendediagrameller objektdiagram.

Containern löser ILogger<TCategoryName> genom att dra nytta av (generiska) öppna typer, vilket eliminerar behovet av att registrera varje (generisk) konstruerad typ.

I beroendeinjiceringsterminologin är en tjänst:

  • Är vanligtvis ett objekt som tillhandahåller en tjänst till andra objekt, till exempel IMyDependency-tjänsten.
  • Är inte relaterad till en webbtjänst, även om tjänsten kan använda en webbtjänst.

Ramverket ger ett robust loggningssystem. De IMyDependency implementeringar som visas i föregående exempel skrevs för att demonstrera grundläggande DI, inte för att implementera loggning. De flesta appar ska inte behöva skriva loggare. Följande kod visar hur du använder standardloggningen, som inte kräver att några tjänster registreras:

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

Föregående kod fungerar korrekt utan att ändra något i Program.cseftersom loggning tillhandahålls av ramverket.

Registrera grupper av tjänster med tilläggsmetoder

ASP.NET Core-ramverket använder en konvention för att registrera en grupp relaterade tjänster. Konventionen är att använda en enda Add{GROUP_NAME} tilläggsmetod för att registrera alla tjänster som krävs av en ramverksfunktion. Metoden AddControllers tilläggsmetod registrerar till exempel de tjänster som krävs för MVC-kontrollanter.

Följande kod genereras av mallen Razor Pages med hjälp av enskilda användarkonton och visar hur du lägger till ytterligare tjänster i containern med hjälp av tilläggsmetoderna AddDbContext och 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();

Överväg följande som registrerar tjänster och konfigurerar alternativ:

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

Relaterade grupper av registreringar kan flyttas till en tilläggsmetod för att registrera tjänster. Till exempel läggs konfigurationstjänsterna till i följande klass:

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

De återstående tjänsterna registreras i en liknande klass. Följande kod använder de nya tilläggsmetoderna för att registrera tjänsterna:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddRazorPages();

var app = builder.Build();

Obs! Varje services.Add{GROUP_NAME} tilläggsmetod lägger till och kan konfigurera tjänster. Till exempel lägger AddControllersWithViews till de tjänster som MVC-styrenheter med vyer kräver och AddRazorPages lägger till de tjänster som Razor Pages kräver.

Tjänstlivslängd

Se tjänstelivslängder i beroendeinjektion i .NET

Om du vill använda begränsade tjänster i mellanprogram använder du någon av följande metoder:

  • Mata in tjänsten i mellanprogrammets Invoke- eller InvokeAsync-metod. Om du använder konstruktorinmatning utlöser ett körningsundantag eftersom det tvingar den begränsade tjänsten att bete sig som en singleton. Exemplet i avsnittet Livslängd och registreringsalternativ visar tillvägagångssättet InvokeAsync.
  • Använd Factory-baserade mellanprogram. Mellanprogram som registrerats med den här metoden aktiveras per klientbegäran (anslutning), vilket gör att begränsade tjänster kan matas in i mellanprogrammets konstruktor.

Mer information finns i Skriv anpassad ASP.NET Core-mellanvara.

Metoder för tjänstregistrering

Se Tjänstregistreringsmetoder i Beroendeinmatning i .NET

Det är vanligt att använda flera implementeringar när modelleringstyper för att testa.

Registrering av en tjänst med endast en implementeringstyp motsvarar registrering av tjänsten med samma implementerings- och tjänsttyp. Det är därför flera implementeringar av en tjänst inte kan registreras med de metoder som inte använder en explicit tjänsttyp. Dessa metoder kan registrera flera instanser av en tjänst, men alla har samma implementering typ.

Någon av dessa tjänstregistreringsmetoder kan användas för att registrera flera tjänstinstanser av samma tjänsttyp. I följande exempel anropas AddSingleton två gånger med IMyDependency som tjänsttyp. Det andra anropet till AddSingleton åsidosätter det föregående när det löses som IMyDependency och adderas till det föregående när flera tjänster löses via IEnumerable<IMyDependency>. Tjänsterna visas i den ordning de registrerades när de löstes via 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);
    }
}

Nyckelade tjänster

Termen nyckelade tjänster refererar till en mekanism för att registrera och hämta DI-tjänster (Dependency Injection) med hjälp av nycklar. En tjänst associeras med en nyckel genom att anropa AddKeyedSingleton (eller AddKeyedScoped eller AddKeyedTransient) för att registrera den. Få åtkomst till en registrerad tjänst genom att ange nyckeln med attributet [FromKeyedServices]. Följande kod visar hur du använder nyckelade tjänster:

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

Nyckelade tjänster i Mellanprogram

Mellanprogram stöder Keyed-tjänster i både konstruktorn och metoden Invoke/InvokeAsync:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");

var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();

internal class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next,
        [FromKeyedServices("test")] MySingletonClass service)
    {
        _next = next;
    }

    public Task Invoke(HttpContext context,
        [FromKeyedServices("test2")]
            MyScopedClass scopedService) => _next(context);
}

Mer information om hur du skapar Mellanprogram finns i Skriva anpassade ASP.NET Core-mellanprogram

Konstruktorinmatningsbeteende

Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET

Entity Framework-kontexter

Som standard läggs Entity Framework-kontexter till i tjänstcontainern med hjälp av den begränsade livslängden eftersom webbappdatabasåtgärder normalt begränsas till klientbegäran. Om du vill använda en annan livslängd anger du livslängden med hjälp av en AddDbContext överlagring. Tjänster med en viss livslängd bör inte använda en databaskontext med en livslängd som är kortare än tjänstens livslängd.

Alternativ för livslängd och registrering

För att visa skillnaden mellan tjänstlivslängder och deras registreringsalternativ bör du överväga följande gränssnitt som representerar en uppgift som en åtgärd med en identifierare, OperationId. Beroende på hur livslängden för en åtgärds tjänst har konfigurerats för följande gränssnitt tillhandahåller containern antingen samma eller olika instanser av tjänsten när en klass begär det:

public interface IOperation
{
    string OperationId { get; }
}

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

Följande Operation-klass implementerar alla föregående gränssnitt. Konstruktorn Operation genererar ett GUID och lagrar de sista 4 tecknen i egenskapen OperationId:

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

    public string OperationId { get; }
}

Följande kod skapar flera registreringar av klassen Operation enligt de namngivna livslängderna:

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

Exempelappen visar objektlivslängder både inom och mellan begäranden. IndexModel och mellanprogram begär varje typ av IOperation typ och loggar OperationId för var och en:

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

På samma sätt som i IndexModellöser mellanprogrammet samma tjänster:

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

Scopade och tillfälliga tjänster måste lösas i InvokeAsync-metoden.

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

Loggningsutdata visar:

  • Tillfälliga objekt är alltid olika. Det tillfälliga OperationId värdet skiljer sig i IndexModel och i mellanprogrammet.
  • Omfångsbegränsade objekt är desamma för en viss begäran men skiljer sig åt mellan varje ny begäran.
  • Singleton- objekt är desamma för varje begäran.

Om du vill minska loggningsutdata anger du "Logging:LogLevel:Microsoft:Error" i filen appsettings.Development.json:

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

Lösa en tjänst vid appstart

Följande kod visar hur du löser en begränsad tjänst under en begränsad tid när appen startar:

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

Omfångsverifiering

Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET

Mer information finns i Omfångsverifiering.

Begär tjänster

Tjänster och deras beroenden inom en ASP.NET Core-begäran exponeras via HttpContext.RequestServices.

Ramverket skapar ett omfång per begäran och RequestServices exponerar den begränsade tjänstleverantören. Alla begränsade tjänster är giltiga så länge begäran är aktiv.

Notis

Föredra att begära beroenden som konstruktorparametrar före att lösa tjänster från RequestServices. Att begära beroenden som konstruktorparametrar ger klasser som är enklare att testa.

Utforma tjänster för beroendeinmatning

När du utformar tjänster för beroendeinjektion:

  • Undvik tillståndskänsliga, statiska klasser och medlemmar. Undvik att skapa globalt tillstånd genom att utforma appar så att de använder singleton-tjänster i stället.
  • Undvik direkt instansiering av beroende klasser inom tjänster. Direkt instansiering kopplar koden till en viss implementering.
  • Gör tjänsterna små, välräknade och enkelt testade.

Om en klass har många inmatade beroenden kan det vara ett tecken på att klassen har för många ansvarsområden och bryter mot SRP(Single Responsibility Principle). Försök att omstrukturera klassen genom att flytta en del av dess ansvarsområden till nya klasser. Tänk på att Razor Sidmodellklasser för sidor och MVC-kontrollantklasser bör fokusera på användargränssnittsproblem.

Avyttring av tjänster

Containern anropar Dispose för typerna IDisposable den skapar. Tjänster som löses från containern bör aldrig tas bort av utvecklaren. Om en typ eller fabrik registreras som en singleton, tas singletonen automatiskt bort av containern.

I följande exempel skapas tjänsterna av tjänstcontainern och tas bort automatiskt: 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");
    }
}

Felsökningskonsolen visar följande utdata efter varje uppdatering av indexsidan:

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

Tjänster som inte har skapats av tjänstcontainern

Överväg följande kod:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

I föregående kod:

  • Tjänstinstanserna skapas inte av tjänstcontainern.
  • Ramverket tar inte bort tjänsterna automatiskt.
  • Utvecklaren ansvarar för att ta bort tjänsterna.

IDisposable-vägledning för transienta och gemensamma instanser

Se IDisposable-vägledning för tillfälliga och delade instanser i Beroendeinmatning i .NET

Ersättning av standardtjänstcontainer

Se Standardtjänstcontainer-byte i Beroendeinjektion i .NET

Rekommendationer

Se rekommendationer i beroendeinmatning i .NET

  • Undvik att använda mönstret för att lokalisera tjänster . Anropa till exempel inte GetService för att hämta en tjänstinstans när du kan använda DI i stället:

    felaktig:

    Felaktig kod

    Rätt:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • En annan service locator-variant att undvika är att injektera en fabrik som löser beroenden vid körning. Båda dessa metoder blandar inversion av kontroll strategier.

  • Undvik statisk åtkomst till HttpContext (till exempel IHttpContextAccessor.HttpContext).

DI är ett alternativ till statiska/globala objektåtkomstmönster. Du kanske inte kan dra nytta av fördelarna med DI om du blandar det med åtkomst till statiska objekt.

Orchard Core är ett programramverk för att skapa modulära program med flera klienter på ASP.NET Core. För mer information, se dokumentationen om Orchard Core.

Se Orchard Core-exempel för hur du kan bygga modulära och fleranvändarapplikationer endast med Orchard Core Framework utan några av dess CMS-specifika funktioner.

Ramverksbaserade tjänster

Program.cs registrerar tjänster som appen använder, inklusive plattformsfunktioner som Entity Framework Core och ASP.NET Core MVC. Till en början har IServiceCollection som tillhandahålls till Program.cs tjänster som definierats av ramverket beroende på hur värden konfigurerades. För appar baserade på ASP.NET Core-mallar registrerar ramverket mer än 250 tjänster.

I följande tabell visas ett litet urval av dessa ramverksregistrerade tjänster:

Tjänsttyp Livstid
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Kortvarig
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Övergående
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Kortvarig
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Kortvarig
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Ytterligare resurser

Av Kirk Larkin, Steve Smithoch Brandon Dahler

ASP.NET Core stöder designmönstret för beroendeinjektion (DI), vilket är en teknik för att uppnå inversion av kontroll (IoC) mellan klasser och deras beroenden.

Mer information om beroendeinmatning i MVC-styrenheter finns i Beroendeinmatning till styrenheter i ASP.NET Core.

Information om hur du använder beroendeinmatning i andra program än webbappar finns i Beroendeinmatning i .NET.

Mer information om beroendeinmatning av alternativ finns i mönstret Alternativ i ASP.NET Core.

Det här avsnittet innehåller information om beroendeinmatning i ASP.NET Core. Den primära dokumentationen om hur du använder beroendeinmatning finns i Beroendeinmatning i .NET.

Visa eller ladda ned exempelkod (hur du laddar ned)

Översikt över beroendeinmatning

Ett beroende är ett objekt som ett annat objekt är beroende av. Granska följande MyDependency-klass med en WriteMessage metod som andra klasser är beroende av:

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

En klass kan skapa en instans av klassen MyDependency för att använda sin WriteMessage-metod. I följande exempel är klassen MyDependency ett beroende av klassen IndexModel:


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

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

Klassen skapar och är direkt beroende av klassen MyDependency. Kodberoenden, till exempel i föregående exempel, är problematiska och bör undvikas av följande skäl:

  • Om du vill ersätta MyDependency med en annan implementering måste klassen IndexModel ändras.
  • Om det finns beroenden för MyDependency måste de konfigureras av klassen IndexModel. I ett stort projekt med flera klasser beroende på MyDependencyblir konfigurationskoden utspridd över appen.
  • Den här implementeringen är svår att enhetstesta.

Beroendeinsprutning åtgärdar dessa problem genom:

  • Användning av ett gränssnitt eller en basklass för att abstrahera beroendeimplementeringen.
  • Registrering av ett beroende i en tjänstcontainer. ASP.NET Core tillhandahåller en inbyggd tjänstcontainer IServiceProvider. Tjänster registreras vanligtvis i appens Program.cs-fil.
  • Inmatning av tjänsten i konstruktorn för den klass där den används. Ramverket tar på sig ansvaret att skapa en instans av beroendet och ta bort det när det inte längre behövs.

I exempelappendefinierar IMyDependency-gränssnittet WriteMessage-metoden:

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

Det här gränssnittet implementeras av en konkret typ, MyDependency:

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

Exempelappen registrerar IMyDependency-tjänsten med den konkreta typen MyDependency. Metoden AddScoped registrerar tjänsten med en begränsad livslängd, livslängden för en enskild begäran. Tjänstlivslängder beskrivs senare i det här avsnittet.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

I exempelappen begärs IMyDependency-tjänsten och används för att anropa metoden WriteMessage:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Genom att använda DI-mönstret, kontrollanten eller Razor sidan:

  • Använder inte den konkreta typen MyDependency, endast det IMyDependency-gränssnitt det implementerar. Det gör det enkelt att ändra implementeringen utan att ändra kontrollern eller Razor-sidan.
  • Skapar inte någon instans av MyDependency, den skapas av DI-containern.

Implementeringen av IMyDependency-gränssnittet kan förbättras med hjälp av det inbyggda loggnings-API:et:

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

Den uppdaterade Program.cs registrerar den nya IMyDependency implementeringen:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

MyDependency2 beror på ILogger<TCategoryName>, som den begär i konstruktorn. ILogger<TCategoryName> är en tjänst som tillhandahålls av ramverket.

Det är inte ovanligt att använda beroendeinjektion i en kedjad form. Varje begärt beroende begär i sin tur sina egna beroenden. Containern löser beroendena i diagrammet och returnerar den fullständigt lösta tjänsten. Den kollektiva uppsättningen beroenden som måste lösas kallas vanligtvis för ett beroendeträd, beroendediagrameller objektdiagram.

Containern löser ILogger<TCategoryName> genom att dra nytta av (generiska) öppna typer, vilket eliminerar behovet av att registrera varje (generisk) konstruerad typ.

Inom beroendeinsprutningsterminologi är en tjänst:

  • Är vanligtvis ett objekt som tillhandahåller en tjänst till andra objekt, till exempel IMyDependency-tjänsten.
  • Är inte relaterad till en webbtjänst, även om tjänsten kan använda en webbtjänst.

Ramverket ger ett robust loggning system. De IMyDependency implementeringar som visas i föregående exempel skrevs för att demonstrera grundläggande DI, inte för att implementera loggning. De flesta appar ska inte behöva skriva loggare. Följande kod visar hur du använder standardloggningen, som inte kräver att några tjänster registreras:

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

Med hjälp av föregående kod behöver du inte uppdatera Program.cseftersom loggning tillhandahålls av ramverket.

Registrera grupper av tjänster med tilläggsmetoder

ASP.NET Core-ramverket använder en konvention för att registrera en grupp relaterade tjänster. Konventionen är att använda en enda Add{GROUP_NAME} tilläggsmetod för att registrera alla tjänster som krävs av en ramverksfunktion. AddControllers tilläggsmetod registrerar till exempel de tjänster som krävs för MVC-kontroller.

Följande kod genereras av mallen Razor Pages med hjälp av enskilda användarkonton och visar hur du lägger till ytterligare tjänster i containern med hjälp av tilläggsmetoderna AddDbContext och 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();

Överväg följande som registrerar tjänster och konfigurerar alternativ:

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

Relaterade grupper av registreringar kan flyttas till en tilläggsmetod för att registrera tjänster. Till exempel läggs konfigurationstjänsterna till i följande klass:

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

De återstående tjänsterna registreras i en liknande klass. Följande kod använder de nya tilläggsmetoderna för att registrera tjänsterna:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddRazorPages();

var app = builder.Build();

Obs! Varje services.Add{GROUP_NAME} tilläggsmetod lägger till och kan konfigurera tjänster. Till exempel lägger AddControllersWithViews till de tjänster som MVC-styrenheter med vyer kräver och AddRazorPages lägger till de tjänster som Razor Pages kräver.

Tjänstlivslängd

Se tjänstlivslängder i beroendeinmatning i .NET

Om du vill använda begränsade tjänster i mellanprogram använder du någon av följande metoder:

  • Mata in tjänsten i mellanprogrammets Invoke- eller InvokeAsync-metod. Om du använder konstruktörsinjektion genererar ett körningsfel eftersom det tvingar den avgränsade tjänsten att bete sig som en singleton. Exemplet i avsnittet Livslängd och registrering visar InvokeAsync metod.
  • Använd Factory-baserade mellanprogram. Mellanprogram som registrerats med den här metoden aktiveras per klientbegäran (anslutning), vilket gör att begränsade tjänster kan matas in i mellanprogrammets konstruktor.

Mer information finns i Skriv anpassad ASP.NET Core-mellanvara.

Metoder för tjänstregistrering

Se Tjänstregistreringsmetoder i Beroendeinmatning i .NET

Det är vanligt att använda flera implementeringar när modelleringstyper för att testa.

Registrering av en tjänst med endast en implementeringstyp motsvarar registrering av tjänsten med samma implementerings- och tjänsttyp. Det är därför flera implementeringar av en tjänst inte kan registreras med de metoder som inte använder en explicit tjänsttyp. Dessa metoder kan registrera flera instanser av en tjänst, men alla har samma implementering typ.

Någon av ovanstående tjänstregistreringsmetoder kan användas för att registrera flera tjänstinstanser av samma tjänsttyp. I följande exempel anropas AddSingleton två gånger med IMyDependency som tjänsttyp. Det andra anropet till AddSingleton åsidosätter det föregående när den löses som IMyDependency och lägger till det föregående när flera tjänster löses upp via IEnumerable<IMyDependency>. Tjänsterna visas i den ordning de registrerades när de löstes via 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);
    }
}

Nyckelade tjänster

keyed services refererar till en mekanism för att registrera och hämta DI-tjänster (Dependency Injection) med hjälp av nycklar. En tjänst associeras med en nyckel genom att anropa AddKeyedSingleton (eller AddKeyedScoped eller AddKeyedTransient) för att registrera den. Få åtkomst till en registrerad tjänst genom att ange nyckeln med attributet [FromKeyedServices]. Följande kod visar hur du använder nyckelade tjänster:

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

Konstruktorinmatningsbeteende

Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET

Entity Framework-kontexter

Som standard läggs Entity Framework-kontexter till i tjänstcontainern med hjälp av den begränsade livslängden eftersom webbappdatabasåtgärder normalt begränsas till klientbegäran. Om du vill använda en annan livslängd anger du livslängden med hjälp av en AddDbContext överlagring. Tjänster med en viss livslängd bör inte använda en databaskontext med en livslängd som är kortare än tjänstens livslängd.

Alternativ för livslängd och registrering

För att visa skillnaden mellan tjänstlivslängder och deras registreringsalternativ bör du överväga följande gränssnitt som representerar en uppgift som en åtgärd med en identifierare, OperationId. Beroende på hur livslängden för en åtgärds tjänst har konfigurerats för följande gränssnitt tillhandahåller containern antingen samma eller olika instanser av tjänsten när en klass begär det:

public interface IOperation
{
    string OperationId { get; }
}

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

Följande Operation-klass implementerar alla föregående gränssnitt. Konstruktorn Operation genererar ett GUID och lagrar de sista 4 tecknen i egenskapen OperationId:

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

    public string OperationId { get; }
}

Följande kod skapar flera registreringar av klassen Operation enligt de namngivna livslängderna:

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

Exempelappen visar objektlivslängder både inom och mellan begäranden. IndexModel och mellanprogram begär varje typ av IOperation typ och loggar OperationId för var och en:

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

På samma sätt som i IndexModellöser mellanprogrammet samma tjänster:

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

Begränsade och tillfälliga tjänster måste lösas i metoden InvokeAsync:

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

Loggningsutdata visar:

  • Tillfälliga objekt är alltid olika. Det tillfälliga OperationId värdet skiljer sig i IndexModel och i mellanprogrammet.
  • Omfångsbegränsade objekt är desamma för en viss begäran men skiljer sig åt mellan varje ny begäran.
  • Singleton- objekt är desamma för varje begäran.

Om du vill minska loggningsutdata anger du "Logging:LogLevel:Microsoft:Error" i filen appsettings.Development.json:

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

Lösa en tjänst vid appstart

Följande kod visar hur du löser en begränsad tjänst under en begränsad tid när appen startar:

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

Omfångsverifiering

Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET

Mer information finns i Omfångsverifiering.

Begär tjänster

Tjänster och deras beroenden inom en ASP.NET Core-begäran exponeras via HttpContext.RequestServices.

Ramverket skapar ett omfång per begäran och RequestServices exponerar den begränsade tjänstleverantören. Alla begränsade tjänster är giltiga så länge begäran är aktiv.

Notera

Föredra att begära beroenden som konstruktorparametrar framför att lösa tjänster från RequestServices. Att begära beroenden som konstruktorparametrar ger klasser som är enklare att testa.

Utforma tjänster för beroendeinmatning

När du utformar tjänster för beroendeinmatning:

  • Undvik tillståndskänsliga, statiska klasser och medlemmar. Undvik att skapa globalt tillstånd genom att utforma appar så att de använder singleton-tjänster i stället.
  • Undvik direkt instansiering av beroende klasser inom tjänster. Direkt instansiering kopplar koden till en viss implementering.
  • Gör tjänsterna små, välräknade och enkelt testade.

Om en klass har många inmatade beroenden kan det vara ett tecken på att klassen har för många ansvarsområden och bryter mot SRP(Single Responsibility Principle). Försök att omstrukturera klassen genom att flytta en del av dess ansvarsområden till nya klasser. Tänk på att Razor Sidmodellklasser för sidor och MVC-kontrollantklasser bör fokusera på användargränssnittsproblem.

Avyttring av tjänster

Containern kallar Dispose för de IDisposable typer den skapar. Tjänster som löses från containern bör aldrig tas bort av utvecklaren. Om en typ eller fabrik registreras som en singleton tas singletonen bort automatiskt av containern.

I följande exempel skapas tjänsterna av tjänstcontainern och tas bort automatiskt: 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");
    }
}

Felsökningskonsolen visar följande utdata efter varje uppdatering av indexsidan:

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

Tjänster som inte har skapats av tjänstcontainern

Överväg följande kod:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

I föregående kod:

  • Tjänstinstanserna skapas inte av tjänstcontainern.
  • Ramverket tar inte bort tjänsterna automatiskt.
  • Utvecklaren ansvarar för att ta bort tjänsterna.

IDisposable-vägledning för tillfälliga och delade instanser

Se IDisposable-vägledning för tillfälliga och delade instanser i Beroendeinmatning i .NET

Ersättning av standardtjänstcontainer

Se Standardtjänstcontainerersättning i Beroendeinmatning i .NET

Rekommendationer

Se rekommendationer i beroendeinmatning i .NET

  • Undvik att använda tjänstlokaliserarmönster. Anropa till exempel inte GetService för att hämta en tjänstinstans när du kan använda DI i stället:

    felaktig:

    Felaktig kod

    Rätt:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • En annan variant av tjänstlokaliseraren att undvika är att injektera en fabrik som hanterar beroenden vid körning. Båda dessa metoder blandar inversion av kontroll strategier.

  • Undvik statisk åtkomst till HttpContext (till exempel IHttpContextAccessor.HttpContext).

DI är ett alternativ till statiska/globala objektåtkomstmönster. Du kanske inte kan dra nytta av fördelarna med DI om du blandar det med statisk objektåtkomst.

Orchard Core är ett ramverk för att bygga modulära applikationer för flera hyresgäster på ASP.NET Core. För mer information, se dokumentationen om Orchard Core.

Se Orchard Core-exempel exempel på hur du skapar modulära appar och appar för flera klientorganisationer med bara Orchard Core Framework utan någon av dess CMS-specifika funktioner.

Ramverksbaserade tjänster

Program.cs registrerar tjänster som appen använder, inklusive plattformsfunktioner som Entity Framework Core och ASP.NET Core MVC. Inledningsvis ges IServiceCollection till Program.cs tjänster som definierats av ramverket beroende på hur värden konfigurerades. För appar baserade på ASP.NET Core-mallar registrerar ramverket mer än 250 tjänster.

I följande tabell visas ett litet urval av dessa ramverksregistrerade tjänster:

Tjänsttyp Livstid
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Kortvarig
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Kortvarig
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Kortvarig
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Övergående
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Ytterligare resurser

Av Kirk Larkin, Steve Smithoch Brandon Dahler

ASP.NET Core stöder designmönstret för beroendeinjektion (DI), vilket är en teknik för att uppnå Inversion of Control (IoC) mellan klasser och deras beroenden.

Mer information om beroendeinmatning i MVC-styrenheter finns i Beroendeinmatning till styrenheter i ASP.NET Core.

Information om hur du använder beroendeinmatning i andra program än webbappar finns i Beroendeinmatning i .NET.

Mer information om beroendeinmatning av alternativ finns i mönstret Alternativ i ASP.NET Core.

Det här avsnittet innehåller information om beroendeinmatning i ASP.NET Core. Den primära dokumentationen om hur du använder beroendeinmatning finns i Beroendeinmatning i .NET.

Visa eller ladda ned exempelkod (hur du laddar ned)

Översikt över beroendeinmatning

Ett beroende är ett objekt som ett annat objekt är beroende av. Granska följande MyDependency-klass med en WriteMessage metod som andra klasser är beroende av:

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

En klass kan skapa en instans av klassen MyDependency för att använda sin WriteMessage-metod. I följande exempel är klassen MyDependency ett beroende av klassen IndexModel:


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

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

Klassen skapar och är direkt beroende av klassen MyDependency. Kodberoenden, till exempel i föregående exempel, är problematiska och bör undvikas av följande skäl:

  • Om du vill ersätta MyDependency med en annan implementering måste klassen IndexModel ändras.
  • Klassen IndexModel måste också konfigurera eventuella beroenden som MyDependency har. I ett stort projekt med flera klasser beroende på MyDependencyblir konfigurationskoden utspridd över appen.
  • Den här implementeringen är svår att enhetstesta.

Beroendeinjektion åtgärdar dessa problem genom:

  • Användning av ett gränssnitt eller en basklass för att abstrahera beroendeimplementeringen.
  • Registrering av beroendet i en tjänstcontainer. ASP.NET Core tillhandahåller en inbyggd tjänstcontainer IServiceProvider. Tjänster registreras vanligtvis i appens Program.cs-fil.
  • Införande av tjänsten i konstruktorn för den klass där den används. Ramverket tar på sig ansvaret att skapa en instans av beroendet och ta bort det när det inte längre behövs.

I exempelappendefinierar IMyDependency-gränssnittet WriteMessage-metoden:

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

Det här gränssnittet implementeras av en konkret typ, MyDependency:

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

Exempelappen registrerar IMyDependency-tjänsten med den konkreta typen MyDependency. Metoden AddScoped registrerar tjänsten med en begränsad livslängd, livslängden för en enskild begäran. Tjänstlivslängder beskrivs senare i det här avsnittet.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

I exempelappen begärs IMyDependency-tjänsten och används för att anropa metoden WriteMessage:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Genom att använda DI-mönstret, kontrollanten eller Razor sidan:

  • Använder inte den konkreta typen MyDependency, utan bara det IMyDependency-gränssnitt som den implementerar. Det gör det enkelt att ändra implementeringen utan att ändra kontrollanten eller Razor sidan.
  • Skapar inte någon instans av MyDependency, den skapas av DI-containern.

Implementeringen av IMyDependency-gränssnittet kan förbättras med hjälp av det inbyggda loggnings-API:et:

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

Den uppdaterade Program.cs registrerar den nya IMyDependency implementeringen:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

MyDependency2 beror på ILogger<TCategoryName>, som begärs i konstruktorn. ILogger<TCategoryName> är en tjänst som tillhandahålls av ramverket.

Det är inte ovanligt att använda beroendeinmatning på ett kedjat sätt. Varje begärt beroende begär i sin tur sina egna beroenden. Containern löser beroendena i diagrammet och returnerar den fullständigt lösta tjänsten. Den kollektiva uppsättningen beroenden som måste lösas kallas vanligtvis för ett beroendeträd, beroendediagrameller objektdiagram.

Containern löser ILogger<TCategoryName> genom att dra nytta av (generiska) öppna typer, vilket eliminerar behovet av att registrera varje (generisk) konstruerad typ.

Inom beroendeinjektionsterminologi är en tjänst:

  • Är vanligtvis ett objekt som tillhandahåller en tjänst till andra objekt, till exempel IMyDependency-tjänsten.
  • Är inte relaterad till en webbtjänst, även om tjänsten kan använda en webbtjänst.

Ramverket ger ett robust loggning system. De IMyDependency implementeringar som visas i föregående exempel skrevs för att demonstrera grundläggande DI, inte för att implementera loggning. De flesta appar ska inte behöva skriva loggare. Följande kod visar hur du använder standardloggningen, som inte kräver att några tjänster registreras:

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

Med hjälp av föregående kod behöver du inte uppdatera Program.cseftersom loggning tillhandahålls av ramverket.

Registrera grupper av tjänster med tilläggsmetoder

ASP.NET Core-ramverket använder en konvention för att registrera en grupp relaterade tjänster. Konventionen är att använda en enda Add{GROUP_NAME} tilläggsmetod för att registrera alla tjänster som krävs av en ramverksfunktion. Metoden AddControllers tilläggsmetod registrerar till exempel de tjänster som krävs för MVC-kontroller.

Följande kod genereras av mallen Razor Pages med hjälp av enskilda användarkonton och visar hur du lägger till ytterligare tjänster i containern med hjälp av tilläggsmetoderna AddDbContext och 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();

Överväg följande som registrerar tjänster och konfigurerar alternativ:

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

Relaterade grupper av registreringar kan flyttas till en tilläggsmetod för att registrera tjänster. Till exempel läggs konfigurationstjänsterna till i följande klass:

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

De återstående tjänsterna registreras i en liknande klass. Följande kod använder de nya tilläggsmetoderna för att registrera tjänsterna:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddRazorPages();

var app = builder.Build();

Obs! Varje services.Add{GROUP_NAME} tilläggsmetod lägger till och kan konfigurera tjänster. Till exempel lägger AddControllersWithViews till de tjänster som MVC-styrenheter med vyer kräver och AddRazorPages lägger till de tjänster som Razor Pages kräver.

Tjänstlivslängd

Se livslängder för tjänster i beroendeinjektion i .NET

Om du vill använda begränsade tjänster i mellanprogram använder du någon av följande metoder:

  • Mata in tjänsten i mellanprogrammets Invoke- eller InvokeAsync-metod. Om du använder konstruktorinmatning utlöser ett körningsundantag eftersom det tvingar den begränsade tjänsten att bete sig som en singleton. Exemplet i avsnittet Livslängd och registreringsalternativ demonstrerar InvokeAsync metodiken.
  • Använd Factory-baserade mellanprogram. Mellanprogram som registrerats med den här metoden aktiveras per klientbegäran (anslutning), vilket gör att begränsade tjänster kan matas in i mellanprogrammets konstruktor.

Mer information finns i Skriv anpassad ASP.NET Core-mellanprogramvara.

Metoder för tjänstregistrering

Se Tjänstregistreringsmetoder i Beroendeinmatning i .NET

Det är vanligt att använda flera implementeringar när modelleringstyper för att testa.

Registrering av en tjänst med endast en implementeringstyp motsvarar registrering av tjänsten med samma implementerings- och tjänsttyp. Det är därför flera implementeringar av en tjänst inte kan registreras med de metoder som inte använder en explicit tjänsttyp. Dessa metoder kan registrera flera instanser av en tjänst, men alla har samma implementering typ.

Någon av ovanstående tjänstregistreringsmetoder kan användas för att registrera flera tjänstinstanser av samma tjänsttyp. I följande exempel anropas AddSingleton två gånger med IMyDependency som tjänsttyp. Det andra anropet till AddSingleton åsidosätter det föregående när det löses som IMyDependency och kompletterar det föregående när flera tjänster löses genom IEnumerable<IMyDependency>. Tjänsterna visas i den ordning de registrerades när de löstes via 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);
    }
}

Konstruktorinmatningsbeteende

Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET

Entity Framework-kontexter

Som standard läggs Entity Framework-kontexter till i tjänstcontainern med hjälp av den begränsade livslängden eftersom webbappdatabasåtgärder normalt begränsas till klientbegäran. Om du vill använda en annan livstid, ange livstiden med hjälp av en AddDbContext överbelastning. Tjänster med en viss livslängd bör inte använda en databaskontext med en livslängd som är kortare än tjänstens livslängd.

Alternativ för livslängd och registrering

För att visa skillnaden mellan tjänstlivslängder och deras registreringsalternativ bör du överväga följande gränssnitt som representerar en uppgift som en åtgärd med en identifierare, OperationId. Beroende på hur livslängden för en åtgärds tjänst har konfigurerats för följande gränssnitt tillhandahåller containern antingen samma eller olika instanser av tjänsten när en klass begär det:

public interface IOperation
{
    string OperationId { get; }
}

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

Följande Operation-klass implementerar alla föregående gränssnitt. Konstruktorn Operation genererar ett GUID och lagrar de sista 4 tecknen i egenskapen OperationId:

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

    public string OperationId { get; }
}

Följande kod skapar flera registreringar av klassen Operation enligt de namngivna livslängderna:

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

Exempelappen visar objektlivslängder både inom och mellan begäranden. IndexModel och mellanprogram begär varje typ av IOperation typ och loggar OperationId för var och en:

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

På samma sätt som i IndexModellöser mellanprogrammet samma tjänster:

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

Omfattade och tillfälliga tjänster måste lösas i metoden InvokeAsync:

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

Loggningsutdata visar:

  • Tillfälliga objekt är alltid olika. Det tillfälliga OperationId värdet skiljer sig i IndexModel och i mellanprogrammet.
  • Omfångsbegränsade objekt är desamma för en viss begäran men skiljer sig åt mellan varje ny begäran.
  • Singleton- objekt är desamma för varje begäran.

Om du vill minska loggningsutdata anger du "Logging:LogLevel:Microsoft:Error" i filen appsettings.Development.json:

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

Lösa en tjänst vid appstart

Följande kod visar hur du löser en begränsad tjänst under en begränsad tid när appen startar:

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

Omfångsverifiering

Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET

Mer information finns i Omfångsverifiering.

Begär tjänster

Tjänster och deras beroenden inom en ASP.NET Core-begäran exponeras via HttpContext.RequestServices.

Ramverket skapar ett omfång per begäran och RequestServices exponerar den begränsade tjänstleverantören. Alla begränsade tjänster är giltiga så länge begäran är aktiv.

Not

Föredra att begära beroenden som konstruktorparametrar snarare än att lösa tjänster från RequestServices. Att begära beroenden som konstruktorparametrar ger klasser som är enklare att testa.

Utforma tjänster för beroendeinmatning

När du utformar tjänster för beroendeinjektion:

  • Undvik tillståndskänsliga, statiska klasser och medlemmar. Undvik att skapa globalt tillstånd genom att utforma appar så att de använder singleton-tjänster i stället.
  • Undvik direkt instansiering av beroende klasser inom tjänster. Direkt instansiering kopplar koden till en viss implementering.
  • Gör tjänsterna små, välräknade och enkelt testade.

Om en klass har många inmatade beroenden kan det vara ett tecken på att klassen har för många ansvarsområden och bryter mot SRP(Single Responsibility Principle). Försök att omstrukturera klassen genom att flytta en del av dess ansvarsområden till nya klasser. Tänk på att Razor Sidmodellklasser för sidor och MVC-kontrollantklasser bör fokusera på användargränssnittsproblem.

Avyttring av tjänster

Containern anropar Dispose för IDisposable-typer den skapar. Tjänster som löses från containern bör aldrig tas bort av utvecklaren. Om en typ eller fabrik registreras som en singleton tas singletonen bort automatiskt av containern.

I följande exempel skapas tjänsterna av tjänstcontainern och tas bort automatiskt: 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");
    }
}

Felsökningskonsolen visar följande utdata efter varje uppdatering av indexsidan:

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

Tjänster som inte har skapats av tjänstcontainern

Överväg följande kod:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

I föregående kod:

  • Tjänstinstanserna skapas inte av tjänstcontainern.
  • Ramverket tar inte bort tjänsterna automatiskt.
  • Utvecklaren ansvarar för att ta bort tjänsterna.

Riktlinjer för användning av IDisposable för tillfälliga och delade instanser

Se IDisposable-vägledning för tillfälliga och delade instanser i Beroendeinmatning i .NET

Ersättning av standardtjänstcontainer

Se Standardtjänstcontainerersättning i Beroendeinmatning i .NET

Rekommendationer

Se rekommendationer i beroendeinmatning i .NET

  • Undvik att använda tjänstlokaliserarmönster. Anropa till exempel inte GetService för att hämta en tjänstinstans när du kan använda DI i stället:

    felaktig:

    Felaktig kod

    Rätt:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • En annan variant av tjänstlokaliserare att undvika är att injicera en fabrik som löser beroenden vid körtid. Båda dessa metoder blandar Inversion of Control-strategier.

  • Undvik statisk åtkomst till HttpContext (till exempel IHttpContextAccessor.HttpContext).

DI är ett alternativ till statiska/globala objektåtkomstmönster. Du kanske inte kan dra nytta av fördelarna med DI om du blandar det med statisk objektåtkomst.

Orchard Core är ett programramverk för att skapa modulära program i en fleranvändarmiljö på ASP.NET Core. För mer information, se dokumentationen om Orchard Core.

Se Orchard Core-exempel för exempel på hur man bygger modulära och multi-tenant appar genom att använda endast Orchard Core Framework utan dess CMS-specifika funktioner.

Ramverksbaserade tjänster

Program.cs registrerar tjänster som appen använder, inklusive plattformsfunktioner som Entity Framework Core och ASP.NET Core MVC. Inledningsvis tillhandahåller IServiceCollection som ges till Program.cs tjänster som definieras av ramverket beroende på hur värden konfigurerades. För appar baserade på ASP.NET Core-mallar registrerar ramverket mer än 250 tjänster.

I följande tabell visas ett litet urval av dessa ramverksregistrerade tjänster:

Tjänsttyp Livstid
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Kortvarig
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Kortvarig
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Kortvarig
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Kortvarig
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Ytterligare resurser

Av Kirk Larkin, Steve Smith, Scott Addieoch Brandon Dahler

ASP.NET Core stöder designmönstret för beroendeinjektion (DI), vilket är en teknik för att uppnå inversion av kontroll (IoC) mellan klasser och deras beroenden.

Mer information om beroendeinmatning i MVC-styrenheter finns i Beroendeinmatning till styrenheter i ASP.NET Core.

Information om hur du använder beroendeinmatning i andra program än webbappar finns i Beroendeinmatning i .NET.

Mer information om beroendeinmatning av alternativ finns i mönstret Alternativ i ASP.NET Core.

Det här avsnittet innehåller information om beroendeinmatning i ASP.NET Core. Den primära dokumentationen om hur du använder beroendeinmatning finns i Beroendeinmatning i .NET.

Visa eller ladda ned exempelkod (hur du laddar ned)

Översikt över beroendeinmatning

Ett beroende är ett objekt som ett annat objekt är beroende av. Granska följande MyDependency-klass med en WriteMessage metod som andra klasser är beroende av:

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

En klass kan skapa en instans av klassen MyDependency för att använda sin WriteMessage-metod. I följande exempel är klassen MyDependency ett beroende av klassen IndexModel:

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

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

Klassen skapar och är direkt beroende av klassen MyDependency. Kodberoenden, till exempel i föregående exempel, är problematiska och bör undvikas av följande skäl:

  • Om du vill ersätta MyDependency med en annan implementering måste klassen IndexModel ändras.
  • Om MyDependency har beroenden ska de också konfigureras av IndexModel-klassen. I ett stort projekt med flera klasser beroende på MyDependencyblir konfigurationskoden utspridd över appen.
  • Den här implementeringen är svår att enhetstesta. Appen bör använda en modell- eller stub-MyDependency-klass, vilket inte är möjligt med den här metoden.

Beroendeinjektion åtgärdar dessa problem genom:

  • Användning av ett gränssnitt eller en basklass för att abstrahera beroendeimplementeringen.
  • Registrering av ett beroende i en tjänstekontainer. ASP.NET Core tillhandahåller en inbyggd tjänstcontainer IServiceProvider. Tjänster registreras vanligtvis i appens Startup.ConfigureServices metod.
  • Inmatning av tjänsten i konstruktorn för den klass där den används. Ramverket tar på sig ansvaret att skapa en instans av beroendet och ta bort det när det inte längre behövs.

I exempelappendefinierar IMyDependency-gränssnittet WriteMessage-metoden:

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

Det här gränssnittet implementeras av en konkret typ, MyDependency:

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

Exempelappen registrerar IMyDependency-tjänsten med den konkreta typen MyDependency. Metoden AddScoped registrerar tjänsten med en begränsad livslängd, livslängden för en enskild begäran. Tjänstlivslängder beskrivs senare i det här avsnittet.

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

    services.AddRazorPages();
}

I exempelappen begärs IMyDependency-tjänsten och används för att anropa metoden WriteMessage:

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Med hjälp av DI-mönstret, kontrolleraren:

  • Använder inte betongtypen MyDependency, bara det IMyDependency gränssnitt som implementeras. Det gör det enkelt att ändra den implementering som kontrollanten använder utan att ändra kontrollanten.
  • Skapar inte någon instans av MyDependency, den skapas av DI-containern.

Implementeringen av IMyDependency-gränssnittet kan förbättras med hjälp av det inbyggda loggnings-API:et:

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

Den uppdaterade ConfigureServices-metoden registrerar den nya IMyDependency implementeringen:

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

    services.AddRazorPages();
}

MyDependency2 ber om ILogger<TCategoryName>, som den begär i konstruktorn. ILogger<TCategoryName> är en tjänst som tillhandahålls av ramverket.

Det är inte ovanligt att använda beroendeinmatning på ett kedjat sätt. Varje begärt beroende begär i sin tur sina egna beroenden. Containern löser beroendena i diagrammet och returnerar den fullständigt lösta tjänsten. Den kollektiva uppsättningen beroenden som måste lösas kallas vanligtvis för ett beroendeträd, beroendediagrameller objektdiagram.

Containern löser ILogger<TCategoryName> genom att dra nytta av (generiska) öppna typer, vilket eliminerar behovet av att registrera varje (generisk) konstruerad typ.

I sambandinmatningsterminologin är en tjänst:

  • Är vanligtvis ett objekt som tillhandahåller en tjänst till andra objekt, till exempel IMyDependency-tjänsten.
  • Är inte relaterad till en webbtjänst, även om tjänsten kan använda en webbtjänst.

Ramverket ger ett robust loggning system. De IMyDependency implementeringar som visas i föregående exempel skrevs för att demonstrera grundläggande DI, inte för att implementera loggning. De flesta appar ska inte behöva skriva loggare. Följande kod visar hur du använder standardloggningen, som inte kräver att några tjänster registreras i 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);
    }
}

Med hjälp av föregående kod behöver du inte uppdatera ConfigureServiceseftersom loggning tillhandahålls av ramverket.

Tjänster som införs i uppstart

Tjänster kan matas in i Startup konstruktorn och metoden Startup.Configure.

Endast följande tjänster kan injiceras i konstruktorn Startup när du använder den generiska värden (IHostBuilder):

Alla tjänster som är registrerade med DI-containern kan matas in i metoden Startup.Configure:

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

Mer information finns i App-start i ASP.NET Core och Åtkomstkonfiguration i Startup.

Registrera grupper av tjänster med tilläggsmetoder

ASP.NET Core-ramverket använder en konvention för att registrera en grupp relaterade tjänster. Konventionen är att använda en enda Add{GROUP_NAME} tilläggsmetod för att registrera alla tjänster som krävs av en ramverksfunktion. Tilläggsmetoden AddControllers registrerar till exempel de tjänster som krävs för MVC-kontroller.

Följande kod genereras av mallen Razor Pages med hjälp av enskilda användarkonton och visar hur du lägger till ytterligare tjänster i containern med hjälp av tilläggsmetoderna AddDbContext och 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();
}

Överväg följande ConfigureServices metod, som registrerar tjänster och konfigurerar alternativ:

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

Relaterade grupper av registreringar kan flyttas till en tilläggsmetod för att registrera tjänster. Till exempel läggs konfigurationstjänsterna till i följande klass:

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

De återstående tjänsterna registreras i en liknande klass. Följande ConfigureServices metod använder de nya tilläggsmetoderna för att registrera tjänsterna:

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

    services.AddRazorPages();
}

Obs! Varje services.Add{GROUP_NAME} tilläggsmetod lägger till och kan konfigurera tjänster. Till exempel lägger AddControllersWithViews till de tjänster som MVC-styrenheter med vyer kräver och AddRazorPages lägger till de tjänster som Razor Pages kräver. Vi rekommenderar att appar följer namngivningskonventionen för att skapa tilläggsmetoder i namnrymden Microsoft.Extensions.DependencyInjection. Skapa tilläggsmetoder i Microsoft.Extensions.DependencyInjection namnrymd:

  • Kapslar in grupper av tjänstregistreringar.
  • Ger bekväm IntelliSense åtkomst till tjänsten.

Tjänstlivslängd

Se livslängder för tjänster i beroendeinjektion i .NET

Om du vill använda begränsade tjänster i mellanprogram använder du någon av följande metoder:

  • Mata in tjänsten i mellanprogrammets Invoke- eller InvokeAsync-metod. Om du använder konstruktorinmatning utlöser ett körningsundantag eftersom det tvingar den begränsade tjänsten att bete sig som en singleton. Exemplet i avsnittet Livslängd och registrering visar InvokeAsync metod.
  • Använd Factory-baserade mellanprogram. Mellanprogram som registrerats med den här metoden aktiveras per klientbegäran (anslutning), vilket gör att begränsade tjänster kan matas in i mellanprogrammets InvokeAsync-metod.

Mer information finns i Write custom ASP.NET Core middleware.

Metoder för tjänstregistrering

Se Tjänstregistreringsmetoder i Beroendeinmatning i .NET

Det är vanligt att använda flera implementeringar när man mockar typer för testning.

Registrering av en tjänst med endast en implementeringstyp motsvarar registrering av tjänsten med samma implementerings- och tjänsttyp. Det är därför flera implementeringar av en tjänst inte kan registreras med de metoder som inte använder en explicit tjänsttyp. Dessa metoder kan registrera flera instanser av en tjänst, men alla har samma implementering typ.

Någon av ovanstående tjänstregistreringsmetoder kan användas för att registrera flera tjänstinstanser av samma tjänsttyp. I följande exempel anropas AddSingleton två gånger med IMyDependency som tjänsttyp. Det andra anropet till AddSingleton åsidosätter det föregående när det löses som IMyDependency och läggs till det föregående när flera tjänster löses via IEnumerable<IMyDependency>. Tjänsterna visas i den ordning de registrerades när de löstes via 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);
    }
}

Konstruktorinmatningsbeteende

Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET

Entity Framework-kontexter

Som standard läggs Entity Framework-kontexter till i tjänstcontainern med hjälp av den begränsade livslängden eftersom webbappdatabasåtgärder normalt begränsas till klientbegäran. Om du vill använda en annan livslängd anger du livslängden med hjälp av en AddDbContext överlagring. Tjänster med en viss livslängd bör inte använda en databaskontext med en livslängd som är kortare än tjänstens livslängd.

Alternativ för livslängd och registrering

För att visa skillnaden mellan tjänstlivslängder och deras registreringsalternativ bör du överväga följande gränssnitt som representerar en uppgift som en åtgärd med en identifierare, OperationId. Beroende på hur livslängden för en åtgärds tjänst har konfigurerats för följande gränssnitt tillhandahåller containern antingen samma eller olika instanser av tjänsten när en klass begär det:

public interface IOperation
{
    string OperationId { get; }
}

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

Följande Operation-klass implementerar alla föregående gränssnitt. Konstruktorn Operation genererar ett GUID och lagrar de sista 4 tecknen i egenskapen OperationId:

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

    public string OperationId { get; }
}

Metoden Startup.ConfigureServices skapar flera registreringar av klassen Operation enligt de namngivna livslängderna:

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

    services.AddRazorPages();
}

Exempelappen visar objektlivslängder både inom och mellan begäranden. IndexModel och mellanprogrammet begär varje IOperation typ och loggar OperationId för var och en:

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

På samma sätt som i IndexModellöser mellanprogrammet samma tjänster:

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

Tjänster med omfattning måste lösas i metoden InvokeAsync:

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

Loggningsutdata visar:

  • Tillfälliga objekt är alltid olika. Det tillfälliga OperationId värdet skiljer sig i IndexModel och i mellanprogrammet.
  • Omfångsbegränsade objekt är desamma för en viss begäran men skiljer sig åt mellan varje ny begäran.
  • Singleton- objekt är desamma för varje begäran.

Om du vill minska loggningsutdata anger du "Logging:LogLevel:Microsoft:Error" i filen appsettings.Development.json:

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

Anropa tjänster från main

Skapa en IServiceScope med IServiceScopeFactory.CreateScope för att lösa en begränsad tjänst inom appens omfång. Den här metoden är användbar för att komma åt en begränsad tjänst vid start för att köra initieringsuppgifter.

I följande exempel visas hur du kommer åt den begränsade IMyDependency-tjänsten och anropar dess WriteMessage-metod i 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>();
            });
}

Omfångsverifiering

Se beteendet för konstruktorinmatning i Beroendeinmatning i .NET

Mer information finns i Omfångsverifiering.

Begär tjänster

Tjänster och deras beroenden inom en ASP.NET Core-begäran exponeras via HttpContext.RequestServices.

Ramverket skapar ett omfång per begäran och RequestServices exponerar den begränsade tjänstleverantören. Alla begränsade tjänster är giltiga så länge begäran är aktiv.

Not

Det är bättre att begära beroenden som konstruktorparametrar istället för att lösa tjänster från RequestServices. Att begära beroenden som konstruktorparametrar ger klasser som är enklare att testa.

Utforma tjänster för beroendeinmatning

När du utformar tjänster för beroendeinjektion:

  • Undvik tillståndskänsliga, statiska klasser och medlemmar. Undvik att skapa globalt tillstånd genom att utforma appar så att de använder singleton-tjänster i stället.
  • Undvik direkt instansiering av beroende klasser inom tjänster. Direkt instansiering kopplar koden till en viss implementering.
  • Gör tjänsterna små, välräknade och enkelt testade.

Om en klass har många inmatade beroenden kan det vara ett tecken på att klassen har för många ansvarsområden och bryter mot SRP(Single Responsibility Principle). Försök att omstrukturera klassen genom att flytta en del av dess ansvarsområden till nya klasser. Tänk på att Razor Sidmodellklasser för sidor och MVC-kontrollantklasser bör fokusera på användargränssnittsproblem.

Avyttring av tjänster

Containern anropar Dispose för de typer IDisposable som den skapar. Tjänster som löses från containern bör aldrig tas bort av utvecklaren. Om en typ eller fabrik registreras som en singleton tas singletonen bort automatiskt av containern.

I följande exempel skapas tjänsterna av tjänstcontainern och tas bort automatiskt:

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

Felsökningskonsolen visar följande utdata efter varje uppdatering av indexsidan:

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

Tjänster som inte har skapats av tjänstcontainern

Överväg följande kod:

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

    services.AddRazorPages();
}

I föregående kod:

  • Tjänstinstanserna skapas inte av tjänstcontainern.
  • Ramverket tar inte bort tjänsterna automatiskt.
  • Utvecklaren ansvarar för att ta bort tjänsterna.

IDisposable-vägledning för tillfälliga och delade instanser

Se IDisposable-vägledning för tillfälliga och delade instanser i Beroendeinmatning i .NET

Ersättning av standardtjänstcontainer

Se Standardtjänstcontainerersättning i Beroendeinmatning i .NET

Rekommendationer

Se rekommendationer i beroendeinmatning i .NET

  • Undvik att använda service locator-mönster. Anropa till exempel inte GetService för att hämta en tjänstinstans när du kan använda DI i stället:

    felaktig:

    Felaktig kod

    Rätt:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • En annan variant av tjänstlokaliseraren att undvika är att införa en fabrik som resolverar beroenden under körning. Båda dessa metoder blandar inversion av kontroll strategier.

  • Undvik statisk åtkomst till HttpContext (till exempel IHttpContextAccessor.HttpContext).

  • Undvik anrop till BuildServiceProvider i ConfigureServices. Att anropa BuildServiceProvider sker vanligtvis när utvecklaren vill lösa en tjänst i ConfigureServices. Tänk till exempel på det fall där LoginPath läses in från konfigurationen. Undvik följande metod:

    felaktig kod som anropar BuildServiceProvider

    I föregående bild visas följande ASP0000 varning när du väljer den gröna vågiga linjen under services.BuildServiceProvider:

    ASP0000 Att anropa "BuildServiceProvider" från programkoden resulterar i att ytterligare en kopia av singleton-tjänster skapas. Överväg alternativ, till exempel beroendeinjektion av tjänster som parametrar för "Konfigurera".

    Att anropa BuildServiceProvider skapar en andra container, vilket kan generera inkonsekventa singletons och orsaka referenser till objektdiagram över flera containrar.

    Ett korrekt sätt att få LoginPath är att använda alternativmönstrets inbyggda stöd för DI:

    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();
    }
    
  • Engångstjänster som är tillfälliga samlas in av containern för att bortskaffas. Detta kan förvandlas till en minnesläcka om det löses ut från containern på den översta nivån.

  • Aktivera omfångsvalidering för att se till att appen inte har singletons som samlar in begränsade tjänster. Mer information finns i Omfångsverifiering.

Precis som med alla uppsättningar med rekommendationer kan det uppstå situationer där det krävs att du ignorerar en rekommendation. Undantag är sällsynta, mestadels specialfall inom själva ramverket.

DI är ett alternativ till statiska/globala objektåtkomstmönster. Du kanske inte kan dra nytta av fördelarna med DI om du blandar det med statisk objektåtkomst.

Orchard Core är ett programramverk för att bygga modulära, fleranvändarapplikationer på ASP.NET Core. För mer information, se dokumentationen om Orchard Core .

Se Orchard Core-exempel för exempel på hur du skapar modulära och fleranvändarappar endast med Orchard Core Framework utan några CMS-specifika funktioner.

Ramverksbaserade tjänster

Metoden Startup.ConfigureServices registrerar tjänster som appen använder, inklusive plattformsfunktioner, till exempel Entity Framework Core och ASP.NET Core MVC. Till en början har IServiceCollection som tillhandahålls till ConfigureServices tjänster som definierats av ramverket beroende på hur värden konfigurerades. För appar baserade på ASP.NET Core-mallar registrerar ramverket mer än 250 tjänster.

I följande tabell visas ett litet urval av dessa ramverksregistrerade tjänster:

Tjänsttyp Livstid
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Kortvarig
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Övergående
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Kortvarig
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Kortvarig
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Ytterligare resurser