Inserimento delle dipendenze in ASP.NET Core

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Di Kirk Larkin, Steve Smith e Brandon Dahler

ASP.NET Core supporta il modello progettuale software per l'inserimento delle dipendenze, ovvero una tecnica per ottenere l'inversione del controllo (IoC) tra classi e relative dipendenze.

Per altre informazioni specifiche dell'inserimento delle dipendenze nei controller MVC, vedere Inserimento delle dipendenze nei controller in ASP.NET Core.

Per informazioni sull'uso dell'inserimento delle dipendenze nelle applicazioni diverse dalle app Web, vedere Inserimento delle dipendenze in .NET.

Per altre informazioni sull'inserimento di dipendenze di opzioni, vedere Modello di opzioni in ASP.NET Core.

Questo argomento fornisce informazioni sull'inserimento delle dipendenze in ASP.NET Core. La documentazione principale sull'uso dell'inserimento delle dipendenze è contenuta in Inserimento delle dipendenze in .NET.

Visualizzare o scaricare il codice di esempio (procedura per il download)

Panoramica dell'inserimento delle dipendenze

Una dipendenza è un oggetto da cui dipende un altro oggetto. Esaminare la classe MyDependency seguente con un metodo WriteMessage da cui dipendono altre classi:

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

Una classe può creare un'istanza della classe MyDependency per usare il relativo metodo WriteMessage. Nell'esempio seguente la classe MyDependency è una dipendenza della classe IndexModel:


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

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

La classe crea e dipende direttamente dalla classe MyDependency. Le dipendenze del codice, ad esempio nell'esempio precedente, sono problematiche e devono essere evitate per i motivi seguenti:

  • Per sostituire MyDependency con un'implementazione diversa, la classe IndexModel deve essere modificata.
  • Se MyDependency presenta delle dipendenze, devono essere configurate anche dalla classe IndexModel. In un progetto di grandi dimensioni con più classi che dipendono da MyDependency, il codice di configurazione diventa sparso in tutta l'app.
  • È difficile eseguire unit test di questa implementazione.

L'inserimento delle dipendenze consente di risolvere questi problemi tramite:

  • L'uso di un'interfaccia o di una classe di base per astrarre l'implementazione delle dipendenze.
  • La registrazione della dipendenza in un contenitore di servizi. ASP.NET Core offre il contenitore di servizi predefinito IServiceProvider. I servizi vengono in genere registrati nel file dell'app Program.cs .
  • L'inserimento del servizio nel costruttore della classe in cui viene usato. Il framework si assume la responsabilità della creazione di un'istanza della dipendenza e della sua eliminazione quando non è più necessaria.

Nell'app di esempio l'interfaccia IMyDependencydefinisce il WriteMessage metodo :

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

Questa interfaccia viene implementata da un tipo concreto, MyDependency:

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

L'app di esempio registra il IMyDependency servizio con il tipo MyDependencyconcreto . Il AddScoped metodo registra il servizio con una durata con ambito, ovvero la durata di una singola richiesta. Le durate dei servizi sono descritte più avanti in questo argomento.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

Nell'app di esempio, il IMyDependency servizio viene richiesto e usato per chiamare il WriteMessage metodo :

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Usando il modello di inserimento delle dipendenze, il controller o Razor la pagina:

  • Non usa il tipo MyDependencyconcreto , solo l'interfaccia IMyDependency implementata. In questo modo è facile modificare l'implementazione senza modificare il controller o Razor la pagina.
  • Non crea un'istanza di MyDependency, viene creata dal contenitore di inserimento delle dipendenze.

L'implementazione dell'interfaccia IMyDependency può essere migliorata usando l'API di registrazione predefinita:

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

L'aggiornamento Program.cs registra la nuova IMyDependency implementazione:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

MyDependency2 dipende da ILogger<TCategoryName>, che viene richiesto nel costruttore. ILogger<TCategoryName> è un servizio fornito dal framework.

Non è insolito usare l'inserimento delle dipendenze in modo concatenato. Ogni dipendenza richiesta richiede a sua volta le proprie dipendenze. Il contenitore risolve le dipendenze nel grafico e restituisce il servizio completamente risolto. Il set di dipendenze che devono essere risolte viene generalmente chiamato albero delle dipendenze o grafico dipendenze o grafico degli oggetti.

Il contenitore risolve ILogger<TCategoryName> avvalendosi dei tipi aperti (generici), eliminando la necessità di registrare ogni tipo costruito (generico).

Nella terminologia di inserimento delle dipendenze, un servizio:

  • In genere è un oggetto che fornisce un servizio ad altri oggetti, ad esempio il servizio IMyDependency.
  • Non è correlato a un servizio Web, anche se il servizio può usare un servizio Web.

Il framework fornisce un sistema di registrazione affidabile. Le implementazioni IMyDependency illustrate negli esempi precedenti sono state scritte per illustrare l'inserimento delle dipendenze di base, non per implementare la registrazione. La maggior parte delle app non deve scrivere logger. Il codice seguente illustra l'uso della registrazione predefinita, che non richiede la registrazione di alcun servizio:

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

Usando il codice precedente, non è necessario aggiornare Program.cs, perché la registrazione viene fornita dal framework.

Registrare gruppi di servizi con metodi di estensione

Il framework ASP.NET Core usa una convenzione per la registrazione di un gruppo di servizi correlati. La convenzione consiste nell'usare un singolo metodo di estensione Add{GROUP_NAME} per registrare tutti i servizi richiesti da una funzionalità del framework. Ad esempio, il AddControllers metodo di estensione registra i servizi necessari per i controller MVC.

Il codice seguente viene generato dal Razor modello Pages usando singoli account utente e illustra come aggiungere altri servizi al contenitore usando i metodi AddDbContext di estensione e 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();

Considerare il codice seguente che registra i servizi e configura le opzioni:

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

I gruppi correlati di registrazioni possono essere spostati in un metodo di estensione per registrare i servizi. Ad esempio, i servizi di configurazione vengono aggiunti alla classe seguente:

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

I servizi rimanenti vengono registrati in una classe simile. Il codice seguente usa i nuovi metodi di estensione per registrare i servizi:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddRazorPages();

var app = builder.Build();

