Esercitazione: Usare l'iniezione delle dipendenze in .NET
Questa esercitazione illustra come usare inserimento delle dipendenze in .NET. Con Microsoft Extensions, la Dependency Injection (inserimento delle dipendenze) viene gestita aggiungendo e configurando i servizi in un IServiceCollection. L'interfaccia IHost espone l'istanza di IServiceProvider, che funge da contenitore di tutti i servizi registrati.
In questa esercitazione si apprenderà come:
Prerequisiti
- .NET Core 3.1 SDK o versione successiva.
- Familiarità con la creazione di nuove applicazioni .NET e l'installazione di pacchetti NuGet.
Creare una nuova applicazione console
Usando il comando dotnet new o una procedura guidata di creazione di un nuovo progetto in IDE, creare una nuova applicazione console .NET denominata ConsoleDI.Example. Aggiungere il pacchetto NuGet Microsoft.Extensions.Hosting al progetto.
Il nuovo file di progetto dell'app console dovrebbe essere simile al seguente:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<RootNamespace>ConsoleDI.Example</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
</ItemGroup>
</Project>
Importante
In questo esempio è necessario il pacchetto NuGet Microsoft.Extensions.Hosting per compilare ed eseguire l'app. Alcuni metapacchetto potrebbero contenere il pacchetto Microsoft.Extensions.Hosting
, nel qual caso non è necessario un riferimento esplicito al pacchetto.
Aggiungere interfacce
In questa app di esempio si apprenderà come l'inserimento delle dipendenze gestisce la durata del servizio. Si creeranno diverse interfacce che rappresentano diverse durate del servizio. Aggiungere le interfacce seguenti alla directory radice del progetto:
IReportServiceLifetime.cs
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IReportServiceLifetime
{
Guid Id { get; }
ServiceLifetime Lifetime { get; }
}
L'interfaccia IReportServiceLifetime
definisce:
- Proprietà
Guid Id
che rappresenta l'identificatore univoco del servizio. - Proprietà ServiceLifetime che rappresenta la durata del servizio.
IExampleTransientService.cs
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IExampleTransientService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}
IExampleScopedService.cs
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IExampleScopedService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}
IExampleSingletonService.cs
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IExampleSingletonService : IReportServiceLifetime
{
ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}
Tutte le sottointerfacce di IReportServiceLifetime
implementano in modo esplicito il IReportServiceLifetime.Lifetime
con un valore predefinito. Ad esempio, IExampleTransientService
implementa in modo esplicito IReportServiceLifetime.Lifetime
con il valore di ServiceLifetime.Transient
.
Aggiungere implementazioni predefinite
Nell'esempio tutte le implementazioni inizializzano la proprietà Id
con il risultato di Guid.NewGuid(). Aggiungere le classi di implementazione predefinite seguenti per i vari servizi alla directory radice del progetto:
ExampleTransientService.cs
namespace ConsoleDI.Example;
internal sealed class ExampleTransientService : IExampleTransientService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
ExampleScopedService.cs
namespace ConsoleDI.Example;
internal sealed class ExampleScopedService : IExampleScopedService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
ExampleSingletonService.cs
namespace ConsoleDI.Example;
internal sealed class ExampleSingletonService : IExampleSingletonService
{
Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}
Ogni implementazione viene definita come internal sealed
e implementa l'interfaccia corrispondente. Non è necessario che siano internal
o sealed
, ma è comune trattare le implementazioni come internal
per evitare l'esposizione di tipi di implementazione ai consumatori esterni. Inoltre, poiché ogni tipo non verrà esteso, viene contrassegnato come sealed
. Ad esempio, ExampleSingletonService
implementa IExampleSingletonService
.
Aggiungi un servizio che richiede l'inserimento delle dipendenze (DI)
Aggiungere la seguente classe di reporter del ciclo di vita del servizio, che funge da servizio all'applicazione console.
ServiceLifetimeReporter.cs
namespace ConsoleDI.Example;
internal sealed class ServiceLifetimeReporter(
IExampleTransientService transientService,
IExampleScopedService scopedService,
IExampleSingletonService singletonService)
{
public void ReportServiceLifetimeDetails(string lifetimeDetails)
{
Console.WriteLine(lifetimeDetails);
LogService(transientService, "Always different");
LogService(scopedService, "Changes only with lifetime");
LogService(singletonService, "Always the same");
}
private static void LogService<T>(T service, string message)
where T : IReportServiceLifetime =>
Console.WriteLine(
$" {typeof(T).Name}: {service.Id} ({message})");
}
Il ServiceLifetimeReporter
definisce un costruttore che richiede ognuna delle interfacce di servizio indicate in precedenza, ovvero IExampleTransientService
, IExampleScopedService
e IExampleSingletonService
. L'oggetto espone un unico metodo che consente all'utilizzatore di redigere un rapporto sul servizio con un determinato parametro lifetimeDetails
. Quando viene richiamato, il metodo ReportServiceLifetimeDetails
registra l'identificatore univoco di ogni servizio con il messaggio di durata del servizio. I messaggi di log consentono di visualizzare la durata del servizio.
Registrare i servizi per DI
Aggiornare Program.cs con il codice seguente:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ConsoleDI.Example;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();
builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();
builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
builder.Services.AddTransient<ServiceLifetimeReporter>();
using IHost host = builder.Build();
ExemplifyServiceLifetime(host.Services, "Lifetime 1");
ExemplifyServiceLifetime(host.Services, "Lifetime 2");
await host.RunAsync();
static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
using IServiceScope serviceScope = hostProvider.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine("...");
logger = provider.GetRequiredService<ServiceLifetimeReporter>();
logger.ReportServiceLifetimeDetails(
$"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");
Console.WriteLine();
}
Ogni services.Add{LIFETIME}<{SERVICE}>
metodo di estensione aggiunge (e potenzialmente configura) servizi. È consigliabile che le app seguano questa convenzione. Non inserire i metodi di estensione nello spazio dei nomi Microsoft.Extensions.DependencyInjection a meno che non si stia creando un pacchetto Microsoft ufficiale. Metodi di estensione definiti all'interno dello spazio dei nomi Microsoft.Extensions.DependencyInjection
:
- Vengono visualizzati in IntelliSense senza richiedere ulteriori direttive di
using
. - Ridurre il numero di direttive di
using
necessarie nelle classiProgram
oStartup
in cui questi metodi di estensione vengono in genere chiamati.
L'app:
- Crea un'istanza di IHostBuilder con le impostazioni del generatore host .
- Configura i servizi e li aggiunge con la durata del servizio corrispondente.
- Chiama Build() e assegna un'istanza di IHost.
- Chiama
ExemplifyScoping
passando il IHost.Services.
Conclusione
In questa app di esempio sono state create diverse interfacce e implementazioni corrispondenti. Ognuno di questi servizi viene identificato in modo univoco e associato a un ServiceLifetime. L'app di esempio illustra la registrazione delle implementazioni del servizio in un'interfaccia e come registrare classi pure senza eseguire il backup delle interfacce. L'app di esempio illustra quindi come vengono risolte le dipendenze definite come parametri del costruttore in fase di esecuzione.
Quando si esegue l'app, viene visualizzato un output simile al seguente:
// Sample output:
// Lifetime 1: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: d08a27fa-87d2-4a06-98d7-2773af886125 (Always different)
// IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 1: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: b43d68fb-2c7b-4a9b-8f02-fc507c164326 (Always different)
// IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
//
// Lifetime 2: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: f3856b59-ab3f-4bbd-876f-7bab0013d392 (Always different)
// IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 2: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
// IExampleTransientService: a8015c6a-08cd-4799-9ec3-2f2af9cbbfd2 (Always different)
// IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
// IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
Dall'output dell'app è possibile osservare che:
- Transient servizi sono sempre diversi, viene creata una nuova istanza ogni volta che si recupera il servizio.
- Scoped i servizi cambiano solo con un nuovo ambito, ma sono la stessa istanza all'interno di un ambito.
- Singleton i servizi sono sempre uguali, viene creata una nuova istanza una sola volta.