Partilhar via


Orientação de padrão de opções para autores de bibliotecas .NET

Com a ajuda da injeção de dependência, registrar seus serviços e suas configurações correspondentes pode fazer uso do padrão de opções. O padrão de opções permite que os consumidores de sua biblioteca (e seus serviços) exijam instâncias de interfaces de opções onde TOptions está sua classe de opções. O consumo de opções de configuração por meio de objetos fortemente tipados ajuda a garantir uma representação de valor consistente, permite a validação com anotações de dados e elimina a carga de analisar manualmente valores de cadeia de caracteres. Há muitos provedores de configuração para os consumidores da sua biblioteca usarem. Com esses provedores, os consumidores podem configurar sua biblioteca de várias maneiras.

Como autor de uma biblioteca .NET, você aprenderá orientações gerais sobre como expor corretamente o padrão de opções aos consumidores da sua biblioteca. Há várias maneiras de conseguir a mesma coisa, e várias considerações a fazer.

Convenções de nomenclatura

Por convenção, os métodos de extensão responsáveis pelo registro de serviços são nomeados Add{Service}, onde {Service} é um nome significativo e descritivo. Add{Service}métodos de extensão são comuns no ASP.NET Core e no .NET.

✔️ CONSIDERE nomes que desambiguem seu serviço de outras ofertas.

❌ NÃO use nomes que já fazem parte do ecossistema .NET de pacotes oficiais da Microsoft.

✔️ CONSIDERE nomear classes estáticas que expõem métodos de extensão como {Type}Extensions, onde {Type} é o tipo que você está estendendo.

Diretrizes de namespace

Os pacotes da Microsoft usam o Microsoft.Extensions.DependencyInjection namespace para unificar o registro de várias ofertas de serviço.

✔️ CONSIDERE um namespace que identifique claramente sua oferta de pacote.

❌ NÃO use o namespace para pacotes não oficiais da Microsoft.Extensions.DependencyInjection Microsoft.

Sem parâmetros

Se o serviço puder funcionar com pouca ou nenhuma configuração explícita, considere um método de extensão sem 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;
    }
}

No código anterior, o AddMyLibraryService:

IConfiguration parâmetro

Ao criar uma biblioteca que expõe muitas opções aos consumidores, convém considerar a necessidade de um IConfiguration método de extensão de parâmetro. A instância esperada IConfiguration deve ter o escopo definido para uma seção nomeada da configuração usando a IConfiguration.GetSection função.

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;
    }
}

No código anterior, o AddMyLibraryService:

Os consumidores nesse padrão fornecem a instância com IConfiguration escopo da seção nomeada:

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();

A chamada para .AddMyLibraryService é feita no IServiceCollection tipo.

Como autor da biblioteca, a especificação de valores padrão depende de você.

Nota

É possível vincular a configuração a uma instância de opções. No entanto, há um risco de colisões de nomes - o que causará erros. Além disso, ao vincular manualmente dessa forma, você limita o consumo do padrão de opções para leitura única. As alterações nas configurações não serão redirecionadas, pois esses consumidores não poderão usar a interface IOptionsMonitor .

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

Em vez disso, você deve usar o BindConfiguration método de extensão. Esse método de extensão vincula a configuração à instância de opções e também registra uma fonte de token de alteração para a seção de configuração. Isso permite que os consumidores usem a interface IOptionsMonitor .

Parâmetro de caminho da seção de configuração

Os consumidores da sua biblioteca podem querer especificar o caminho da seção de configuração para vincular seu tipo subjacente TOptions . Nesse cenário, você define um string parâmetro em seu método de extensão.

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;
    }
}

No código anterior, o AddMyLibraryService:

No próximo exemplo, o pacote NuGet Microsoft.Extensions.Options.DataAnnotations é usado para habilitar a validação de anotação de dados. A SupportOptions classe é definida da seguinte forma:

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 o seguinte arquivo JSON appsettings.json é usado:

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

Action<TOptions> parâmetro

Os consumidores de sua biblioteca podem estar interessados em fornecer uma expressão lambda que produza uma instância de sua classe de opções. Nesse cenário, você define um Action<LibraryOptions> parâmetro em seu método de extensão.

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;
    }
}

No código anterior, o AddMyLibraryService:

Os consumidores nesse padrão fornecem uma expressão lambda (ou um delegado que satisfaz o 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 instância de opções

Os consumidores da sua biblioteca podem preferir fornecer uma instância de opções embutidas. Nesse cenário, você expõe um método de extensão que usa uma instância do seu 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;
    }
}

No código anterior, o AddMyLibraryService:

Os consumidores nesse padrão fornecem uma instância da classe, definindo os valores de LibraryOptions propriedade desejados embutidos:

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();

Pós-configuração

Depois que todos os valores de opção de configuração forem vinculados ou especificados, a funcionalidade pós-configuração estará disponível. Expondo o mesmo Action<TOptions> parâmetro detalhado anteriormente, você pode optar por chamar PostConfigure. A pós-configuração é executada após todas as .Configure chamadas. Há algumas razões pelas quais você gostaria de considerar o uso PostConfigure:

  • Ordem de execução: Você pode substituir quaisquer valores de configuração que foram definidos nas .Configure chamadas.
  • Validação: Você pode validar que os valores padrão foram definidos depois que todas as outras configurações foram aplicadas.
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;
    }
}

No código anterior, o AddMyLibraryService:

Os consumidores nesse padrão fornecem uma expressão lambda (ou um delegado que satisfaz o Action<LibraryOptions> parâmetro), assim como fariam com o Action<TOptions> parâmetro em um cenário de configuração não-post:

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 também