Nota: ogni metodo di estensione services.Add{GROUP_NAME} aggiunge e configura potenzialmente i servizi. Ad esempio, AddControllersWithViews aggiunge i controller MVC dei servizi con visualizzazioni richiesti e AddRazorPages aggiunge le pagina Razor dei servizi richieste.

Durate del servizio

Vedere Durata del servizio in Inserimento delle dipendenze in .NET

Per usare i servizi con ambito nel middleware, usare uno degli approcci seguenti:

  • Inserire il servizio nel metodo o InvokeAsync del Invoke middleware. L'uso dell'inserimento del costruttore genera un'eccezione di runtime perché forza il comportamento del servizio con ambito come un singleton. L'esempio nella sezione Opzioni di durata e registrazione illustra l'approccio InvokeAsync .
  • Usare il middleware basato su factory. Il middleware registrato con questo approccio viene attivato per ogni richiesta client (connessione), che consente l'inserimento di servizi con ambito nel costruttore del middleware.

Per altre informazioni, vedere Scrivere middleware personalizzato ASP.NET Core.

Metodi di registrazione del servizio

Vedere Metodi di registrazione del servizio in Inserimento delle dipendenze in .NET

È comune usare più implementazioni quando si simulano i tipi per i test.

La registrazione di un servizio con solo un tipo di implementazione equivale alla registrazione del servizio con lo stesso tipo di implementazione e di servizio. Ecco perché non è possibile registrare più implementazioni di un servizio usando i metodi che non accettano un tipo di servizio esplicito. Questi metodi possono registrare più istanze di un servizio, ma avranno tutti lo stesso tipo di implementazione.

Uno dei metodi di registrazione del servizio precedenti può essere usato per registrare più istanze del servizio dello stesso tipo di servizio. Nell'esempio seguente AddSingleton viene chiamato due volte con IMyDependency come tipo di servizio. La seconda chiamata a AddSingleton esegue l'override di quella precedente quando viene risolta come IMyDependency e si aggiunge a quella precedente quando più servizi vengono risolti tramite IEnumerable<IMyDependency>. I servizi vengono visualizzati nell'ordine in cui sono stati registrati quando sono stati risolti tramite 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);
    }
}

Servizi con chiave

I servizi con chiave fanno riferimento a un meccanismo per la registrazione e il recupero di servizi di inserimento delle dipendenze tramite chiavi. Un servizio è associato a una chiave chiamando AddKeyedSingleton (o AddKeyedScoped ) AddKeyedTransientper registrarlo. Accedere a un servizio registrato specificando la chiave con l'attributo [FromKeyedServices] . Il codice seguente illustra come usare i servizi con chiave:

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

Comportamento dell'inserimento del costruttore

Vedere Comportamento di inserimento del costruttore in Inserimento delle dipendenze in .NET

Contesti di Entity Framework

Per impostazione predefinita, i contesti di Entity Framework vengono aggiunti al contenitore del servizio usando la durata con ambito perché le operazioni del database dell'app Web sono in genere limitati alla richiesta client. Per usare una durata diversa, specificare la durata usando un AddDbContext overload. I servizi di una determinata durata non devono usare un contesto di database con una durata inferiore alla durata del servizio.

Opzioni di durata e di registrazione

Per illustrare la differenza tra la durata del servizio e le relative opzioni di registrazione, considerare le interfacce seguenti che rappresentano un'attività come operazione con un identificatore , OperationId. A seconda del modo in cui viene configurata la durata del servizio di un'operazione per le interfacce seguenti, il contenitore fornisce le stesse istanze o diverse del servizio quando richiesto da una classe:

public interface IOperation
{
    string OperationId { get; }
}

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

La classe seguente Operation implementa tutte le interfacce precedenti. Il Operation costruttore genera un GUID e archivia gli ultimi 4 caratteri nella OperationId proprietà :

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

    public string OperationId { get; }
}

Il codice seguente crea più registrazioni della Operation classe in base alle durate denominate:

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

L'app di esempio illustra la durata degli oggetti sia all'interno che tra le richieste. E IndexModel il middleware richiedono ogni tipo di IOperation tipo e registrano per OperationId ognuno di essi:

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

Analogamente a IndexModel, il middleware risolve gli stessi servizi:

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

I servizi con ambito e temporanei devono essere risolti nel InvokeAsync metodo :

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

L'output del logger mostra:

  • Gli oggetti temporanei sono sempre diversi. Il valore temporaneo OperationId è diverso in IndexModel e nel middleware.
  • Gli oggetti con ambito sono gli stessi per una determinata richiesta, ma differiscono per ogni nuova richiesta.
  • Gli oggetti Singleton sono gli stessi per ogni richiesta.

Per ridurre l'output della registrazione, impostare "Logging:LogLevel:Microsoft:Error" nel appsettings.Development.json file:

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

Risolvere un servizio all'avvio dell'app

Il codice seguente illustra come risolvere un servizio con ambito per una durata limitata all'avvio dell'app:

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

Convalida dell'ambito

Vedere Comportamento di inserimento del costruttore in Inserimento delle dipendenze in .NET

Per ulteriori informazioni, vedere Convalida dell'ambito.

Servizi di richiesta

I servizi e le relative dipendenze all'interno di una richiesta core ASP.NET vengono esposti tramite HttpContext.RequestServices.

Il framework crea un ambito per richiesta ed RequestServices espone il provider di servizi con ambito. Tutti i servizi con ambito sono validi fino a quando la richiesta è attiva.

Nota

Preferire la richiesta di dipendenze come parametri del costruttore rispetto alla risoluzione dei servizi da RequestServices. La richiesta di dipendenze come parametri del costruttore produce classi che sono più facili da testare.

Progettare i servizi per l'inserimento di dipendenze

Quando si progettano servizi per l'inserimento delle dipendenze:

  • Evitare classi e membri statici con stato. Evitare di creare uno stato globale progettando invece le app per l'uso dei servizi singleton.
  • Evitare la creazione diretta di istanze delle classi dipendenti all'interno di servizi. La creazione diretta di istanze associa il codice a una determinata implementazione.
  • Rendere i servizi di dimensioni ridotte, ben fattorizzati e facili da testare.

