Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
In questo articolo, crei un'app console .NET che crea manualmente un ServiceCollection e il corrispondente ServiceProvider. Imparerai come registrare i servizi e risolverli usando l'iniezione delle dipendenze. Questo articolo utilizza il pacchetto NuGet Microsoft.Extensions.DependencyInjection per illustrare le nozioni di base della Dependency Injection (DI) in .NET.
Nota
Questo articolo non sfrutta le funzionalità dell'host generico. Per una guida più completa, vedere Usare l'inserimento delle dipendenze in .NET.
Inizia
Per iniziare, creare una nuova applicazione console .NET denominata DI.Basics. Nell'elenco seguente viene fatto riferimento ad alcuni degli approcci più comuni per la creazione di un progetto console:
- Visual Studio: File > Nuovo > menu Progetto.
- Visual Studio Code e l'opzione di menu dell'estensione C# Dev Kit: Esplora soluzioni.
-
Comando .NET CLI:
dotnet new console
nel terminale.
È necessario aggiungere il riferimento al pacchetto a Microsoft.Extensions.DependencyInjection nel file di progetto. Indipendentemente dall'approccio, assicurarsi che il progetto sia simile al seguente codice XML del file DI.Basics.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
</ItemGroup>
</Project>
Nozioni di base sull'iniezione delle dipendenze
L'iniezione delle dipendenze è un pattern di progettazione che consente di rimuovere le dipendenze codificate staticamente e di rendere l'applicazione più manutenibile e testabile. La DI è una tecnica per ottenere l'Inversion of Control (IoC) tra classi e le loro dipendenze.
Le astrazioni per l'inserimento delle dipendenze in .NET sono definite nel pacchetto NuGet Microsoft.Extensions.DependencyInjection.Abstractions:
- IServiceCollection: definisce un contratto per una raccolta di descrittori di servizio.
- IServiceProvider: definisce un meccanismo per il recupero di un oggetto servizio.
- ServiceDescriptor: descrive un servizio indicandone il tipo, l'implementazione e la durata.
All'interno di .NET, l'inserimento delle dipendenze (DI) viene gestito aggiungendo i servizi e configurandoli in un IServiceCollection
. Dopo la registrazione dei servizi, un’istanza IServiceProvider
viene compilata chiamando il metodo BuildServiceProvider.
IServiceProvider
funge da contenitore di tutti i servizi registrati e viene usato per risolverli.
Creare servizi di esempio
Non tutti i servizi vengono creati equamente. Alcuni servizi richiedono una nuova istanza ogni volta che il contenitore di servizi li richiede (transitorio), mentre altri devono essere condivisi a livello di ambito tra le richieste (con ambito) o per l'intera durata dell'app (singleton). Per altre informazioni sulla durata del servizio, vedere Durate del servizio.
Analogamente, alcuni servizi espongono solo un tipo concreto, mentre altri sono espressi come contratto tra un'interfaccia e un tipo di implementazione. È possibile creare diverse varianti di servizi per illustrare questi concetti.
Creare un nuovo file C# denominato IConsole.cs e aggiungere il codice seguente:
public interface IConsole
{
void WriteLine(string message);
}
Questo file definisce un'interfaccia IConsole
che espone un singolo metodo, WriteLine
. Creare quindi un nuovo file C# denominato DefaultConsole.cs e aggiungere il codice seguente:
internal sealed class DefaultConsole : IConsole
{
public bool IsEnabled { get; set; } = true;
void IConsole.WriteLine(string message)
{
if (IsEnabled is false)
{
return;
}
Console.WriteLine(message);
}
}
Il codice precedente rappresenta l'implementazione predefinita dell'interfaccia IConsole
. Il metodo WriteLine
scrive in modo condizionale nella console in base alla proprietà IsEnabled
.
Suggerimento
La denominazione di un'implementazione è una scelta che il team di sviluppo deve concordare. Il prefisso Default
è una convenzione comune per indicare un'implementazione predefinita di un'interfaccia, ma non è obbligatoria.
Creare quindi un file IGreetingService.cs e aggiungere il codice C# seguente:
public interface IGreetingService
{
string Greet(string name);
}
Aggiungere quindi un nuovo file C# denominato DefaultGreetingService.cs e aggiungere il codice seguente:
internal sealed class DefaultGreetingService(
IConsole console) : IGreetingService
{
public string Greet(string name)
{
var greeting = $"Hello, {name}!";
console.WriteLine(greeting);
return greeting;
}
}
Il codice precedente rappresenta l'implementazione predefinita dell'interfaccia IGreetingService
. L'implementazione del servizio richiede IConsole
come parametro del costruttore primario. Il metodo Greet
:
- Crea un
greeting
partendo dalname
. - Chiama il metodo
WriteLine
nell'istanzaIConsole
. - Restituisce
greeting
al chiamante.
L'ultimo servizio da creare è il file FarewellService.cs, aggiungere il codice C# seguente prima di continuare:
public class FarewellService(IConsole console)
{
public string SayGoodbye(string name)
{
var farewell = $"Goodbye, {name}!";
console.WriteLine(farewell);
return farewell;
}
}
FarewellService
rappresenta un tipo concreto, non un'interfaccia. Deve essere dichiarato come public
per renderlo accessibile agli utenti. A differenza di altri tipi di implementazione del servizio dichiarati come internal
e sealed
, questo codice dimostra che non tutti i servizi devono essere interfacce. Mostra anche che le implementazioni del servizio possono essere sealed
per impedire l'ereditarietà e internal
per limitare l'accesso al modulo.
Aggiornare la classe Program
Aprire il file Program.cs e sostituire il codice esistente con il codice C# riportato di seguito:
using Microsoft.Extensions.DependencyInjection;
// 1. Create the service collection.
var services = new ServiceCollection();
// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
});
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();
// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();
// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();
// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");
Il codice aggiornato precedente illustra la procedura:
- Creare una nuova istanza
ServiceCollection
. - Registrare e configurare i servizi in
ServiceCollection
:- Il
IConsole
, utilizzando l'implementazione di overload di factory, restituisce un tipoDefaultConsole
con ilIsEnabled
impostato sutrue
. -
IGreetingService
viene aggiunto con un tipo di implementazione corrispondente del tipoDefaultGreetingService
. -
FarewellService
viene aggiunto come tipo concreto.
- Il
- Costruire
ServiceProvider
dalServiceCollection
. - Risolvi i servizi
IGreetingService
eFarewellService
. - Utilizzare i servizi risolti per salutare e dire addio a una persona denominata
David
.
Se si aggiorna la proprietà IsEnabled
di DefaultConsole
in false
, i metodi Greet
e SayGoodbye
omettono la scrittura nei messaggi risultanti nella console. Una modifica di questo tipo consente di dimostrare che il servizio IConsole
viene inserito nei servizi IGreetingService
e FarewellService
come dipendenza che influenza il comportamento delle app.
Tutti questi servizi vengono registrati come singleton, anche se in questo esempio funzionerebbero allo stesso modo se fossero registrati come servizi temporanei o con ambito.
Importante
In questo esempio artificioso, la durata del servizio non è importante, ma in un contesto reale, è consigliabile considerare attentamente la durata di ogni servizio.
Eseguire l'app di esempio
Per eseguire l'app di esempio, premere F5 in Visual Studio, Visual Studio Code o eseguire il comando dotnet run
nel terminale. Al completamento dell'app, verrà visualizzato l'output seguente:
Hello, David!
Goodbye, David!
Descrittori del servizio
Le API più comunemente usate per aggiungere servizi al ServiceCollection
sono metodi di estensione generici con nome basato sul ciclo di vita, come:
AddSingleton<TService>
AddTransient<TService>
AddScoped<TService>
Questi metodi sono metodi pratici che creano un'istanza ServiceDescriptor e la aggiungono a ServiceCollection
.
ServiceDescriptor
è una classe semplice che descrive un servizio indicandone il tipo, il tipo di implementazione e la durata. Può anche descrivere le fabbriche di implementazione e le istanze.
Per ognuno dei servizi registrati in ServiceCollection
, è invece possibile chiamare direttamente il metodo Add
con un'istanza ServiceDescriptor
. Vedi gli esempi seguenti:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IConsole),
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
},
lifetime: ServiceLifetime.Singleton));
Il codice precedente equivale alla modalità di registrazione del servizio IConsole
in ServiceCollection
. Il metodo Add
viene utilizzato per aggiungere un'istanza ServiceDescriptor
che descrive il servizio IConsole
. Il metodo statico ServiceDescriptor.Describe
delega a vari costruttori ServiceDescriptor
. Si consideri il codice equivalente per il servizio IGreetingService
:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IGreetingService),
implementationType: typeof(DefaultGreetingService),
lifetime: ServiceLifetime.Singleton));
Il codice precedente descrive il servizio IGreetingService
indicandone il tipo, il tipo di implementazione e la durata. Infine, prendere in considerazione il codice equivalente per il servizio FarewellService
:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(FarewellService),
implementationType: typeof(FarewellService),
lifetime: ServiceLifetime.Singleton));
Il codice precedente descrive il tipo concreto FarewellService
, sia come tipo di servizio che di implementazione. Il servizio viene registrato come servizio singleton.