Pokyny k vzoru možností pro autory knihoven .NET

Díky injektáži závislostí může registrace služeb a jejich odpovídající konfigurace využít vzor možností. Vzor možností umožňuje uživatelům vaší knihovny (a vašim službám) vyžadovat instance rozhraní možností, kde TOptions je třída možností. Využívání možností konfigurace prostřednictvím objektů silného typu pomáhá zajistit konzistentní reprezentaci hodnot, umožňuje ověřování pomocí datových poznámek a odstraňuje zátěž ruční analýzy řetězcových hodnot. Uživatelé vaší knihovny mohou používat mnoho poskytovatelů konfigurace. S těmito poskytovateli můžou uživatelé konfigurovat vaši knihovnu mnoha způsoby.

Jako autor knihovny .NET se dozvíte obecné pokyny, jak správně vystavit vzor možností uživatelům knihovny. Existují různé způsoby, jak dosáhnout stejné věci, a několik aspektů, které je potřeba vzít v úvahu.

Zásady vytváření názvů

Podle konvence jsou metody rozšíření zodpovědné za registraci služeb pojmenovány Add{Service}, kde {Service} je smysluplný a popisný název. Add{Service} Metody rozšíření jsou běžné v ASP.NET Core a .NET.

✔️ ZVAŽTE názvy, které nejednoznačností vaší služby od jiných nabídek.

❌ NEPOUŽÍVEJTE názvy, které jsou již součástí ekosystému .NET z oficiálních balíčků Microsoftu.

✔️ ZVAŽTE pojmenování statických tříd, které zpřístupňují rozšiřující metody jako {Type}Extensions, kde {Type} je typ, který rozšiřujete.

Pokyny k oboru názvů

Balíčky Microsoftu využívají Microsoft.Extensions.DependencyInjection obor názvů ke sjednocení registrace různých nabídek služeb.

✔️ ZVAŽTE obor názvů, který jasně identifikuje vaši nabídku balíčků.

❌ NEPOUŽÍVEJTE Microsoft.Extensions.DependencyInjection obor názvů pro jiné než oficiální balíčky Společnosti Microsoft.

Bez parametrů

Pokud vaše služba může pracovat s minimální nebo žádnou explicitní konfigurací, zvažte metodu rozšíření bez parametrů.

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

V předchozím kódu:AddMyLibraryService

IConfiguration Parametr

Když vytvoříte knihovnu, která uživatelům zpřístupňuje mnoho možností, můžete zvážit vyžadování metody rozšíření parametru IConfiguration . Očekávaná IConfiguration instance by měla být vymezena na pojmenovanou část konfigurace pomocí IConfiguration.GetSection funkce.

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

V předchozím kódu:AddMyLibraryService

Příjemci v tomto vzoru poskytují vymezenou IConfiguration instanci pojmenovaného oddílu:

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

Volání .AddMyLibraryService se provede u IServiceCollection typu.

Jako autor knihovny je zadání výchozích hodnot na vás.

Poznámka:

Konfiguraci je možné svázat s instancí možností. Existuje však riziko kolizí názvů , což způsobí chyby. Navíc když tímto způsobem ručně vytvoříte vazbu, omezíte spotřebu vzorů možností na čtení jednou. Změny nastavení nebudou znovu vázány, protože uživatelé nebudou moci používat rozhraní IOptionsMonitor .

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

Místo toho byste měli použít metodu BindConfiguration rozšíření. Tato metoda rozšíření sváže konfiguraci s instancí možností a také zaregistruje zdroj tokenu změn pro oddíl konfigurace. To umožňuje uživatelům používat rozhraní IOptionsMonitor .

Parametr cesty oddílu konfigurace

Příjemci vaší knihovny mohou chtít zadat cestu ke konfiguračnímu oddílu pro vytvoření vazby souvisejícího TOptions typu. V tomto scénáři definujete string parametr v metodě rozšíření.

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

V předchozím kódu:AddMyLibraryService

V dalším příkladu se k povolení ověření datových poznámek použije balíček NuGet Microsoft.Extensions.Options.DataAnnotations . Třída SupportOptions je definována takto:

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

Představte si, že se používá následující soubor JSON appsettings.json :

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

Action<TOptions> Parametr

Spotřebitelé vaší knihovny mohou mít zájem o poskytnutí výrazu lambda, který poskytuje instanci vaší třídy možností. V tomto scénáři definujete Action<LibraryOptions> parametr v metodě rozšíření.

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

V předchozím kódu:AddMyLibraryService

Příjemci v tomto vzoru poskytují výraz lambda (nebo delegát, který tento Action<LibraryOptions> parametr splňuje):

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

Parametr instance možnosti

Příjemci vaší knihovny můžou raději poskytnout inlinovanou instanci možností. V tomto scénáři zveřejníte rozšiřující metodu, která přebírá instanci objektu 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;
    }
}

V předchozím kódu:AddMyLibraryService

Příjemci v tomto vzoru poskytují instanci třídy, která definuje hodnoty požadovaných LibraryOptions vlastností v textu:

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

Po konfiguraci

Po vázání nebo zadání všech hodnot možností konfigurace je k dispozici funkce po konfiguraci. Zveřejnění stejného Action<TOptions> parametru , který je podrobně popsán dříve, můžete zvolit volání PostConfigure. Po dokončení konfigurace se spustí po všech .Configure voláních. Existuje několik důvodů, proč byste měli zvážit použití PostConfigure:

  • Pořadí provádění: Můžete přepsat všechny hodnoty konfigurace, které byly nastaveny ve .Configure voláních.
  • Ověření: Po použití všech ostatních konfigurací můžete ověřit, jestli jsou nastavené výchozí hodnoty.
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;
    }
}

V předchozím kódu:AddMyLibraryService

Příjemci v tomto vzoru poskytují výraz lambda (nebo delegát, který tento parametr splňujeAction<LibraryOptions>), stejně jako u parametru Action<TOptions> ve scénáři konfigurace, který není po dokončení konfigurace:

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

Viz také