Se una classe ha molte dipendenze inserite, potrebbe essere un segno che la classe ha troppe responsabilità e viola il principio di responsabilità singola (SRP). Tentare di effettuare il refactoring della classe spostando alcune delle responsabilità in nuove classi. Tenere presente che Razor le classi del modello di pagina Pages e le classi di controller MVC devono concentrarsi sulle problematiche dell'interfaccia utente.

Eliminazione dei servizi

Il contenitore chiama Dispose per i tipi IDisposable creati. I servizi risolti dal contenitore non devono mai essere eliminati dallo sviluppatore. Se un tipo o una factory viene registrato come singleton, il contenitore elimina automaticamente il singleton.

Nell'esempio seguente i servizi vengono creati dal contenitore del servizio ed eliminati automaticamente: 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");
    }
}

La console di debug mostra l'output seguente dopo ogni aggiornamento della pagina Indice:

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

Servizi non creati dal contenitore del servizio

Osservare il codice seguente:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

Nel codice precedente:

  • Le istanze del servizio non vengono create dal contenitore del servizio.
  • Il framework non elimina automaticamente i servizi.
  • Lo sviluppatore è responsabile dell'eliminazione dei servizi.

Indicazioni IDisposable per le istanze temporanee e condivise

Vedere Linee guida IDisposable per l'istanza temporanea e condivisa in Inserimento delle dipendenze in .NET

Sostituzione del contenitore di servizi predefinito

Vedere Sostituzione predefinita del contenitore del servizio in Inserimento delle dipendenze in .NET

Consigli

Vedere Consigli in Inserimento delle dipendenze in .NET

  • Evitare di usare il modello di localizzatore del servizio. Ad esempio, non richiamare GetService per ottenere un'istanza del servizio quando è invece possibile usare l'inserimento delle dipendenze:

    Risposta errata:

    Codice non corretto

    Risposta corretta:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Un'altra variazione del localizzatore del servizio da evitare è inserire una factory che risolve le dipendenze in fase di esecuzione. Queste procedure combinano le strategie di inversione del controllo.

  • Evitare l'accesso statico a HttpContext (ad esempio IHttpContextAccessor.HttpContext).

L'inserimento di dipendenze è un'alternativa ai modelli di accesso agli oggetti statici/globali. Se l'inserimento di dipendenze viene usato con l'accesso agli oggetti statico i vantaggi non saranno evidenti.

Orchard Core è un framework applicativo per la creazione di applicazioni modulari multi-tenant in ASP.NET Core. Per altre informazioni, vedere la documentazione di Orchard Core.

Vedere gli esempi di Orchard Core per esempi di come creare app modulari e multi-tenant usando solo Orchard Core Framework senza alcuna funzionalità specifica di CMS.

Servizi forniti dal framework

Program.cs registra i servizi usati dall'app, incluse le funzionalità della piattaforma, ad esempio Entity Framework Core e ASP.NET Core MVC. Inizialmente, l'oggetto IServiceCollection fornito a Program.cs ha servizi definiti dal framework a seconda della modalità di configurazione dell'host. Per le app basate sui modelli ASP.NET Core, il framework registra più di 250 servizi.

La tabella seguente elenca un piccolo esempio di questi servizi registrati dal framework:

Tipo di servizio Durata
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Temporaneo
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Temporaneo
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Temporaneo
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Temporaneo
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Risorse aggiuntive

Di Kirk Larkin, Steve Smith e Brandon Dahler

ASP.NET Core supporta il modello progettuale software per l'inserimento delle dipendenze, ovvero una tecnica per ottenere l'inversione del controllo (IoC) tra classi e relative dipendenze.

Per altre informazioni specifiche dell'inserimento delle dipendenze nei controller MVC, vedere Inserimento delle dipendenze nei controller in ASP.NET Core.

Per informazioni sull'uso dell'inserimento delle dipendenze nelle applicazioni diverse dalle app Web, vedere Inserimento delle dipendenze in .NET.

Per altre informazioni sull'inserimento di dipendenze di opzioni, vedere Modello di opzioni in ASP.NET Core.

Questo argomento fornisce informazioni sull'inserimento delle dipendenze in ASP.NET Core. La documentazione principale sull'uso dell'inserimento delle dipendenze è contenuta in Inserimento delle dipendenze in .NET.

Visualizzare o scaricare il codice di esempio (procedura per il download)

Panoramica dell'inserimento delle dipendenze

Una dipendenza è un oggetto da cui dipende un altro oggetto. Esaminare la classe MyDependency seguente con un metodo WriteMessage da cui dipendono altre classi:

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

Una classe può creare un'istanza della classe MyDependency per usare il relativo metodo WriteMessage. Nell'esempio seguente la classe MyDependency è una dipendenza della classe IndexModel:


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

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

La classe crea e dipende direttamente dalla classe MyDependency. Le dipendenze del codice, ad esempio nell'esempio precedente, sono problematiche e devono essere evitate per i motivi seguenti:

  • Per sostituire MyDependency con un'implementazione diversa, la classe IndexModel deve essere modificata.
  • Se MyDependency presenta delle dipendenze, devono essere configurate anche dalla classe IndexModel. In un progetto di grandi dimensioni con più classi che dipendono da MyDependency, il codice di configurazione diventa sparso in tutta l'app.
  • È difficile eseguire unit test di questa implementazione.

L'inserimento delle dipendenze consente di risolvere questi problemi tramite:

  • L'uso di un'interfaccia o di una classe di base per astrarre l'implementazione delle dipendenze.
  • La registrazione della dipendenza in un contenitore di servizi. ASP.NET Core offre il contenitore di servizi predefinito IServiceProvider. I servizi vengono in genere registrati nel file dell'app Program.cs .
  • L'inserimento del servizio nel costruttore della classe in cui viene usato. Il framework si assume la responsabilità della creazione di un'istanza della dipendenza e della sua eliminazione quando non è più necessaria.

Nell'app di esempio l'interfaccia IMyDependencydefinisce il WriteMessage metodo :

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

Questa interfaccia viene implementata da un tipo concreto, MyDependency:

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

