Compartilhar via


Diretrizes de padrão de opções para autores da biblioteca .NET

Com a ajuda da injeção de dependência, é possível registrar seus serviços e suas configurações correspondentes utilizando o 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 evita a análise manual dos valores de cadeia de caracteres. Há muitos provedores de configuração para os consumidores de sua biblioteca usarem. Com esses provedores, os consumidores podem configurar sua biblioteca de várias maneiras.

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

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 em ASP.NET Core e .NET.

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

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

✔️ CONSIDERE a nomeação de 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 Microsoft.Extensions.DependencyInjection namespace para pacotes não oficiais da Microsoft.

Sem-parâmetros

Se o serviço puder trabalhar com configuração mínima ou nenhuma 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:

Parâmetro IConfiguration

Ao criar uma biblioteca que expõe muitas opções aos consumidores, talvez você queira considerar a necessidade de um IConfiguration método de extensão de parâmetro. A instância esperada IConfiguration deve ser definida como escopo para uma seção nomeada da configuração usando a função 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;
    }
}

No código anterior, o AddMyLibraryService:

Os consumidores nesse padrão fornecem a instância IConfiguration em 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 a ser feita no método IServiceCollection.

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

Observação

É possível associar a configuração a uma instância de opções. No entanto, há um risco de conflitos de nomes - que resultarão em erros. Além disso, ao associar manualmente dessa forma, você limita o consumo do padrão de opções para leitura uma vez. As alterações nas configurações não serão vinculadas novamente, 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 associa 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 biblioteca podem querer especificar o caminho da seção de configuração para vincular o tipo TOptions subjacente. 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 classe SupportOptions é definida da seguinte maneira:

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 seja usado:

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

Parâmetro Action<TOptions>

Os consumidores de sua biblioteca podem estar interessados em fornecer uma expressão lambda que produz 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 parâmetro Action<LibraryOptions>):

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 de 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 objeto LibraryOptionsde opções.

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 LibraryOptions, definindo os valores de 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();

Configuração posterior

Depois que todos os valores de opção de configuração forem associados 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 configuração de postagem é executada após todas as chamadas .Configure. Há alguns motivos pelos quais você deseja considerar o uso PostConfigure:

  • Ordem de execução: você pode substituir todos os valores de configuração que foram definidos nas .Configure chamadas.
  • Validação: você pode validar se os valores padrão foram definidos depois que todas as outras configurações tiverem sido 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 parâmetro Action<LibraryOptions>), da mesma forma que fariam com o parâmetro Action<TOptions> em um cenário de configuração que seja pós-postagem:

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