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 9 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 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 9 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.
Per Blazor indicazioni sull'inserimento delle dipendenze, che aggiunge o sostituisce le indicazioni contenute in questo articolo, vedere ASP.NET Blazor core dependency injection.
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 classeIndexModel
deve essere modificata. - Se
MyDependency
presenta delle dipendenze, devono essere configurate anche dalla classeIndexModel
. In un progetto di grandi dimensioni con più classi che dipendono daMyDependency
, 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 IMyDependency
definisce 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 MyDependency
concreto . 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
MyDependency
concreto , solo l'interfacciaIMyDependency
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
delInvoke
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'approccioInvokeAsync
. - 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
) AddKeyedTransient
per 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"));
}
}
Servizi chiave nel middleware
Il middleware supporta i servizi keyed sia nel costruttore che nel Invoke
/InvokeAsync
metodo :
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);
}
Per altre informazioni sulla creazione del middleware, vedere Scrivere middleware personalizzato ASP.NET Core
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 inIndexModel
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 Raccomandazioni nell'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:
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.
Modelli consigliati per la multi-tenancy nell'inserimento delle dipendenze
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
- Inserimento di dipendenze in viste in ASP.NET Core
- Inserimento delle dipendenze nei controller in ASP.NET Core
- Inserimento delle dipendenze nei gestori dei requisiti in ASP.NET Core
- ASP.NET core Blazor dependency injection
- Modelli di conferenza NDC per lo sviluppo di app di inserimento delle dipendenze
- Avvio dell'app in ASP.NET Core
- Attivazione middleware basata su factory in ASP.NET Core
- Informazioni di base sull'inserimento delle dipendenze in .NET
- Linee guida per l'inserimento delle dipendenze
- Esercitazione: Usare l'inserimento delle dipendenze in .NET
- Inserimento delle dipendenze .NET
- ASP.NET CORE DEPENDENCY INJECTION: CHE COS'È ISERVICECOLLECTION?
- Quattro modi per eliminare IDisposable in ASP.NET Core
- Scrittura di codice pulito in ASP.NET Core con inserimento delle dipendenze (MSDN)
- Explicit Dependencies Principle (Principio delle dipendenze esplicite)
- Inversion of Control Containers and the Dependency Injection Pattern (Martin Fowler) (Contenitori di inversione del controllo e modello di inserimento delle dipendenze)
- How to register a service with multiple interfaces in ASP.NET Core DI (Come registrare un servizio con più interfacce in ASP.NET Core DI)
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 classeIndexModel
deve essere modificata. - Se
MyDependency
presenta delle dipendenze, devono essere configurate anche dalla classeIndexModel
. In un progetto di grandi dimensioni con più classi che dipendono daMyDependency
, 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 IMyDependency
definisce 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 MyDependency
concreto . 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
MyDependency
concreto , solo l'interfacciaIMyDependency
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
delInvoke
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'approccioInvokeAsync
. - 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
) AddKeyedTransient
per 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 inIndexModel
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 Raccomandazioni nell'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:
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.
Modelli consigliati per la multi-tenancy nell'inserimento delle dipendenze
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
- Inserimento di dipendenze in viste in ASP.NET Core
- Inserimento delle dipendenze nei controller in ASP.NET Core
- Inserimento delle dipendenze nei gestori dei requisiti in ASP.NET Core
- ASP.NET core Blazor dependency injection
- Modelli di conferenza NDC per lo sviluppo di app di inserimento delle dipendenze
- Avvio dell'app in ASP.NET Core
- Attivazione middleware basata su factory in ASP.NET Core
- Informazioni di base sull'inserimento delle dipendenze in .NET
- Linee guida per l'inserimento delle dipendenze
- Esercitazione: Usare l'inserimento delle dipendenze in .NET
- Inserimento delle dipendenze .NET
- ASP.NET CORE DEPENDENCY INJECTION: CHE COS'È ISERVICECOLLECTION?
- Quattro modi per eliminare IDisposable in ASP.NET Core
- Scrittura di codice pulito in ASP.NET Core con inserimento delle dipendenze (MSDN)
- Explicit Dependencies Principle (Principio delle dipendenze esplicite)
- Inversion of Control Containers and the Dependency Injection Pattern (Martin Fowler) (Contenitori di inversione del controllo e modello di inserimento delle dipendenze)
- How to register a service with multiple interfaces in ASP.NET Core DI (Come registrare un servizio con più interfacce in ASP.NET Core DI)
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 classeIndexModel
deve essere modificata. - Se
MyDependency
presenta delle dipendenze, devono essere configurate anche dalla classeIndexModel
. In un progetto di grandi dimensioni con più classi che dipendono daMyDependency
, 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 IMyDependency
definisce 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 MyDependency
concreto . 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
MyDependency
concreto , solo l'interfacciaIMyDependency
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
delInvoke
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'approccioInvokeAsync
. - 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 inIndexModel
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 Raccomandazioni nell'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:
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.
Modelli consigliati per la multi-tenancy nell'inserimento delle dipendenze
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
- Inserimento di dipendenze in viste in ASP.NET Core
- Inserimento delle dipendenze nei controller in ASP.NET Core
- Inserimento delle dipendenze nei gestori dei requisiti in ASP.NET Core
- ASP.NET core Blazor dependency injection
- Modelli di conferenza NDC per lo sviluppo di app di inserimento delle dipendenze
- Avvio dell'app in ASP.NET Core
- Attivazione middleware basata su factory in ASP.NET Core
- Quattro modi per eliminare IDisposable in ASP.NET Core
- Scrittura di codice pulito in ASP.NET Core con inserimento delle dipendenze (MSDN)
- Explicit Dependencies Principle (Principio delle dipendenze esplicite)
- Inversion of Control Containers and the Dependency Injection Pattern (Martin Fowler) (Contenitori di inversione del controllo e modello di inserimento delle dipendenze)
- How to register a service with multiple interfaces in ASP.NET Core DI (Come registrare un servizio con più interfacce in ASP.NET Core DI)
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 classeIndexModel
deve essere modificata. - Se
MyDependency
presenta delle dipendenze, devono essere configurate anche dalla classeIndexModel
. In un progetto di grandi dimensioni con più classi che dipendono daMyDependency
, 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 IMyDependency
definisce 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 MyDependency
concreto . 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
MyDependency
concreto , solo l'interfacciaIMyDependency
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
delInvoke
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'approccioInvokeAsync
. - 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 inIndexModel
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 Raccomandazioni nell'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:
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 chiamataBuildServiceProvider
in genere avviene quando lo sviluppatore vuole risolvere un servizio inConfigureServices
. Si consideri ad esempio il caso in cui l'oggettoLoginPath
viene caricato dalla configurazione. Evitare l'approccio seguente: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.
Modelli consigliati per la multi-tenancy nell'inserimento delle dipendenze
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
- Inserimento di dipendenze in viste in ASP.NET Core
- Inserimento delle dipendenze nei controller in ASP.NET Core
- Inserimento delle dipendenze nei gestori dei requisiti in ASP.NET Core
- ASP.NET core Blazor dependency injection
- Modelli di conferenza NDC per lo sviluppo di app di inserimento delle dipendenze
- Avvio dell'app in ASP.NET Core
- Attivazione middleware basata su factory in ASP.NET Core
- Quattro modi per eliminare IDisposable in ASP.NET Core
- Scrittura di codice pulito in ASP.NET Core con inserimento delle dipendenze (MSDN)
- Explicit Dependencies Principle (Principio delle dipendenze esplicite)
- Inversion of Control Containers and the Dependency Injection Pattern (Martin Fowler) (Contenitori di inversione del controllo e modello di inserimento delle dipendenze)
- How to register a service with multiple interfaces in ASP.NET Core DI (Come registrare un servizio con più interfacce in ASP.NET Core DI)