Compartir a través de


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:

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 objeto name.
  • Llama al método WriteLine en la instancia de IConsole.
  • 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 de DefaultConsole con el IsEnabled establecido en "true".
    • ElIGreetingService se agrega con un tipo de implementación correspondiente de tipoDefaultGreetingService.
    • FarewellService se agrega como un tipo concreto.
  • Compile el ServiceProvider desde el ServiceCollection.
  • Resuelva los servicios de IGreetingService y FarewellService.
  • 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.

Consulte también