Compartir por


Guía de patrones de opciones para autores de bibliotecas de .NET

Con la ayuda de la inserción de dependencias, el registro de los servicios y sus configuraciones correspondientes pueden utilizar el patrón de opciones. El patrón de opciones permite a los consumidores de la biblioteca (y sus servicios) requerir instancias de interfaces de opciones donde TOptions es la clase de opciones. El consumo de opciones de configuración mediante objetos fuertemente tipados ayuda a garantizar una representación de valores coherente, habilita la validación con anotaciones de datos y elimina la carga de analizar manualmente los valores de cadena. Hay muchos proveedores de configuración que los consumidores de su biblioteca pueden utilizar. Con estos proveedores, los consumidores pueden configurar la biblioteca de muchas maneras.

Como autor de la biblioteca .NET, obtendrá instrucciones generales sobre cómo exponer correctamente el patrón de opciones a los consumidores de la biblioteca. Hay varias maneras de lograr lo mismo y varias consideraciones que se deben tener en cuenta.

Convenciones de nomenclatura

Por convención, los métodos de extensión responsables de registrar servicios se denominan Add{Service}, donde {Service} es un nombre significativo y descriptivo. Add{Service} Los métodos de extensión son comunes en ASP.NET Core y .NET por igual.

✔️ CONSIDERA nombres que distinguen tu servicio de otras ofertas.

❌ NO use nombres que ya formen parte del ecosistema de .NET de paquetes oficiales de Microsoft.

✔️ CONSIDERE la posibilidad de asignar nombres a clases estáticas que exponen métodos de extensión como {Type}Extensions, donde {Type} es el tipo que va a extender.

Guía sobre los espacios de nombres

Los paquetes de Microsoft usan el Microsoft.Extensions.DependencyInjection espacio de nombres para unificar el registro de varias ofertas de servicio.

✔️ CONSIDERE un espacio de nombres que identifique claramente la oferta del paquete.

❌ NO use el Microsoft.Extensions.DependencyInjection espacio de nombres para paquetes de Microsoft no oficiales.

Sin parámetros

Si el servicio puede funcionar con una configuración mínima o sin ninguna configuración explícita, considere un método de extensión sin parámetros.

using Microsoft.Extensions.DependencyInjection;

namespace ExampleLibrary.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMyLibraryService(
        this IServiceCollection services)
    {
        services.AddOptions<LibraryOptions>()
            .Configure(options =>
            {
                // Specify default option values
            });

        // Register lib services here...
        // services.AddScoped<ILibraryService, DefaultLibraryService>();

        return services;
    }
}

En el código anterior, AddMyLibraryService:

Parámetro IConfiguration

Al crear una biblioteca que expone muchas opciones a los consumidores, puede considerar la posibilidad de requerir un IConfiguration método de extensión de parámetros. La instancia esperada IConfiguration debe tener como ámbito una sección con nombre de la configuración mediante la función IConfiguration.GetSection.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace ExampleLibrary.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMyLibraryService(
      this IServiceCollection services,
      IConfiguration namedConfigurationSection)
    {
        // Default library options are overridden
        // by bound configuration values.
        services.Configure<LibraryOptions>(namedConfigurationSection);

        // Register lib services here...
        // services.AddScoped<ILibraryService, DefaultLibraryService>();

        return services;
    }
}

En el código anterior, AddMyLibraryService:

Los consumidores de este patrón proporcionan la instancia con ámbito IConfiguration de la sección titulada:

using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddMyLibraryService(
    builder.Configuration.GetSection("LibraryOptions"));

using IHost host = builder.Build();

// Application code should start here.

await host.RunAsync();

La llamada a .AddMyLibraryService se efectúa en el tipo IServiceCollection.

Como autor de la biblioteca, especificar los valores predeterminados es para usted.

Nota:

Es posible enlazar la configuración a una instancia de opciones. Sin embargo, existe un riesgo de colisiones de nombres, lo que provocará errores. Además, cuando se realiza un enlace manual de esta forma, se limita el consumo del patrón de opciones a una sola lectura. Los cambios en la configuración no se volverán a enlazar, ya que estos consumidores no podrán usar la interfaz IOptionsMonitor.

services.AddOptions<LibraryOptions>()
    .Configure<IConfiguration>(
        (options, configuration) =>
            configuration.GetSection("LibraryOptions").Bind(options));

En su lugar, debe usar el método de extensión BindConfiguration. Este método de extensión enlaza la configuración a la instancia de opciones y también registra un origen de token de cambio para la sección de configuración. Esto permite a los consumidores usar la interfaz IOptionsMonitor .

Parámetro de ruta de acceso de la sección de configuración

Es posible que los consumidores de la biblioteca quieran especificar la ruta de acceso de la sección de configuración para enlazar el tipo subyacente TOptions. En este escenario, se define un string parámetro en el método de extensión.

