Tutorial: Uso de la inserción de dependencias en .NET
En este tutorial se muestra cómo usar la inserción de dependencias (DI) en .NET. Con las Extensiones de Microsoft, la inserción de dependencias se administra mediante la adición de servicios y su configuración en IServiceCollection. La interfaz IHost expone la instancia de IServiceProvider, que actúa como contenedor de todos los servicios registrados.
En este tutorial, aprenderá a:
Prerrequisitos
- SDK de .NET Core 3.1 o versiones posteriores.
- Familiaridad con la creación de nuevas aplicaciones .NET e instalación de paquetes NuGet.
Creación de una nueva aplicación de consola
Con el comando dotnet new o un asistente para proyectos nuevos del IDE, cree una nueva aplicación de consola de .NET denominada ConsoleDI.Example. Agregue el paquete NuGet microsoft.Extensions.Hosting al proyecto.
El nuevo archivo de proyecto de aplicación de consola debe ser similar al siguiente:
<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
En este ejemplo, el paquete NuGet Microsoft.Extensions.Hosting es necesario para compilar y ejecutar la aplicación. Algunos metapaquetes pueden contener el paquete Microsoft.Extensions.Hosting
, en cuyo caso no se requiere una referencia de paquete explícita.
Adición de interfaces
En esta aplicación de ejemplo, aprenderá cómo la inserción de dependencias controla la duración del servicio. Creará varias interfaces que representan diferentes duraciones de servicio. Agregue las siguientes interfaces al directorio raíz del proyecto:
IReportServiceLifetime.cs
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleDI.Example;
public interface IReportServiceLifetime
{
Guid Id { get; }
ServiceLifetime Lifetime { get; }
}
La interfaz IReportServiceLifetime
define:
- Propiedad
Guid Id
que representa el identificador único del servicio. - Una propiedad ServiceLifetime que representa la duración del servicio.
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;
}
Todas las subinterfaciones de IReportServiceLifetime
implementan explícitamente el IReportServiceLifetime.Lifetime
con un valor predeterminado. Por ejemplo, IExampleTransientService
implementa explícitamente IReportServiceLifetime.Lifetime
con el valor ServiceLifetime.Transient
.
Adición de implementaciones predeterminadas
Todas las implementaciones de ejemplo inicializan su propiedad Id
con el resultado de Guid.NewGuid(). Agregue las siguientes clases de implementación predeterminadas para los distintos servicios al directorio raíz del proyecto:
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();
}
Cada implementación se define como internal sealed
e implementa su interfaz correspondiente. No es obligatorio que sean internal
o sealed
; sin embargo, es común tratar las implementaciones como internal
para evitar la exposición de tipos de implementación a usuarios externos. Además, dado que cada tipo no se extenderá, se marca como sealed
. Por ejemplo, ExampleSingletonService
implementa IExampleSingletonService
.
Adición de un servicio que requiera DI
Agregue la siguiente clase de informador de duración de servicio, que actúa como un servicio para la aplicación de consola:
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})");
}
El ServiceLifetimeReporter
define un constructor que requiere cada una de las interfaces de servicio mencionadas anteriormente, es decir, IExampleTransientService
, IExampleScopedService
y IExampleSingletonService
. El objeto expone un único método que permite al consumidor informar sobre el servicio con un parámetro de lifetimeDetails
determinado. Cuando se invoca, el método ReportServiceLifetimeDetails
registra el identificador único de cada servicio con el mensaje de duración del servicio. Los mensajes de registro ayudan a visualizar la duración del servicio.
Registro de servicios para DI
Actualice Program.cs con el código siguiente:
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();
}
Cada services.Add{LIFETIME}<{SERVICE}>
método de extensión agrega (y potencialmente configura) servicios. Se recomienda que las aplicaciones sigan esta convención. No coloque métodos de extensión en el espacio de nombres Microsoft.Extensions.DependencyInjection a menos que cree un paquete oficial de Microsoft. Métodos de extensión definidos en el espacio de nombres Microsoft.Extensions.DependencyInjection
:
- Se muestran en IntelliSense sin necesidad de directivas
using
adicionales. - Reduzca el número de directivas
using
necesarias en las clasesProgram
oStartup
, donde típicamente se llaman estos métodos de extensión.
La aplicación:
- Crea una instancia de IHostBuilder con la configuración del generador de hosts.
- Configura los servicios y los agrega con su duración de servicio correspondiente.
- Llama a Build() y asigna una instancia de IHost.
- Llama a
ExemplifyScoping
, pasando el IHost.Services.
Conclusión
En esta aplicación de ejemplo, ha creado varias interfaces y implementaciones correspondientes. Cada uno de estos servicios se identifica de forma única y se empareja con un ServiceLifetime. La aplicación de ejemplo muestra el registro de implementaciones de servicio en una interfaz y cómo registrar clases puras sin interfaces de respaldo. A continuación, la aplicación de ejemplo muestra cómo se resuelven las dependencias definidas como parámetros de constructor en tiempo de ejecución.
Al ejecutar la aplicación, muestra una salida similar a la siguiente:
// 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)
En la salida de la aplicación, puede ver lo siguiente:
- Los servicios de Transient son siempre diferentes; se crea una nueva instancia cada vez que se recupera el servicio.
- Los servicios Scoped solo cambian con un nuevo ámbito, pero son la misma instancia dentro de un ámbito.
- Singleton servicios siempre son los mismos, una nueva instancia solo se crea una vez.