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.
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 Blazor indicazioni sull'inserimento delle dipendenze, che aggiunge o sostituisce le indicazioni contenute in questo articolo, vedere ASP.NET Blazor core dependency injection.
Questo articolo 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.
Una dipendenza è un oggetto da cui dipende un altro oggetto. Esaminare la classe MyDependency seguente con un metodo WriteMessage da cui dipendono altre classi:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Una classe può creare un'istanza della classe MyDependency per usare il relativo metodo WriteMessage. Nell'esempio seguente la classe MyDependency è una dipendenza della classe IndexModel:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
La classe crea e dipende direttamente dalla classe MyDependency. Le dipendenze del codice, ad esempio nell'esempio precedente, sono problematiche e devono essere evitate per i motivi seguenti:
Per sostituire MyDependency con un'implementazione diversa, la classe IndexModel deve essere modificata.
Se MyDependency presenta delle dipendenze, devono essere configurate anche dalla classe IndexModel. In un progetto di grandi dimensioni con più classi che dipendono da MyDependency, il codice di configurazione diventa sparso in tutta l'app.
È difficile eseguire unit test di questa implementazione.
L'inserimento delle dipendenze consente di risolvere questi problemi tramite:
L'uso di un'interfaccia o di una classe di base per astrarre l'implementazione delle dipendenze.
La registrazione della dipendenza in un contenitore di servizi. ASP.NET Core offre il contenitore di servizi predefinito IServiceProvider. I servizi vengono in genere registrati nel file dell'app Program.cs .
L'inserimento del servizio nel costruttore della classe in cui viene usato. Il framework si assume la responsabilità della creazione di un'istanza della dipendenza e della sua eliminazione quando non è più necessaria.
Nell'app di esempio l'interfaccia definisce il IMyDependency metodo :
public interface IMyDependency
{
void WriteMessage(string message);
}
Questa interfaccia viene implementata da un tipo concreto, MyDependency:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
L'app di esempio registra il IMyDependency servizio con il tipo MyDependencyconcreto . Il AddScoped metodo registra il servizio con una durata con ambito, ovvero la durata di una singola richiesta.
Le durate dei servizi sono descritte più avanti in questo articolo.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
Nell'app di esempio, il IMyDependency servizio viene richiesto e usato per chiamare il WriteMessage metodo :
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Usando il modello di inserimento delle dipendenze, il controller o Razor la pagina:
Non usa il tipo MyDependencyconcreto , solo l'interfaccia IMyDependency implementata. In questo modo è facile modificare l'implementazione senza modificare il controller o Razor la pagina.
Non crea un'istanza di MyDependency, viene creata dal contenitore di inserimento delle dipendenze.
L'implementazione dell'interfaccia IMyDependency può essere migliorata usando l'API di registrazione predefinita:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
L'aggiornamento Program.cs registra la nuova IMyDependency implementazione:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
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.
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 potrebbe 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);
}
}
Il codice precedente funziona correttamente senza modificare alcun elemento in Program.cs, perché di registrazione viene fornito 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.
Per usare i servizi con ambito nel middleware, usare uno degli approcci seguenti:
Inserire il servizio nel metodo o Invoke del InvokeAsync middleware. L'uso dell'inserimento del costruttore genera un'eccezione di runtime perché forza il comportamento del servizio con ambito come un singleton. L'esempio nella sezione Opzioni di durata e registrazione illustra l'approccio InvokeAsync .
Usare il middleware basato su factory. Il middleware registrato con questo approccio viene attivato per ogni richiesta client (connessione), che consente l'inserimento di servizi con ambito nel costruttore del middleware.
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 di un servizio, ma hanno tutti lo stesso tipo di implementazione .
Uno di questi metodi di registrazione del servizio 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
Il termine servizi con chiave si riferisce a un meccanismo per la registrazione e il recupero dei servizi di Dependency Injection (DI) utilizzando chiavi. Un servizio è associato a una chiave chiamando AddKeyedSingleton (o AddKeyedScoped ) AddKeyedTransientper registrarlo. Accedere a un servizio registrato specificando la chiave con l'attributo [FromKeyedServices] . Il codice seguente illustra come usare i servizi con chiave:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
smallCache.Get("date"));
app.MapControllers();
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
{
return cache.Get("data-mvc");
}
}
public class MyHub : Hub
{
public void Method([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}
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 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:
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;
}
}
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();
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.
L'inserimento di dipendenze è un'alternativa ai modelli di accesso agli oggetti statici/globali. Potrebbe non essere possibile realizzare i vantaggi dell'inserimento delle dipendenze se si combina con l'accesso a oggetti statici.
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.
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:
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.
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.
Una dipendenza è un oggetto da cui dipende un altro oggetto. Esaminare la classe MyDependency seguente con un metodo WriteMessage da cui dipendono altre classi:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Una classe può creare un'istanza della classe MyDependency per usare il relativo metodo WriteMessage. Nell'esempio seguente la classe MyDependency è una dipendenza della classe IndexModel:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
La classe crea e dipende direttamente dalla classe MyDependency. Le dipendenze del codice, ad esempio nell'esempio precedente, sono problematiche e devono essere evitate per i motivi seguenti:
Per sostituire MyDependency con un'implementazione diversa, la classe IndexModel deve essere modificata.
Se MyDependency presenta delle dipendenze, devono essere configurate anche dalla classe IndexModel. In un progetto di grandi dimensioni con più classi che dipendono da MyDependency, il codice di configurazione diventa sparso in tutta l'app.
È difficile eseguire unit test di questa implementazione.
L'inserimento delle dipendenze consente di risolvere questi problemi tramite:
L'uso di un'interfaccia o di una classe di base per astrarre l'implementazione delle dipendenze.
La registrazione della dipendenza in un contenitore di servizi. ASP.NET Core offre il contenitore di servizi predefinito IServiceProvider. I servizi vengono in genere registrati nel file dell'app Program.cs .
L'inserimento del servizio nel costruttore della classe in cui viene usato. Il framework si assume la responsabilità della creazione di un'istanza della dipendenza e della sua eliminazione quando non è più necessaria.
Nell'app di esempio l'interfaccia definisce il IMyDependency metodo :
public interface IMyDependency
{
void WriteMessage(string message);
}
Questa interfaccia viene implementata da un tipo concreto, MyDependency:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
L'app di esempio registra il IMyDependency servizio con il tipo MyDependencyconcreto . Il AddScoped metodo registra il servizio con una durata con ambito, ovvero la durata di una singola richiesta. Le durate dei servizi sono descritte più avanti in questo argomento.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
Nell'app di esempio, il IMyDependency servizio viene richiesto e usato per chiamare il WriteMessage metodo :
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Usando il modello di inserimento delle dipendenze, il controller o Razor la pagina:
Non usa il tipo MyDependencyconcreto , solo l'interfaccia IMyDependency implementata. In questo modo è facile modificare l'implementazione senza modificare il controller o Razor la pagina.
Non crea un'istanza di MyDependency, viene creata dal contenitore di inserimento delle dipendenze.
L'implementazione dell'interfaccia IMyDependency può essere migliorata usando l'API di registrazione predefinita:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
L'aggiornamento Program.cs registra la nuova IMyDependency implementazione:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
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.
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.
Per usare i servizi con ambito nel middleware, usare uno degli approcci seguenti:
Inserire il servizio nel metodo o Invoke del InvokeAsync middleware. L'uso dell'inserimento del costruttore genera un'eccezione di runtime perché forza il comportamento del servizio con ambito come un singleton. L'esempio nella sezione Opzioni di durata e registrazione illustra l'approccio InvokeAsync .
Usare il middleware basato su factory. Il middleware registrato con questo approccio viene attivato per ogni richiesta client (connessione), che consente l'inserimento di servizi con ambito nel costruttore del middleware.
La registrazione di un servizio con solo un tipo di implementazione equivale alla registrazione del servizio con lo stesso tipo di implementazione e di servizio. Ecco perché non è possibile registrare più implementazioni di un servizio usando i metodi che non accettano un tipo di servizio esplicito. Questi metodi possono registrare più istanze di un servizio, ma avranno tutti lo stesso tipo di implementazione.
Uno dei metodi di registrazione del servizio precedenti può essere usato per registrare più istanze del servizio dello stesso tipo di servizio. Nell'esempio seguente AddSingleton viene chiamato due volte con IMyDependency come tipo di servizio. La seconda chiamata a AddSingleton esegue l'override di quella precedente quando viene risolta come IMyDependency e si aggiunge a quella precedente quando più servizi vengono risolti tramite IEnumerable<IMyDependency>. I servizi vengono visualizzati nell'ordine in cui sono stati registrati quando sono stati risolti tramite IEnumerable<{SERVICE}>.
services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();
public class MyService
{
public MyService(IMyDependency myDependency,
IEnumerable<IMyDependency> myDependencies)
{
Trace.Assert(myDependency is DifferentDependency);
var dependencyArray = myDependencies.ToArray();
Trace.Assert(dependencyArray[0] is MyDependency);
Trace.Assert(dependencyArray[1] is DifferentDependency);
}
}
Servizi con chiave
I servizi con chiave fanno riferimento a un meccanismo per la registrazione e il recupero di servizi di inserimento delle dipendenze tramite chiavi. Un servizio è associato a una chiave chiamando AddKeyedSingleton (o AddKeyedScoped ) AddKeyedTransientper registrarlo. Accedere a un servizio registrato specificando la chiave con l'attributo [FromKeyedServices] . Il codice seguente illustra come usare i servizi con chiave:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();
var app = builder.Build();
app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
smallCache.Get("date"));
app.MapControllers();
app.Run();
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
[HttpGet("big-cache")]
public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
{
return cache.Get("data-mvc");
}
}
public class MyHub : Hub
{
public void Method([FromKeyedServices("small")] ICache cache)
{
Console.WriteLine(cache.Get("signalr"));
}
}
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:
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();
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.
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.
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:
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.
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.
Una dipendenza è un oggetto da cui dipende un altro oggetto. Esaminare la classe MyDependency seguente con un metodo WriteMessage da cui dipendono altre classi:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Una classe può creare un'istanza della classe MyDependency per usare il relativo metodo WriteMessage. Nell'esempio seguente la classe MyDependency è una dipendenza della classe IndexModel:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet");
}
}
La classe crea e dipende direttamente dalla classe MyDependency. Le dipendenze del codice, ad esempio nell'esempio precedente, sono problematiche e devono essere evitate per i motivi seguenti:
Per sostituire MyDependency con un'implementazione diversa, la classe IndexModel deve essere modificata.
Se MyDependency presenta delle dipendenze, devono essere configurate anche dalla classe IndexModel. In un progetto di grandi dimensioni con più classi che dipendono da MyDependency, il codice di configurazione diventa sparso in tutta l'app.
È difficile eseguire unit test di questa implementazione.
L'inserimento delle dipendenze consente di risolvere questi problemi tramite:
L'uso di un'interfaccia o di una classe di base per astrarre l'implementazione delle dipendenze.
La registrazione della dipendenza in un contenitore di servizi. ASP.NET Core offre il contenitore di servizi predefinito IServiceProvider. I servizi vengono in genere registrati nel file dell'app Program.cs .
L'inserimento del servizio nel costruttore della classe in cui viene usato. Il framework si assume la responsabilità della creazione di un'istanza della dipendenza e della sua eliminazione quando non è più necessaria.
Nell'app di esempio l'interfaccia definisce il IMyDependency metodo :
public interface IMyDependency
{
void WriteMessage(string message);
}
Questa interfaccia viene implementata da un tipo concreto, MyDependency:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
L'app di esempio registra il IMyDependency servizio con il tipo MyDependencyconcreto . Il AddScoped metodo registra il servizio con una durata con ambito, ovvero la durata di una singola richiesta. Le durate dei servizi sono descritte più avanti in questo argomento.
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency>();
var app = builder.Build();
Nell'app di esempio, il IMyDependency servizio viene richiesto e usato per chiamare il WriteMessage metodo :
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Usando il modello di inserimento delle dipendenze, il controller o Razor la pagina:
Non usa il tipo MyDependencyconcreto , solo l'interfaccia IMyDependency implementata. In questo modo è facile modificare l'implementazione senza modificare il controller o Razor la pagina.
Non crea un'istanza di MyDependency, viene creata dal contenitore di inserimento delle dipendenze.
L'implementazione dell'interfaccia IMyDependency può essere migliorata usando l'API di registrazione predefinita:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
L'aggiornamento Program.cs registra la nuova IMyDependency implementazione:
using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<IMyDependency, MyDependency2>();
var app = builder.Build();
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.
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.
Per usare i servizi con ambito nel middleware, usare uno degli approcci seguenti:
Inserire il servizio nel metodo o Invoke del InvokeAsync middleware. L'uso dell'inserimento del costruttore genera un'eccezione di runtime perché forza il comportamento del servizio con ambito come un singleton. L'esempio nella sezione Opzioni di durata e registrazione illustra l'approccio InvokeAsync .
Usare il middleware basato su factory. Il middleware registrato con questo approccio viene attivato per ogni richiesta client (connessione), che consente l'inserimento di servizi con ambito nel costruttore del middleware.
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);
}
}
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:
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();
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.
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.
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:
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.
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.
Una dipendenza è un oggetto da cui dipende un altro oggetto. Esaminare la classe MyDependency seguente con un metodo WriteMessage da cui dipendono altre classi:
public class MyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
}
}
Una classe può creare un'istanza della classe MyDependency per usare il relativo metodo WriteMessage. Nell'esempio seguente la classe MyDependency è una dipendenza della classe IndexModel:
public class IndexModel : PageModel
{
private readonly MyDependency _dependency = new MyDependency();
public void OnGet()
{
_dependency.WriteMessage("IndexModel.OnGet created this message.");
}
}
La classe crea e dipende direttamente dalla classe MyDependency. Le dipendenze del codice, ad esempio nell'esempio precedente, sono problematiche e devono essere evitate per i motivi seguenti:
Per sostituire MyDependency con un'implementazione diversa, la classe IndexModel deve essere modificata.
Se MyDependency presenta delle dipendenze, devono essere configurate anche dalla classe IndexModel. In un progetto di grandi dimensioni con più classi che dipendono da MyDependency, il codice di configurazione diventa sparso in tutta l'app.
È difficile eseguire unit test di questa implementazione. L'app dovrebbe usare una classe MyDependency fittizia o stub, ma ciò non è possibile con questo approccio.
L'inserimento delle dipendenze consente di risolvere questi problemi tramite:
L'uso di un'interfaccia o di una classe di base per astrarre l'implementazione delle dipendenze.
La registrazione della dipendenza in un contenitore di servizi. ASP.NET Core offre il contenitore di servizi predefinito IServiceProvider. I servizi vengono in genere registrati nel metodo dell'app Startup.ConfigureServices .
L'inserimento del servizio nel costruttore della classe in cui viene usato. Il framework si assume la responsabilità della creazione di un'istanza della dipendenza e della sua eliminazione quando non è più necessaria.
Nell'app di esempio l'interfaccia definisce il IMyDependency metodo :
public interface IMyDependency
{
void WriteMessage(string message);
}
Questa interfaccia viene implementata da un tipo concreto, MyDependency:
public class MyDependency : IMyDependency
{
public void WriteMessage(string message)
{
Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
}
}
L'app di esempio registra il IMyDependency servizio con il tipo MyDependencyconcreto . Il AddScoped metodo registra il servizio con una durata con ambito, ovvero la durata di una singola richiesta. Le durate dei servizi sono descritte più avanti in questo argomento.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
services.AddRazorPages();
}
Nell'app di esempio, il IMyDependency servizio viene richiesto e usato per chiamare il WriteMessage metodo :
public class Index2Model : PageModel
{
private readonly IMyDependency _myDependency;
public Index2Model(IMyDependency myDependency)
{
_myDependency = myDependency;
}
public void OnGet()
{
_myDependency.WriteMessage("Index2Model.OnGet");
}
}
Usando il modello di inserimento delle dipendenze, il controller:
Non usa il tipo MyDependencyconcreto , solo l'interfaccia IMyDependency implementata. In questo modo è facile modificare l'implementazione usata dal controller senza modificare il controller.
Non crea un'istanza di MyDependency, viene creata dal contenitore di inserimento delle dipendenze.
L'implementazione dell'interfaccia IMyDependency può essere migliorata usando l'API di registrazione predefinita:
public class MyDependency2 : IMyDependency
{
private readonly ILogger<MyDependency2> _logger;
public MyDependency2(ILogger<MyDependency2> logger)
{
_logger = logger;
}
public void WriteMessage(string message)
{
_logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
}
}
Il metodo ConfigureServices aggiornato registra la nuova implementazione IMyDependency:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency2>();
services.AddRazorPages();
}
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.
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):
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:
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:
Per usare i servizi con ambito nel middleware, usare uno degli approcci seguenti:
Inserire il servizio nel metodo o Invoke del InvokeAsync middleware. L'uso dell'inserimento del costruttore genera un'eccezione di runtime perché forza il comportamento del servizio con ambito come un singleton. L'esempio nella sezione Opzioni di durata e registrazione illustra l'approccio InvokeAsync .
Usare il middleware basato su factory. Il middleware registrato con questo approccio viene attivato per ogni richiesta client (connessione), che consente l'inserimento di servizi con ambito nel metodo del InvokeAsync middleware.
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);
}
}
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:
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:
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>();
});
}
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();
}
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 chiamate a BuildServiceProvider in ConfigureServices. La chiamata BuildServiceProvider in genere avviene quando lo sviluppatore vuole risolvere un servizio in ConfigureServices. Si consideri ad esempio il caso in cui l'oggetto LoginPath viene caricato dalla configurazione. Evitare l'approccio seguente:
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:
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.
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:
L'origine di questo contenuto è disponibile in GitHub, in cui è anche possibile creare ed esaminare i problemi e le richieste pull. Per ulteriori informazioni, vedere la guida per i collaboratori.
Feedback su ASP.NET Core
ASP.NET Core è un progetto di open source. Selezionare un collegamento per fornire feedback:
Comprendere e implementare l'inserimento delle dipendenze in un'app ASP.NET Core. Usare il contenitore di servizi predefinito ASP.NET Core per gestire le dipendenze. Registrare i servizi con il contenitore del servizio.