L'app di esempio registra il IMyDependency servizio con il tipo MyDependencyconcreto . Il AddScoped metodo registra il servizio con una durata con ambito, ovvero la durata di una singola richiesta. Le durate dei servizi sono descritte più avanti in questo argomento.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

Nell'app di esempio, il IMyDependency servizio viene richiesto e usato per chiamare il WriteMessage metodo :

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Usando il modello di inserimento delle dipendenze, il controller o Razor la pagina:

  • Non usa il tipo MyDependencyconcreto , solo l'interfaccia IMyDependency implementata. In questo modo è facile modificare l'implementazione senza modificare il controller o Razor la pagina.
  • Non crea un'istanza di MyDependency, viene creata dal contenitore di inserimento delle dipendenze.

L'implementazione dell'interfaccia IMyDependency può essere migliorata usando l'API di registrazione predefinita:

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

L'aggiornamento Program.cs registra la nuova IMyDependency implementazione:

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

var app = builder.Build();

MyDependency2 dipende da ILogger<TCategoryName>, che viene richiesto nel costruttore. ILogger<TCategoryName> è un servizio fornito dal framework.

Non è insolito usare l'inserimento delle dipendenze in modo concatenato. Ogni dipendenza richiesta richiede a sua volta le proprie dipendenze. Il contenitore risolve le dipendenze nel grafico e restituisce il servizio completamente risolto. Il set di dipendenze che devono essere risolte viene generalmente chiamato albero delle dipendenze o grafico dipendenze o grafico degli oggetti.

Il contenitore risolve ILogger<TCategoryName> avvalendosi dei tipi aperti (generici), eliminando la necessità di registrare ogni tipo costruito (generico).

Nella terminologia di inserimento delle dipendenze, un servizio:

  • In genere è un oggetto che fornisce un servizio ad altri oggetti, ad esempio il servizio IMyDependency.
  • Non è correlato a un servizio Web, anche se il servizio può usare un servizio Web.

Il framework fornisce un sistema di registrazione affidabile. Le implementazioni IMyDependency illustrate negli esempi precedenti sono state scritte per illustrare l'inserimento delle dipendenze di base, non per implementare la registrazione. La maggior parte delle app non deve scrivere logger. Il codice seguente illustra l'uso della registrazione predefinita, che non richiede la registrazione di alcun servizio:

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

Usando il codice precedente, non è necessario aggiornare Program.cs, perché la registrazione viene fornita dal framework.

Registrare gruppi di servizi con metodi di estensione

Il framework ASP.NET Core usa una convenzione per la registrazione di un gruppo di servizi correlati. La convenzione consiste nell'usare un singolo metodo di estensione Add{GROUP_NAME} per registrare tutti i servizi richiesti da una funzionalità del framework. Ad esempio, il AddControllers metodo di estensione registra i servizi necessari per i controller MVC.

Il codice seguente viene generato dal Razor modello Pages usando singoli account utente e illustra come aggiungere altri servizi al contenitore usando i metodi AddDbContext di estensione e 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();

Considerare il codice seguente che registra i servizi e configura le opzioni:

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

I gruppi correlati di registrazioni possono essere spostati in un metodo di estensione per registrare i servizi. Ad esempio, i servizi di configurazione vengono aggiunti alla classe seguente:

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

I servizi rimanenti vengono registrati in una classe simile. Il codice seguente usa i nuovi metodi di estensione per registrare i servizi:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

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

builder.Services.AddRazorPages();

var app = builder.Build();

Nota: ogni metodo di estensione services.Add{GROUP_NAME} aggiunge e configura potenzialmente i servizi. Ad esempio, AddControllersWithViews aggiunge i controller MVC dei servizi con visualizzazioni richiesti e AddRazorPages aggiunge le pagina Razor dei servizi richieste.

Durate del servizio

Vedere Durata del servizio in Inserimento delle dipendenze in .NET

Per usare i servizi con ambito nel middleware, usare uno degli approcci seguenti:

  • Inserire il servizio nel metodo o InvokeAsync del Invoke middleware. L'uso dell'inserimento del costruttore genera un'eccezione di runtime perché forza il comportamento del servizio con ambito come un singleton. L'esempio nella sezione Opzioni di durata e registrazione illustra l'approccio InvokeAsync .
  • Usare il middleware basato su factory. Il middleware registrato con questo approccio viene attivato per ogni richiesta client (connessione), che consente l'inserimento di servizi con ambito nel costruttore del middleware.

Per altre informazioni, vedere Scrivere middleware personalizzato ASP.NET Core.

Metodi di registrazione del servizio

Vedere Metodi di registrazione del servizio in Inserimento delle dipendenze in .NET

È comune usare più implementazioni quando si simulano i tipi per i test.

La registrazione di un servizio con solo un tipo di implementazione equivale alla registrazione del servizio con lo stesso tipo di implementazione e di servizio. Ecco perché non è possibile registrare più implementazioni di un servizio usando i metodi che non accettano un tipo di servizio esplicito. Questi metodi possono registrare più istanze di un servizio, ma avranno tutti lo stesso tipo di implementazione.

Uno dei metodi di registrazione del servizio precedenti può essere usato per registrare più istanze del servizio dello stesso tipo di servizio. Nell'esempio seguente AddSingleton viene chiamato due volte con IMyDependency come tipo di servizio. La seconda chiamata a AddSingleton esegue l'override di quella precedente quando viene risolta come IMyDependency e si aggiunge a quella precedente quando più servizi vengono risolti tramite IEnumerable<IMyDependency>. I servizi vengono visualizzati nell'ordine in cui sono stati registrati quando sono stati risolti tramite 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);
    }
}

Comportamento dell'inserimento del costruttore

Vedere Comportamento di inserimento del costruttore in Inserimento delle dipendenze in .NET

Contesti di Entity Framework

Per impostazione predefinita, i contesti di Entity Framework vengono aggiunti al contenitore del servizio usando la durata con ambito perché le operazioni del database dell'app Web sono in genere limitati alla richiesta client. Per usare una durata diversa, specificare la durata usando un AddDbContext overload. I servizi di una determinata durata non devono usare un contesto di database con una durata inferiore alla durata del servizio.

Opzioni di durata e di registrazione

