Descripción de los conceptos básicos de inserción de dependencias en .NET
En este artículo, creará una aplicación de consola de .NET que crea manualmente un ServiceCollection objeto y correspondiente ServiceProvider. Aprenderá a registrar servicios y resolverlos mediante la inserción de dependencias (DI). En este artículo se usa el paquete NuGet de Microsoft.Extensions.DependencyInjection para demostrar los conceptos básicos de la inserción de dependencias en .NET.
Nota:
En este artículo no se aprovechan las características del host genérico. Para obtener una guía más completa, consulte Uso de la inserción de dependencias en .NET.
Introducción
Para empezar, cree una nueva aplicación de consola de .NET denominada DI.Basics. En la lista siguiente se hace referencia a algunos de los enfoques más comunes para crear un proyecto de consola:
- Visual Studio: archivo > menú Nuevo >proyecto.
- Visual Studio Code y la opción de menú del Kit de desarrollo de C#: Explorador de soluciones.
- CLI de .NET:
dotnet new console
comando en el terminal.
Debe agregar la referencia de paquete a Microsoft.Extensions.DependencyInjection en el archivo del proyecto. Independientemente del enfoque, asegúrese de que el proyecto es similar al siguiente XML del archivo 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="8.0.0" />
</ItemGroup>
</Project>
Conceptos básicos de inserción de dependencias
La inserción de dependencias es un patrón de diseño que permite quitar dependencias codificadas de forma rígida y hacer que la aplicación sea más fácil de mantener y probar. DI es una técnica para lograr inversión de control (IoC) entre clases y sus dependencias.
Las abstracciones de DI en .NET se definen en el paquete NuGet Microsoft.Extensions.DependencyInjection.Abstractions:
- IServiceCollection: define un contrato para una colección de descriptores de servicio.
- IServiceProvider: define un mecanismo para recuperar un objeto de servicio.
- ServiceDescriptor: describe un servicio con su tipo de servicio, implementación y duración.
En .NET, la inserción de dependencias se administra agregando servicios y configurándolos en un IServiceCollection
. Una vez registrados los servicios, a medida que se compila IServiceProvider
instancia llamando al método BuildServiceProvider. El IServiceProvider
actúa como contenedor de todos los servicios registrados y se usa para resolver los servicios.
Creación de servicios de example
No todos los servicios se crean igualmente. Algunos servicios requieren una nueva instancia cada vez que el contenedor de servicios los obtiene (transient), mientras que otros deben compartirse entre solicitudes (scoped) o durante toda la duración de la aplicación (singleton). Para obtener más información sobre la duración del servicio, consulte Duración del servicio.
Del mismo modo, algunos servicios solo exponen un tipo concreto, mientras que otros se expresan como un contrato entre una interfaz y un tipo de implementación. Puede crear varias variaciones de servicios para ayudar a demostrar estos conceptos.
Cree un nuevo archivo de C# denominado IConsole.cs y agregue el código siguiente:
public interface IConsole
{
void WriteLine(string message);
}
Este archivo define una interfaz IConsole
que expone un único método, WriteLine
. A continuación, cree un nuevo archivo de C# denominado DefaultConsole.cs y agregue el código siguiente:
internal sealed class DefaultConsole : IConsole
{
public bool IsEnabled { get; set; } = true;
void IConsole.WriteLine(string message)
{
if (IsEnabled is false)
{
return;
}
Console.WriteLine(message);
}
}
El código anterior representa la implementación predeterminada de la interfaz IConsole
. El método WriteLine
escribe condicionalmente en la consola en función de la propiedad IsEnabled
.
Sugerencia
La nomenclatura de una implementación es una opción que el equipo de desarrollo debe aceptar. El prefijo Default
es una convención común para indicar un implementación predeterminada de una interfaz, pero no es necesario.
A continuación, cree un archivo IGreetingService.cs y agregue el siguiente código de C#:
public interface IGreetingService
{
string Greet(string name);
}
A continuación, agregue un nuevo archivo de C# denominado DefaultGreetingService.cs y agregue el código siguiente:
internal sealed class DefaultGreetingService(
IConsole console) : IGreetingService
{
public string Greet(string name)
{
var greeting = $"Hello, {name}!";
console.WriteLine(greeting);
return greeting;
}
}
El código anterior representa la implementación predeterminada de la interfaz IGreetingService
. La implementación del servicio requiere un IConsole
como parámetro de constructor principal. El método Greet
realiza las acciones siguientes:
- Cree un
greeting
determinado objetoname
. - Llama al método
WriteLine
en la instancia deIConsole
. - Devuelva el
greeting
al autor de la llamada.
El último servicio que se va a crear es el archivo FarewellService.cs, agregue el siguiente código de C# antes de continuar:
public class FarewellService(IConsole console)
{
public string SayGoodbye(string name)
{
var farewell = $"Goodbye, {name}!";
console.WriteLine(farewell);
return farewell;
}
}
El FarewellService
representa un tipo concreto, no una interfaz. Debe declararse como public
para que sea accesible para los consumidores. A diferencia de otros tipos de implementación de servicio que se declararon como internal
y sealed
, este código demuestra que no todos los servicios deben ser interfaces. También muestra que las implementaciones de servicio se pueden sealed
para evitar la herencia y internal
restringir el acceso al ensamblado.
Actualización de la clase Program
Abra el archivo Program.cs y reemplace el código existente por el código de C# siguiente:
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");
El código actualizado anterior muestra el procedimiento:
- Cree una instancia de
ServiceCollection
. - Registrar y configurar servicios en el
ServiceCollection
:- El
IConsole
mediante la sobrecarga del generador de implementación, devuelve un tipo deDefaultConsole
con elIsEnabled
establecido en "true". - El
IGreetingService
se agrega con un tipo de implementación correspondiente de tipoDefaultGreetingService
. FarewellService
se agrega como un tipo concreto.
- El
- Compile el
ServiceProvider
desde elServiceCollection
. - Resuelva los servicios de
IGreetingService
yFarewellService
. - Use los servicios resueltos para saludar y despedirse de una persona denominada
David
.
Si actualiza la propiedad IsEnabled
del DefaultConsole
a false
, los métodos Greet
y SayGoodbye
omiten escribir en los mensajes resultantes en la consola. Un cambio similar a este, ayuda a demostrar que elIConsole
servicio esinsertado en el IGreetingService
y FarewellService
servicios como una dependencia que influye en el comportamiento de las aplicaciones.
Todos estos servicios se registran como singletons, aunque para este ejemplo, funciona de forma idéntica si se registraron como servicios transient o scoped.
Importante
En este example ficticio, las duraciones del servicio no importan, pero en una aplicación real, debe tener en cuenta cuidadosamente la duración de cada servicio.
Ejecutar la aplicación de ejemplo
Para ejecutar la aplicación de ejemplo, presione F5 en Visual Studio, Visual Studio Code o ejecute el dotnet run
comando en el terminal. Cuando se complete la aplicación, debería ver la siguiente salida:
Hello, David!
Goodbye, David!
Descriptores de servicio
Las API más usadas para agregar servicios a la ServiceCollection
son métodos de extensión genéricos con nombre de duración, como:
AddSingleton<TService>
AddTransient<TService>
AddScoped<TService>
Estos métodos son métodos útiles que crean una instancia de ServiceDescriptor y lo agregan al ServiceCollection
. El ServiceDescriptor
es una clase sencilla que describe un servicio con su tipo de servicio, tipo de implementación y duración. También puede describir factorías de implementación e instancias.
Para cada uno de los servicios que registró en el ServiceCollection
, podría llamar directamente al método Add
con una instancia de ServiceDescriptor
. Considere los siguientes ejemplos:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IConsole),
implementationFactory: static _ => new DefaultConsole
{
IsEnabled = true
},
lifetime: ServiceLifetime.Singleton));
El código anterior es equivalente a cómo se registró el servicio IConsole
en el ServiceCollection
. El método Add
se usa para agregar una instancia de ServiceDescriptor
que describe el servicio IConsole
. El método estático ServiceDescriptor.Describe
delega a varios constructores de ServiceDescriptor
. Tenga en cuenta el código equivalente para el servicio IGreetingService
:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(IGreetingService),
implementationType: typeof(DefaultGreetingService),
lifetime: ServiceLifetime.Singleton));
En el código anterior se describe el servicio IGreetingService
con su tipo de servicio, tipo de implementación y duración. Por último, considere el código equivalente para el servicio FarewellService
:
services.Add(ServiceDescriptor.Describe(
serviceType: typeof(FarewellService),
implementationType: typeof(FarewellService),
lifetime: ServiceLifetime.Singleton));
El código anterior describe el tipo de FarewellService
concreto como los tipos de servicio e implementación. El servicio se registra como un servicio singleton.