Partager via


Conseils sur les modèles d’options pour les auteurs de bibliothèque .NET

Avec l’aide de l’injection de dépendances, l’inscription de vos services et leurs configurations correspondantes peuvent utiliser le modèle d’options. Le modèle d’options permet aux consommateurs de votre bibliothèque (et de vos services) de demander des instances d’interfaces d’optionsTOptions correspond à votre classe d’options. L’utilisation d’options de configuration par le biais d’objets fortement typés permet de garantir une représentation cohérente de la valeur, d’activer la validation avec des annotations de données et d’éviter la charge de travail que représente l’analyse manuelle de valeurs de chaîne. Il existe de nombreux fournisseurs de configuration que les consommateurs de votre bibliothèque peuvent utiliser. Ces fournisseurs permettent aux consommateurs de configurer votre bibliothèque de plusieurs façons.

En tant qu’auteur de bibliothèque .NET, vous allez découvrir des conseils généraux sur la manière d’exposer correctement le modèle d’options aux consommateurs de votre bibliothèque. Il existe différentes façons de parvenir au même résultat, et plusieurs considérations à prendre en compte.

Conventions d’affectation de noms

Par convention, les méthodes d’extension responsables de l’inscription des services sont nommées Add{Service}, où {Service} correspond à un nom explicite et descriptif. Les méthodes d’extension Add{Service} sont courantes dans ASP.NET Core et .NET.

✔️ PRIVILÉGIEZ les noms qui lèvent l’ambiguité entre votre service et d’autres offres.

❌ N’utilisez PAS de noms qui font déjà partie de l’écosystème .NET à partir de packages Microsoft officiels.

✔️ ENVISAGEZ de nommer les classes statiques qui exposent les méthodes d’extension en tant que {Type}Extensions, où {Type} correspond au type que vous étendez.

Conseils relatifs à l’espace de noms

Les packages Microsoft utilisent l’espace de noms Microsoft.Extensions.DependencyInjection pour unifier l’inscription de différentes offres de service.

✔️ PRIVILÉGIEZ un espace de noms qui identifie clairement votre offre de package.

❌ N’utilisez PAS l’espace de noms Microsoft.Extensions.DependencyInjection pour les packages Microsoft non officiels.

Sans paramètre

Si votre service peut fonctionner avec une configuration minimale ou sans configuration explicite, envisagez une méthode d’extension sans paramètre.

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

Dans le code précédent, AddMyLibraryService :

Paramètre IConfiguration

Lorsque vous créez une bibliothèque qui expose de nombreuses options aux consommateurs, vous pouvez envisager d’exiger une méthode d’extension de paramètre IConfiguration. L’instance attendue IConfiguration doit être étendue à une section nommée de la configuration à l’aide de la fonction 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;
    }
}

Dans le code précédent, AddMyLibraryService :

Les consommateurs de ce modèle fournissent l’instance délimitée IConfiguration de la section nommée :

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

L’appel à .AddMyLibraryService est effectué sur le type IServiceCollection.

En tant qu’auteur de bibliothèque, la spécification des valeurs par défaut vous incombe.

Notes

Il est possible de lier la configuration à une instance d’options. Toutefois, il existe un risque de collisions de noms susceptible de provoquer des erreurs. En outre, lors d’une telle liaison manuelle, vous limitez la consommation de votre modèle d’options en lecture seule. Les modifications apportées aux paramètres ne seront pas une nouvelle fois liées, car de tels consommateurs ne pourront pas utiliser l’interface IOptionsMonitor.

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

Vous devez plutôt utiliser la méthode d’extension BindConfiguration. Cette méthode d’extension lie la configuration à l’instance d’options et inscrit également une source de jeton de modification pour la section de configuration. Les consommateurs peuvent ainsi utiliser l’interface IOptionsMonitor.

Paramètre de chemin d’accès à la section de configuration

Les consommateurs de votre bibliothèque peuvent spécifier le chemin de section de configuration pour lier votre type sous-jacent TOptions. Dans ce scénario, vous définissez un paramètre string dans votre méthode d’extension.

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

Dans le code précédent, AddMyLibraryService :

Dans l’exemple suivant, le package NuGet Microsoft.Extensions.Options.DataAnnotations est utilisé pour activer la validation des annotations de données. La classe SupportOptions est définie comme suit :

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

Imaginez que le fichier JSON appsettings.json suivant est utilisé :

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

Paramètre Action<TOptions>

Les consommateurs de votre bibliothèque peuvent être intéressés par la mise à disposition d’une expression lambda qui génère une instance de votre classe d’options. Dans ce scénario, vous définissez un paramètre Action<LibraryOptions> dans votre méthode d’extension.

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

Dans le code précédent, AddMyLibraryService :

  • Étend une instance de IServiceCollection
  • Définit un paramètre Action<T>configureOptions, où T correspond à LibraryOptions
  • Appelle Configure compte tenu de l’action configureOptions

Les consommateurs de ce modèle fournissent une expression lambda (ou un délégué qui satisfait le paramètre 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();

Paramètre d’instance d’options

Les consommateurs de votre bibliothèque peuvent préférer fournir une instance d’options inlined. Dans ce scénario, vous exposez une méthode d’extension qui utilise une instance de votre objet d’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;
    }
}

Dans le code précédent, AddMyLibraryService :

Les consommateurs de ce modèle fournissent une instance de la classe LibraryOptions, en définissant les valeurs de propriété souhaitées inlined :

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

Après la configuration

Une fois toutes les valeurs d’options de configuration liées ou spécifiées, la fonctionnalité post-configuration est disponible. En exposant le même paramètre Action<TOptions> détaillé précédemment, vous pouvez choisir d’appeler PostConfigure. La post-configuration s’exécute après tous les appels .Configure. Il existe quelques raisons pour lesquelles vous souhaitez envisager d’utiliser PostConfigure :

  • Ordre d’exécution : vous pouvez remplacer toutes les valeurs de configuration qui ont été définies dans les .Configure appels.
  • Validation : vous pouvez vérifier que les valeurs par défaut ont été définies une fois que toutes les autres configurations ont été appliquées.
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;
    }
}

Dans le code précédent, AddMyLibraryService :

Les consommateurs de ce modèle fournissent une expression lambda (ou un délégué qui satisfait le paramètre Action<LibraryOptions>), comme ils le feraient avec le paramètre Action<TOptions> dans un scénario autre que pos-configuration :

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

Voir aussi