Per illustrare la differenza tra la durata del servizio e le relative opzioni di registrazione, considerare le interfacce seguenti che rappresentano un'attività come operazione con un identificatore , OperationId. A seconda del modo in cui viene configurata la durata del servizio di un'operazione per le interfacce seguenti, il contenitore fornisce le stesse istanze o diverse del servizio quando richiesto da una classe:

public interface IOperation
{
    string OperationId { get; }
}

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

La classe seguente Operation implementa tutte le interfacce precedenti. Il Operation costruttore genera un GUID e archivia gli ultimi 4 caratteri nella OperationId proprietà :

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

    public string OperationId { get; }
}

Il codice seguente crea più registrazioni della Operation classe in base alle durate denominate:

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

L'app di esempio illustra la durata degli oggetti sia all'interno che tra le richieste. E IndexModel il middleware richiedono ogni tipo di IOperation tipo e registrano per OperationId ognuno di essi:

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

Analogamente a IndexModel, il middleware risolve gli stessi servizi:

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

I servizi con ambito e temporanei devono essere risolti nel InvokeAsync metodo :

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

L'output del logger mostra:

  • Gli oggetti temporanei sono sempre diversi. Il valore temporaneo OperationId è diverso in IndexModel e nel middleware.
  • Gli oggetti con ambito sono gli stessi per una determinata richiesta, ma differiscono per ogni nuova richiesta.
  • Gli oggetti Singleton sono gli stessi per ogni richiesta.

Per ridurre l'output della registrazione, impostare "Logging:LogLevel:Microsoft:Error" nel appsettings.Development.json file:

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

Risolvere un servizio all'avvio dell'app

Il codice seguente illustra come risolvere un servizio con ambito per una durata limitata all'avvio dell'app:

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

Convalida dell'ambito

Vedere Comportamento di inserimento del costruttore in Inserimento delle dipendenze in .NET

Per ulteriori informazioni, vedere Convalida dell'ambito.

Servizi di richiesta

I servizi e le relative dipendenze all'interno di una richiesta core ASP.NET vengono esposti tramite HttpContext.RequestServices.

Il framework crea un ambito per richiesta ed RequestServices espone il provider di servizi con ambito. Tutti i servizi con ambito sono validi fino a quando la richiesta è attiva.

Nota

Preferire la richiesta di dipendenze come parametri del costruttore rispetto alla risoluzione dei servizi da RequestServices. La richiesta di dipendenze come parametri del costruttore produce classi che sono più facili da testare.

Progettare i servizi per l'inserimento di dipendenze

Quando si progettano servizi per l'inserimento delle dipendenze:

  • Evitare classi e membri statici con stato. Evitare di creare uno stato globale progettando invece le app per l'uso dei servizi singleton.
  • Evitare la creazione diretta di istanze delle classi dipendenti all'interno di servizi. La creazione diretta di istanze associa il codice a una determinata implementazione.
  • Rendere i servizi di dimensioni ridotte, ben fattorizzati e facili da testare.

Se una classe ha molte dipendenze inserite, potrebbe essere un segno che la classe ha troppe responsabilità e viola il principio di responsabilità singola (SRP). Tentare di effettuare il refactoring della classe spostando alcune delle responsabilità in nuove classi. Tenere presente che Razor le classi del modello di pagina Pages e le classi di controller MVC devono concentrarsi sulle problematiche dell'interfaccia utente.

Eliminazione dei servizi

Il contenitore chiama Dispose per i tipi IDisposable creati. I servizi risolti dal contenitore non devono mai essere eliminati dallo sviluppatore. Se un tipo o una factory viene registrato come singleton, il contenitore elimina automaticamente il singleton.

Nell'esempio seguente i servizi vengono creati dal contenitore del servizio ed eliminati automaticamente: 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");
    }
}

La console di debug mostra l'output seguente dopo ogni aggiornamento della pagina Indice:

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

Servizi non creati dal contenitore del servizio

Osservare il codice seguente:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

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

Nel codice precedente:

  • Le istanze del servizio non vengono create dal contenitore del servizio.
  • Il framework non elimina automaticamente i servizi.
  • Lo sviluppatore è responsabile dell'eliminazione dei servizi.

Indicazioni IDisposable per le istanze temporanee e condivise

Vedere Linee guida IDisposable per l'istanza temporanea e condivisa in Inserimento delle dipendenze in .NET

Sostituzione del contenitore di servizi predefinito

Vedere Sostituzione predefinita del contenitore del servizio in Inserimento delle dipendenze in .NET

Consigli

Vedere Consigli in Inserimento delle dipendenze in .NET

  • Evitare di usare il modello di localizzatore del servizio. Ad esempio, non richiamare GetService per ottenere un'istanza del servizio quando è invece possibile usare l'inserimento delle dipendenze:

    Risposta errata:

    Codice non corretto

    Risposta corretta:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Un'altra variazione del localizzatore del servizio da evitare è inserire una factory che risolve le dipendenze in fase di esecuzione. Queste procedure combinano le strategie di inversione del controllo.

  • Evitare l'accesso statico a HttpContext (ad esempio IHttpContextAccessor.HttpContext).

L'inserimento di dipendenze è un'alternativa ai modelli di accesso agli oggetti statici/globali. Se l'inserimento di dipendenze viene usato con l'accesso agli oggetti statico i vantaggi non saranno evidenti.

Orchard Core è un framework applicativo per la creazione di applicazioni modulari multi-tenant in ASP.NET Core. Per altre informazioni, vedere la documentazione di Orchard Core.

Vedere gli esempi di Orchard Core per esempi di come creare app modulari e multi-tenant usando solo Orchard Core Framework senza alcuna funzionalità specifica di CMS.

Servizi forniti dal framework

Program.cs registra i servizi usati dall'app, incluse le funzionalità della piattaforma, ad esempio Entity Framework Core e ASP.NET Core MVC. Inizialmente, l'oggetto IServiceCollection fornito a Program.cs ha servizi definiti dal framework a seconda della modalità di configurazione dell'host. Per le app basate sui modelli ASP.NET Core, il framework registra più di 250 servizi.

La tabella seguente elenca un piccolo esempio di questi servizi registrati dal framework:

Tipo di servizio Durata
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Temporaneo
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Temporaneo
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Temporaneo
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Temporaneo
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Risorse aggiuntive

Di Kirk Larkin, Steve Smith, Scott Addie e Brandon Dahler

ASP.NET Core supporta il modello progettuale software per l'inserimento delle dipendenze, ovvero una tecnica per ottenere l'inversione del controllo (IoC) tra classi e relative dipendenze.

Per altre informazioni specifiche dell'inserimento delle dipendenze nei controller MVC, vedere Inserimento delle dipendenze nei controller in ASP.NET Core.

Per informazioni sull'uso dell'inserimento delle dipendenze nelle applicazioni diverse dalle app Web, vedere Inserimento delle dipendenze in .NET.

Per altre informazioni sull'inserimento di dipendenze di opzioni, vedere Modello di opzioni in ASP.NET Core.

Questo argomento fornisce informazioni sull'inserimento delle dipendenze in ASP.NET Core. La documentazione principale sull'uso dell'inserimento delle dipendenze è contenuta in Inserimento delle dipendenze in .NET.

Visualizzare o scaricare il codice di esempio (procedura per il download)

Panoramica dell'inserimento delle dipendenze

Una dipendenza è un oggetto da cui dipende un altro oggetto. Esaminare la classe MyDependency seguente con un metodo WriteMessage da cui dipendono altre classi:

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

Una classe può creare un'istanza della classe MyDependency per usare il relativo metodo WriteMessage. Nell'esempio seguente la classe MyDependency è una dipendenza della classe IndexModel:

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

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

La classe crea e dipende direttamente dalla classe MyDependency. Le dipendenze del codice, ad esempio nell'esempio precedente, sono problematiche e devono essere evitate per i motivi seguenti:

  • Per sostituire MyDependency con un'implementazione diversa, la classe IndexModel deve essere modificata.
  • Se MyDependency presenta delle dipendenze, devono essere configurate anche dalla classe IndexModel. In un progetto di grandi dimensioni con più classi che dipendono da MyDependency, il codice di configurazione diventa sparso in tutta l'app.
  • È difficile eseguire unit test di questa implementazione. L'app dovrebbe usare una classe MyDependency fittizia o stub, ma ciò non è possibile con questo approccio.

L'inserimento delle dipendenze consente di risolvere questi problemi tramite:

  • L'uso di un'interfaccia o di una classe di base per astrarre l'implementazione delle dipendenze.
  • La registrazione della dipendenza in un contenitore di servizi. ASP.NET Core offre il contenitore di servizi predefinito IServiceProvider. I servizi vengono in genere registrati nel metodo dell'app Startup.ConfigureServices .
  • L'inserimento del servizio nel costruttore della classe in cui viene usato. Il framework si assume la responsabilità della creazione di un'istanza della dipendenza e della sua eliminazione quando non è più necessaria.

Nell'app di esempio l'interfaccia IMyDependencydefinisce il WriteMessage metodo :

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

Questa interfaccia viene implementata da un tipo concreto, MyDependency:

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

L'app di esempio registra il IMyDependency servizio con il tipo MyDependencyconcreto . Il AddScoped metodo registra il servizio con una durata con ambito, ovvero la durata di una singola richiesta. Le durate dei servizi sono descritte più avanti in questo argomento.

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

    services.AddRazorPages();
}

Nell'app di esempio, il IMyDependency servizio viene richiesto e usato per chiamare il WriteMessage metodo :

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

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

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

Usando il modello di inserimento delle dipendenze, il controller:

  • Non usa il tipo MyDependencyconcreto , solo l'interfaccia IMyDependency implementata. In questo modo è facile modificare l'implementazione usata dal controller senza modificare il controller.
  • Non crea un'istanza di MyDependency, viene creata dal contenitore di inserimento delle dipendenze.

L'implementazione dell'interfaccia IMyDependency può essere migliorata usando l'API di registrazione predefinita:

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

Il metodo ConfigureServices aggiornato registra la nuova implementazione IMyDependency:

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

    services.AddRazorPages();
}

MyDependency2 dipende da ILogger<TCategoryName>, che viene richiesto nel costruttore. ILogger<TCategoryName> è un servizio fornito dal framework.

Non è insolito usare l'inserimento delle dipendenze in modo concatenato. Ogni dipendenza richiesta richiede a sua volta le proprie dipendenze. Il contenitore risolve le dipendenze nel grafico e restituisce il servizio completamente risolto. Il set di dipendenze che devono essere risolte viene generalmente chiamato albero delle dipendenze o grafico dipendenze o grafico degli oggetti.

Il contenitore risolve ILogger<TCategoryName> avvalendosi dei tipi aperti (generici), eliminando la necessità di registrare ogni tipo costruito (generico).

Nella terminologia di inserimento delle dipendenze, un servizio:

  • In genere è un oggetto che fornisce un servizio ad altri oggetti, ad esempio il servizio IMyDependency.
  • Non è correlato a un servizio Web, anche se il servizio può usare un servizio Web.

Il framework fornisce un sistema di registrazione affidabile. Le implementazioni IMyDependency illustrate negli esempi precedenti sono state scritte per illustrare l'inserimento delle dipendenze di base, non per implementare la registrazione. La maggior parte delle app non deve scrivere logger. Il codice seguente illustra l'uso della registrazione predefinita, che non richiede la registrazione di alcun servizio in 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);
    }
}

Usando il codice precedente, non è necessario aggiornare ConfigureServices, perché la registrazione viene fornita dal framework.

Servizi inseriti nell'avvio

I servizi possono essere inseriti nel Startup costruttore e nel Startup.Configure metodo .

Solo i servizi seguenti possono essere inseriti nel Startup costruttore quando si usa l'host generico (IHostBuilder):

Qualsiasi servizio registrato con il contenitore di inserimento delle dipendenze può essere inserito nel Startup.Configure metodo :

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

Per altre informazioni, vedere Avvio dell'app in ASP.NET configurazione di Core e Access in Avvio.

Registrare gruppi di servizi con metodi di estensione

Il framework ASP.NET Core usa una convenzione per la registrazione di un gruppo di servizi correlati. La convenzione consiste nell'usare un singolo metodo di estensione Add{GROUP_NAME} per registrare tutti i servizi richiesti da una funzionalità del framework. Ad esempio, il AddControllers metodo di estensione registra i servizi necessari per i controller MVC.