using Microsoft.Extensions.DependencyInjection;

namespace ExampleLibrary.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMyLibraryService(
      this IServiceCollection services,
      string configSectionPath)
    {
        services.AddOptions<SupportOptions>()
            .BindConfiguration(configSectionPath)
            .ValidateDataAnnotations()
            .ValidateOnStart();

        // Register lib services here...
        // services.AddScoped<ILibraryService, DefaultLibraryService>();

        return services;
    }
}

En el código anterior, AddMyLibraryService:

En el ejemplo siguiente, se usa el paquete NuGet Microsoft.Extensions.Options.DataAnnotations para habilitar la validación de anotaciones de datos. La clase SupportOptions se define como sigue:

using System.ComponentModel.DataAnnotations;

public sealed class SupportOptions
{
    [Url]
    public string? Url { get; set; }

    [Required, EmailAddress]
    public required string Email { get; set; }

    [Required, DataType(DataType.PhoneNumber)]
    public required string PhoneNumber { get; set; }
}

Imagine que se usa el siguiente archivo JSON appsettings.json :

{
    "Support": {
        "Url": "https://support.example.com",
        "Email": "help@support.example.com",
        "PhoneNumber": "+1(888)-SUPPORT"
    }
}

Parámetro Action<TOptions>

Los consumidores de su biblioteca pueden estar interesados en proporcionar una expresión lambda que genere una instancia de su clase de opciones. En este escenario, se define un Action<LibraryOptions> parámetro en el método de extensión.

using Microsoft.Extensions.DependencyInjection;

namespace ExampleLibrary.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMyLibraryService(
        this IServiceCollection services,
        Action<LibraryOptions> configureOptions)
    {
        services.Configure(configureOptions);

        // Register lib services here...
        // services.AddScoped<ILibraryService, DefaultLibraryService>();

        return services;
    }
}

En el código anterior, AddMyLibraryService:

Los consumidores de este patrón proporcionan una expresión lambda (o un delegado que satisface el Action<LibraryOptions> parámetro):

using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddMyLibraryService(options =>
{
    // User defined option values
    // options.SomePropertyValue = ...
});
                                                                        
using IHost host = builder.Build();

// Application code should start here.

await host.RunAsync();

Parámetro de la instancia de opciones

Es posible que los consumidores de la biblioteca prefieran proporcionar una instancia de opciones insertadas. En este escenario, se expone un método de extensión que toma una instancia del objeto options, LibraryOptions.

using Microsoft.Extensions.DependencyInjection;

namespace ExampleLibrary.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMyLibraryService(
      this IServiceCollection services,
      LibraryOptions userOptions)
    {
        services.AddOptions<LibraryOptions>()
            .Configure(options =>
            {
                // Overwrite default option values
                // with the user provided options.
                // options.SomeValue = userOptions.SomeValue;
            });

        // Register lib services here...
        // services.AddScoped<ILibraryService, DefaultLibraryService>();

        return services;
    }
}

En el código anterior, AddMyLibraryService:

Los consumidores de este patrón proporcionan una instancia de la clase LibraryOptions, definiendo los valores de propiedad deseados en línea.

using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddMyLibraryService(new LibraryOptions
{
    // Specify option values
    // SomePropertyValue = ...
});

using IHost host = builder.Build();

// Application code should start here.

await host.RunAsync();

Configuración posterior

Una vez enlazados o especificados todos los valores de opción de configuración, la funcionalidad posterior a la configuración está disponible. Al exponer el mismo Action<TOptions> parámetro detallado anteriormente, podrías optar por llamar a PostConfigure. La configuración posterior se ejecuta después de todas las llamadas a .Configure. Hay varias razones por las que podrías querer considerar utilizar PostConfigure.

  • Orden de ejecución: puede invalidar los valores de configuración establecidos en las .Configure llamadas.
  • Validación: puede validar que se han establecido los valores predeterminados después de aplicar todas las demás configuraciones.
using Microsoft.Extensions.DependencyInjection;

namespace ExampleLibrary.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMyLibraryService(
      this IServiceCollection services,
      Action<LibraryOptions> configureOptions)
    {
        services.PostConfigure(configureOptions);

        // Register lib services here...
        // services.AddScoped<ILibraryService, DefaultLibraryService>();

        return services;
    }
}

En el código anterior, AddMyLibraryService:

Los consumidores de este patrón proporcionan una expresión lambda (o un delegado que satisface el Action<LibraryOptions> parámetro), igual que lo harían con el Action<TOptions> parámetro en un escenario sin configuración posterior.

using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddMyLibraryService(options =>
{
    // Specify option values
    // options.SomePropertyValue = ...
});

using IHost host = builder.Build();

// Application code should start here.

await host.RunAsync();

Consulte también