Share via


Linee guida per i modelli di opzioni per gli autori di librerie .NET

Con l'aiuto dell'inserimento delle dipendenze, la registrazione dei servizi e delle configurazioni corrispondenti può usare il modello di opzioni. Il modello di opzioni consente ai consumer della libreria (e dei servizi) di richiedere istanze di interfacce di opzioni in cui TOptions è la classe di opzioni. L'utilizzo di opzioni di configurazione tramite oggetti fortemente tipizzati consente di garantire una rappresentazione coerente del valore, consente la convalida con le annotazioni dei dati e rimuove il carico di analisi manuale dei valori di stringa. Sono disponibili molti provider di configurazione per i consumer della libreria da usare. Con questi provider, i consumer possono configurare la libreria in molti modi.

Gli autori di librerie .NET apprenderanno indicazioni generali su come esporre correttamente il modello di opzioni ai consumer della libreria. Esistono diversi modi per ottenere la stessa cosa e diverse considerazioni da fare.

Convenzioni di denominazione

Per convenzione, i metodi di estensione responsabili della registrazione dei servizi sono denominati Add{Service}, dove {Service} è un nome significativo e descrittivo. I metodi di estensione Add{Service} sono comuni in ASP.NET Core e .NET allo stesso modo.

✔️ CONSIDERARE i nomi che disambiguino il servizio da altre offerte.

❌ NON usare nomi che fanno già parte dell'ecosistema .NET dai pacchetti Microsoft ufficiali.

✔️ CONSIDERARE la denominazione di classi statiche che espongono metodi di estensione come {Type}Extensions, dove {Type} è il tipo che si sta estendendo.

Indicazioni per lo spazio dei nomi

I pacchetti Microsoft usano lo spazio Microsoft.Extensions.DependencyInjection dei nomi per unificare la registrazione di varie offerte di servizio.

✔️ CONSIDERARE uno spazio dei nomi che identifica chiaramente l'offerta del pacchetto.

❌ NON usare lo spazio Microsoft.Extensions.DependencyInjection dei nomi per i pacchetti Microsoft non ufficiali.

Senza parametri

Se il servizio può funzionare con una configurazione minima o nessuna esplicita, prendere in considerazione un metodo di estensione senza parametri.

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

Nel codice precedente, AddMyLibraryService:

parametro IConfiguration

Quando si crea una libreria che espone molte opzioni ai consumer, è consigliabile prendere in considerazione la necessità di un metodo IConfiguration di estensione dei parametri. L'istanza IConfiguration prevista deve avere come ambito una sezione denominata della configurazione usando la funzione 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;
    }
}

Nel codice precedente, AddMyLibraryService:

I consumer in questo modello forniscono l'istanza di IConfiguration con ambito della sezione denominata:

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 chiamata a .AddMyLibraryService viene eseguita sul tipo di IServiceCollection.

L'autore della libreria è libero di specificare i valori predefiniti.

Nota

È possibile associare la configurazione a un'istanza di opzioni. Tuttavia, esiste un rischio di conflitti di nomi, che causeranno errori. Inoltre, quando si associa manualmente in questo modo, si limita l'utilizzo del modello di opzioni a read-once. Le modifiche apportate alle impostazioni non verranno riassociato, in quanto tali consumer non saranno in grado di usare l'interfaccia IOptionsMonitor .

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

È invece consigliabile usare il metodo di estensione BindConfiguration. Questo metodo di estensione associa la configurazione all'istanza delle opzioni e registra anche un'origine del token di modifica per la sezione di configurazione. In questo modo i consumer possono usare l'interfaccia IOptionsMonitor.

Parametro del percorso della sezione di configurazione

I consumer della libreria possono voler specificare il percorso della sezione di configurazione per associare il tipo di TOptions sottostante. In questo scenario si definisce un parametro string nel metodo di estensione.

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

Nel codice precedente, AddMyLibraryService:

Nell'esempio seguente viene usato il pacchetto NuGet Microsoft.Extensions.Options.DataAnnotations per abilitare la convalida dell'annotazione dei dati. La classe SupportOptions è definita come segue:

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

Si supponga che venga usato il file JSON appsettings.json seguente:

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

parametro Action<TOptions>

I consumer della libreria potrebbero essere interessati a fornire un'espressione lambda che restituisce un'istanza della classe options. In questo scenario si definisce un parametro Action<LibraryOptions> nel metodo di estensione.

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

Nel codice precedente, AddMyLibraryService:

I consumer in questo modello forniscono un'espressione lambda (o un delegato che soddisfa il parametro 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();

Parametro dell'istanza di Options

Gli utenti della libreria potrebbero preferire di fornire un'istanza di opzioni inlined. In questo scenario si espone un metodo di estensione che accetta un'istanza dell'oggetto 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;
    }
}

Nel codice precedente, AddMyLibraryService:

I consumer in questo modello forniscono un'istanza della classe LibraryOptions, definendo i valori di proprietà desiderati inline:

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

Post-configurazione

Dopo aver associato o specificato tutti i valori delle opzioni di configurazione, è disponibile la funzionalità post-configurazione. Esponendo lo stesso parametro Action<TOptions> dettagliato in precedenza, è possibile scegliere di chiamare PostConfigure. Post-configure viene eseguito dopo tutte le chiamate .Configure. Esistono alcuni motivi per cui si vuole prendere in considerazione l'uso di PostConfigure:

  • Ordine di esecuzione: è possibile eseguire l'override di tutti i valori di configurazione impostati nelle chiamate .Configure.
  • Convalida: è possibile verificare che i valori predefiniti siano stati impostati dopo l'applicazione di tutte le altre configurazioni.
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;
    }
}

Nel codice precedente, AddMyLibraryService:

I consumer in questo modello forniscono un'espressione lambda (o un delegato che soddisfa il parametro Action<LibraryOptions>), proprio come con il parametro Action<TOptions> in uno scenario di configurazione non post-configurazione:

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

Vedi anche