Il codice seguente viene generato dal Razor modello Pages usando singoli account utente e illustra come aggiungere altri servizi al contenitore usando i metodi AddDbContext di estensione e 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();
}

Considerare il metodo ConfigureServices seguente che registra i servizi e configura le opzioni:

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

I gruppi correlati di registrazioni possono essere spostati in un metodo di estensione per registrare i servizi. Ad esempio, i servizi di configurazione vengono aggiunti alla classe seguente:

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

I servizi rimanenti vengono registrati in una classe simile. Il metodo ConfigureServices seguente usa i nuovi metodi di estensione per registrare i servizi:

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

    services.AddRazorPages();
}

Nota: ogni metodo di estensione services.Add{GROUP_NAME} aggiunge e configura potenzialmente i servizi. Ad esempio, AddControllersWithViews aggiunge i controller MVC dei servizi con visualizzazioni richiesti e AddRazorPages aggiunge le pagina Razor dei servizi richieste. È consigliabile che le app seguano la convenzione di denominazione della creazione di metodi di estensione nello spazio dei nomi Microsoft.Extensions.DependencyInjection. La creazione dei metodi di estensione nello spazio dei nomi Microsoft.Extensions.DependencyInjection:

  • Incapsula gruppi di registrazioni dei servizi.
  • Offre l'utile accesso IntelliSense al servizio.

Durate del servizio

Vedere Durata del servizio in Inserimento delle dipendenze in .NET

Per usare i servizi con ambito nel middleware, usare uno degli approcci seguenti:

  • Inserire il servizio nel metodo o InvokeAsync del Invoke middleware. L'uso dell'inserimento del costruttore genera un'eccezione di runtime perché forza il comportamento del servizio con ambito come un singleton. L'esempio nella sezione Opzioni di durata e registrazione illustra l'approccio InvokeAsync .
  • Usare il middleware basato su factory. Il middleware registrato con questo approccio viene attivato per ogni richiesta client (connessione), che consente l'inserimento di servizi con ambito nel metodo del InvokeAsync middleware.

Per altre informazioni, vedere Scrivere middleware personalizzato ASP.NET Core.

Metodi di registrazione del servizio

Vedere Metodi di registrazione del servizio in Inserimento delle dipendenze in .NET

È comune usare più implementazioni quando si simulano i tipi per i test.

La registrazione di un servizio con solo un tipo di implementazione equivale alla registrazione del servizio con lo stesso tipo di implementazione e di servizio. Ecco perché non è possibile registrare più implementazioni di un servizio usando i metodi che non accettano un tipo di servizio esplicito. Questi metodi possono registrare più istanze di un servizio, ma avranno tutti lo stesso tipo di implementazione.

Uno dei metodi di registrazione del servizio precedenti può essere usato per registrare più istanze del servizio dello stesso tipo di servizio. Nell'esempio seguente AddSingleton viene chiamato due volte con IMyDependency come tipo di servizio. La seconda chiamata a AddSingleton esegue l'override di quella precedente quando viene risolta come IMyDependency e si aggiunge a quella precedente quando più servizi vengono risolti tramite IEnumerable<IMyDependency>. I servizi vengono visualizzati nell'ordine in cui sono stati registrati quando sono stati risolti tramite 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);
    }
}

Comportamento dell'inserimento del costruttore

Vedere Comportamento di inserimento del costruttore in Inserimento delle dipendenze in .NET

Contesti di Entity Framework

Per impostazione predefinita, i contesti di Entity Framework vengono aggiunti al contenitore del servizio usando la durata con ambito perché le operazioni del database dell'app Web sono in genere limitati alla richiesta client. Per usare una durata diversa, specificare la durata usando un AddDbContext overload. I servizi di una determinata durata non devono usare un contesto di database con una durata inferiore alla durata del servizio.

Opzioni di durata e di registrazione

Per illustrare la differenza tra la durata del servizio e le relative opzioni di registrazione, considerare le interfacce seguenti che rappresentano un'attività come operazione con un identificatore , OperationId. A seconda del modo in cui viene configurata la durata del servizio di un'operazione per le interfacce seguenti, il contenitore fornisce le stesse istanze o diverse del servizio quando richiesto da una classe:

public interface IOperation
{
    string OperationId { get; }
}

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

La classe seguente Operation implementa tutte le interfacce precedenti. Il Operation costruttore genera un GUID e archivia gli ultimi 4 caratteri nella OperationId proprietà :

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

    public string OperationId { get; }
}

Il Startup.ConfigureServices metodo crea più registrazioni della Operation classe in base alle durate denominate:

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

    services.AddRazorPages();
}

L'app di esempio illustra la durata degli oggetti sia all'interno che tra le richieste. E IndexModel il middleware richiedono ogni tipo di IOperation tipo e registrano per OperationId ognuno di essi:

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

Analogamente a IndexModel, il middleware risolve gli stessi servizi:

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

I servizi con ambito devono essere risolti nel InvokeAsync metodo :

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

L'output del logger mostra:

  • Gli oggetti temporanei sono sempre diversi. Il valore temporaneo OperationId è diverso in IndexModel e nel middleware.
  • Gli oggetti con ambito sono gli stessi per una determinata richiesta, ma differiscono per ogni nuova richiesta.
  • Gli oggetti Singleton sono gli stessi per ogni richiesta.

Per ridurre l'output della registrazione, impostare "Logging:LogLevel:Microsoft:Error" nel appsettings.Development.json file:

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

Chiamare i servizi da main

Creare un IServiceScopeIServiceScope con IServiceScopeFactory.CreateScope per risolvere un servizio con ambito nell'ambito di applicazione dell'app. Questo approccio è utile per l'accesso a un servizio con ambito all'avvio e per l'esecuzione di attività di inizializzazione.

Nell'esempio seguente viene illustrato come accedere al servizio con IMyDependency ambito e chiamare il relativo WriteMessage metodo in 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>();
            });
}

Convalida dell'ambito

Vedere Comportamento di inserimento del costruttore in Inserimento delle dipendenze in .NET

Per ulteriori informazioni, vedere Convalida dell'ambito.

Servizi di richiesta

I servizi e le relative dipendenze all'interno di una richiesta core ASP.NET vengono esposti tramite HttpContext.RequestServices.

Il framework crea un ambito per richiesta ed RequestServices espone il provider di servizi con ambito. Tutti i servizi con ambito sono validi fino a quando la richiesta è attiva.

Nota

Preferire la richiesta di dipendenze come parametri del costruttore rispetto alla risoluzione dei servizi da RequestServices. La richiesta di dipendenze come parametri del costruttore produce classi che sono più facili da testare.

Progettare i servizi per l'inserimento di dipendenze

Quando si progettano servizi per l'inserimento delle dipendenze:

  • Evitare classi e membri statici con stato. Evitare di creare uno stato globale progettando invece le app per l'uso dei servizi singleton.
  • Evitare la creazione diretta di istanze delle classi dipendenti all'interno di servizi. La creazione diretta di istanze associa il codice a una determinata implementazione.
  • Rendere i servizi di dimensioni ridotte, ben fattorizzati e facili da testare.

Se una classe ha molte dipendenze inserite, potrebbe essere un segno che la classe ha troppe responsabilità e viola il principio di responsabilità singola (SRP). Tentare di effettuare il refactoring della classe spostando alcune delle responsabilità in nuove classi. Tenere presente che Razor le classi del modello di pagina Pages e le classi di controller MVC devono concentrarsi sulle problematiche dell'interfaccia utente.

Eliminazione dei servizi

Il contenitore chiama Dispose per i tipi IDisposable creati. I servizi risolti dal contenitore non devono mai essere eliminati dallo sviluppatore. Se un tipo o una factory viene registrato come singleton, il contenitore elimina automaticamente il singleton.

Nell'esempio seguente i servizi vengono creati dal contenitore del servizio ed eliminati automaticamente:

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

La console di debug mostra l'output seguente dopo ogni aggiornamento della pagina Indice:

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

Servizi non creati dal contenitore del servizio

Osservare il codice seguente:

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

    services.AddRazorPages();
}

Nel codice precedente:

  • Le istanze del servizio non vengono create dal contenitore del servizio.
  • Il framework non elimina automaticamente i servizi.
  • Lo sviluppatore è responsabile dell'eliminazione dei servizi.

Indicazioni IDisposable per le istanze temporanee e condivise

Vedere Linee guida IDisposable per l'istanza temporanea e condivisa in Inserimento delle dipendenze in .NET

Sostituzione del contenitore di servizi predefinito

Vedere Sostituzione predefinita del contenitore del servizio in Inserimento delle dipendenze in .NET

Consigli

Vedere Consigli in Inserimento delle dipendenze in .NET

  • Evitare di usare il modello di localizzatore del servizio. Ad esempio, non richiamare GetService per ottenere un'istanza del servizio quando è invece possibile usare l'inserimento delle dipendenze:

    Risposta errata:

    Codice non corretto

    Risposta corretta:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Un'altra variazione del localizzatore del servizio da evitare è inserire una factory che risolve le dipendenze in fase di esecuzione. Queste procedure combinano le strategie di inversione del controllo.

  • Evitare l'accesso statico a HttpContext (ad esempio IHttpContextAccessor.HttpContext).

  • Evitare chiamate a BuildServiceProvider in ConfigureServices. La chiamata BuildServiceProvider in genere avviene quando lo sviluppatore vuole risolvere un servizio in ConfigureServices. Si consideri ad esempio il caso in cui l'oggetto LoginPath viene caricato dalla configurazione. Evitare l'approccio seguente:

    codice non valido che chiama BuildServiceProvider

    Nell'immagine precedente, selezionando la linea ondulata verde sotto services.BuildServiceProvider viene visualizzato l'avviso ASP0000 seguente:

    ASP0000 La chiamata di 'BuildServiceProvider' dal codice dell'applicazione comporta la creazione di una copia aggiuntiva dei servizi singleton. Prendere in considerazione alternative, ad esempio l'inserimento di servizi come parametri in 'Configure'.

    La chiamata BuildServiceProvider crea un secondo contenitore, che può creare singleton strappati e causare riferimenti a oggetti grafici in più contenitori.

    Un modo corretto per ottenere LoginPath consiste nell'usare il supporto predefinito del modello di opzioni per l'inserimento delle dipendenze:

    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();
    }
    
  • I servizi temporanei eliminabili vengono acquisiti dal contenitore per l'eliminazione. Ciò può trasformarsi in una perdita di memoria se risolta dal contenitore di livello superiore.

  • Abilitare la convalida dell'ambito per assicurarti che l'app non abbia singleton che acquisiscano servizi con ambito. Per ulteriori informazioni, vedere Convalida dell'ambito.

È tuttavia possibile che in alcuni casi queste raccomandazioni debbano essere ignorate. Le eccezioni sono rare e principalmente si tratta di casi speciali all'interno del framework stesso.

L'inserimento di dipendenze è un'alternativa ai modelli di accesso agli oggetti statici/globali. Se l'inserimento di dipendenze viene usato con l'accesso agli oggetti statico i vantaggi non saranno evidenti.

Orchard Core è un framework applicativo per la creazione di applicazioni modulari multi-tenant in ASP.NET Core. Per altre informazioni, vedere la documentazione di Orchard Core.

Vedere gli esempi di Orchard Core per esempi di come creare app modulari e multi-tenant usando solo Orchard Core Framework senza alcuna funzionalità specifica di CMS.

Servizi forniti dal framework

Il Startup.ConfigureServices metodo registra i servizi usati dall'app, incluse le funzionalità della piattaforma, ad esempio Entity Framework Core e ASP.NET Core MVC. Inizialmente, l'oggetto IServiceCollection fornito a ConfigureServices ha servizi definiti dal framework a seconda della modalità di configurazione dell'host. Per le app basate sui modelli ASP.NET Core, il framework registra più di 250 servizi.

La tabella seguente elenca un piccolo esempio di questi servizi registrati dal framework:

Tipo di servizio Durata
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Temporaneo
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Temporaneo
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Temporaneo
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Temporaneo
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Risorse aggiuntive