Modèle d’options dans ASP.NET Core

Remarque

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version ASP.NET Core 8.0 de cet article.

Par Rick Anderson.

Le modèle d’options utilise des classes pour fournir un accès fortement typé aux groupes de paramètres associés. Quand les paramètres de configuration sont isolés par scénario dans des classes distinctes, l’application est conforme à deux principes d’ingénierie logicielle importants :

  • Encapsulation :
    • Les classes qui dépendent de paramètres de configuration dépendent uniquement de ceux qu’elles utilisent.
  • Séparation des responsabilités :
    • Les paramètres des différentes parties de l’application ne sont ni dépendants ni associés les uns aux autres.

Ces options fournissent également un mécanisme de validation des données de configuration. Pour plus d'informations, reportez-vous à la section Validation des options.

Cet article fournit des informations sur le modèle d’options dans ASP.NET Core. Pour plus d’informations sur l’utilisation du modèle d’options dans les applications console, consultez Modèle d’options dans .NET.

Lier une configuration hiérarchique

La meilleure méthode pour lire les valeurs de configuration associées consiste à utiliser le modèle d’options. Par exemple, pour lire les valeurs de configuration suivantes :

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

Créez la classe PositionOptions suivante :

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; } = String.Empty;
    public string Name { get; set; } = String.Empty;
}

Classe d’options :

  • Doit être non abstrait.
  • Possède des propriétés publiques en lecture-écriture du type qui ont des éléments correspondants dans la configuration sont liées.
  • Possède ses propriétés en lecture-écriture liées aux entrées correspondantes dans la configuration.
  • N’a pas de champs liés. Dans le code précédent, Position n’est pas lié. Le champ Position est utilisé pour qu’il ne soit pas nécessaire de coder la chaîne "Position" en dur dans l’application lors de la liaison de la classe à un fournisseur de configuration.

Le code suivant :

  • Appelle ConfigurationBinder.Bind pour lier la classe PositionOptions à la section Position.
  • Affiche les données de configuration Position.
public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

Dans le code précédent, par défaut, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

ConfigurationBinder.Get<T> lie et retourne le type spécifié. Il peut être plus pratique d’utiliser ConfigurationBinder.Get<T> que ConfigurationBinder.Bind. Le code suivant illustre la classe ConfigurationBinder.Get<T> avec la classe PositionOptions :

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions? positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

Dans le code précédent, par défaut, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

Bind permet également la création d’une classe abstraite. Prenez le code suivant, qui utilise la classe abstraite SomethingWithAName :

namespace ConfigSample.Options;

public abstract class SomethingWithAName
{
    public abstract string? Name { get; set; }
}

public class NameTitleOptions(int age) : SomethingWithAName
{
    public const string NameTitle = "NameTitle";

    public override string? Name { get; set; }
    public string Title { get; set; } = string.Empty;

    public int Age { get; set; } = age;
}

Le code suivant affiche les valeurs de configuration NameTitleOptions :

public class Test33Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test33Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var nameTitleOptions = new NameTitleOptions(22);
        Configuration.GetSection(NameTitleOptions.NameTitle).Bind(nameTitleOptions);

        return Content($"Title: {nameTitleOptions.Title} \n" +
                       $"Name: {nameTitleOptions.Name}  \n" +
                       $"Age: {nameTitleOptions.Age}"
                       );
    }
}

Les appels à Bind sont moins stricts que les appels à Get<> :

  • Bind permet la concrétisation d’une abstraction.
  • Get<> doit créer une instance elle-même.

Le modèle d’options

Une autre approche lors de l’utilisation du modèle d’options consiste à lier la section Position et à l’ajouter au conteneur de service d’injection de dépendances. Dans le code suivant, PositionOptions est ajouté au conteneur de service avec Configure et lié à la configuration :

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

À l’aide du code précédent, le code suivant lit les options de position :

public class Test2Model : PageModel
{
    private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Title: {_options.Title} \n" +
                       $"Name: {_options.Name}");
    }
}

Dans le code précédent, les modifications apportées au fichier de configuration JSON après le démarrage de l’application ne sont pas lues. Pour lire les modifications après que l’application a démarré, utilisez IOptionsSnapshot.

Interfaces d’options

IOptions<TOptions>:

IOptionsSnapshot<TOptions> :

IOptionsMonitor<TOptions> :

Les scénarios de post-configuration permettent de paramétrer ou de modifier les options après chaque configuration de IConfigureOptions<TOptions>.

IOptionsFactory<TOptions> est chargée de créer les instances d’options. Elle dispose d’une seule méthode Create. L’implémentation par défaut prend toutes les IConfigureOptions<TOptions> et IPostConfigureOptions<TOptions> inscrites et exécute toutes les configurations, puis les post-configurations. Elle fait la distinction entre IConfigureNamedOptions<TOptions> et IConfigureOptions<TOptions> et n’appelle que l’interface appropriée.

IOptionsMonitorCache<TOptions> est utilisée par IOptionsMonitor<TOptions> pour mettre en cache les instances TOptions. IOptionsMonitorCache<TOptions> invalide les instances des options dans le moniteur afin que la valeur soit recalculée (TryRemove). Les valeurs peuvent aussi être introduites manuellement avec TryAdd. La méthode Clear est utilisée quand toutes les instances nommées doivent être recréées à la demande.

Utiliser IOptionsSnapshot pour lire des données mises à jour

Utilisation de IOptionsSnapshot<TOptions>:

  • Les options sont calculées une fois par requête quand le système y accède et sont mises en cache pour toute la durée de vie de la requête.
  • Peut entraîner une pénalité de performances importante, car il s’agit d’un service délimité et est recalculé par requête. Pour plus d’informations, consultez ce problème GitHub et Améliorer les performances de la liaison de configuration.
  • Les modifications apportées à la configuration sont lues après le démarrage de l’application lors de l’utilisation de fournisseurs de configuration qui prennent en charge la lecture des valeurs de configuration mises à jour.

La différence entre IOptionsMonitor et IOptionsSnapshot est que :

  • IOptionsMonitor est un service Singleton qui récupère les valeurs d’option actuelles à tout instant, ce qui est particulièrement utile dans les dépendances Singleton.
  • IOptionsSnapshot est un service délimité et fournit un instantané des options au moment où l’objet IOptionsSnapshot<T> est construit. Les instantanés d’options sont conçus pour être utilisés avec des dépendances temporaires et étendues.

Le code suivant utilise IOptionsSnapshot<TOptions>.

public class TestSnapModel : PageModel
{
    private readonly MyOptions _snapshotOptions;

    public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
    {
        _snapshotOptions = snapshotOptionsAccessor.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_snapshotOptions.Option1} \n" +
                       $"Option2: {_snapshotOptions.Option2}");
    }
}

Le code suivant inscrit une instance de configuration qui MyOptions se lie à :

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

Dans le code précédent, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

IOptionsMonitor

Le code suivant inscrit une instance de configuration à laquelle MyOptions se lie.

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

L'exemple suivant utilise IOptionsMonitor<TOptions> :

public class TestMonitorModel : PageModel
{
    private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

    public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
                       $"Option2: {_optionsDelegate.CurrentValue.Option2}");
    }
}

Dans le code précédent, par défaut, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

Prise en charge des options nommées à l’aide de IConfigureNamedOptions

Options nommées :

  • Sont utiles lorsque plusieurs sections de configuration se lient aux mêmes propriétés.
  • Respectent la casse.

Examinons le fichier appsettings.json suivant :

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

Au lieu de créer deux classes à lier TopItem:Month et TopItem:Year, la classe suivante est utilisée pour chaque section :

public class TopItemSettings
{
    public const string Month = "Month";
    public const string Year = "Year";

    public string Name { get; set; } = string.Empty;
    public string Model { get; set; } = string.Empty;
}

Le code ci-dessous configure les options nommées :

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
    builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
    builder.Configuration.GetSection("TopItem:Year"));

var app = builder.Build();

Le code suivant affiche les options nommées :

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

Toutes les options sont des instances nommées. Les instances IConfigureOptions<TOptions> sont traitées comme ciblant l’instance Options.DefaultName, qui est string.Empty. En outre, IConfigureNamedOptions<TOptions> implémente IConfigureOptions<TOptions>. L’implémentation par défaut de IOptionsFactory<TOptions> possède une logique qui utilise chaque élément de manière appropriée. L’option nommée null est utilisée pour cibler toutes les instances nommées au lieu d’une instance nommée spécifique. ConfigureAll et PostConfigureAll utilisent cette convention.

API OptionsBuilder

OptionsBuilder<TOptions> permet de configurer des instances TOptions. OptionsBuilder simplifie la création d’options nommées. En effet, il est le seul paramètre de l’appel AddOptions<TOptions>(string optionsName) initial et n’apparaît pas dans les appels ultérieurs. La validation des options et les surcharges ConfigureOptions qui acceptent des dépendances de service sont uniquement disponibles avec OptionsBuilder.

OptionsBuilder est utilisé dans la section Validation des options.

Pour plus d’informations sur l’ajout d’un dépôt personnalisé, consultez Utiliser AddOptions pour configurer un dépôt personnalisé.

Utiliser les services d’injection de dépendances (DI) pour configurer des options

Les service sont accessibles à partir de l’injection de dépendances pendant la configuration des options de deux manières différentes :

  • Passez un délégué de configuration à Configure sur OptionsBuilder<TOptions>. OptionsBuilder<TOptions> fournit des surcharges de Configure qui permettent d’utiliser jusqu’à cinq services pour configurer des options :

    builder.Services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Créer un type qui implémente IConfigureOptions<TOptions> ou IConfigureNamedOptions<TOptions> et inscrit le type en tant que service.

Nous vous recommandons de transmettre un délégué de configuration à Configure, car il est plus complexe de créer un service. La création d’un type équivaut à ce que fait l’infrastructure lors de l’appel de Configure. L’appel de Configure a pour effet d’inscrire une instance générique temporaire de IConfigureNamedOptions<TOptions>, dont l’un des constructeurs accepte les types de service génériques spécifiés.

Validation des options

La validation des options permet de valider les valeurs d’option.

Examinons le fichier appsettings.json suivant :

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

La classe suivante est utilisé pour lier à la section de configuration "MyConfig" et applique quelques règles DataAnnotations :

public class MyConfigOptions
{
    public const string MyConfig = "MyConfig";

    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public string Key1 { get; set; }
    [Range(0, 1000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public int Key2 { get; set; }
    public int Key3 { get; set; }
}

Le code suivant :

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
            .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();

var app = builder.Build();

La méthode d'extension ValidateDataAnnotations est définie dans le package NuGet Microsoft.Extensions.Options.DataAnnotations. Pour les applications web qui utilisent le kit de développement logiciel (SDK) Microsoft.NET.Sdk.Web, ce package est référencé implicitement à partir de l’infrastructure partagée.

Le code suivant affiche les valeurs de configuration ou les erreurs de validation :

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptions<MyConfigOptions> _config;

    public HomeController(IOptions<MyConfigOptions> config,
                          ILogger<HomeController> logger)
    {
        _config = config;
        _logger = logger;

        try
        {
            var configValue = _config.Value;

        }
        catch (OptionsValidationException ex)
        {
            foreach (var failure in ex.Failures)
            {
                _logger.LogError(failure);
            }
        }
    }

    public ContentResult Index()
    {
        string msg;
        try
        {
            msg = $"Key1: {_config.Value.Key1} \n" +
                  $"Key2: {_config.Value.Key2} \n" +
                  $"Key3: {_config.Value.Key3}";
        }
        catch (OptionsValidationException optValEx)
        {
            return Content(optValEx.Message);
        }
        return Content(msg);
    }

Le code suivant applique une règle de validation plus complexe à l’aide d’un délégué :

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
            .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations()
        .Validate(config =>
        {
            if (config.Key2 != 0)
            {
                return config.Key3 > config.Key2;
            }

            return true;
        }, "Key3 must be > than Key2.");   // Failure message.

var app = builder.Build();

IValidateOptions<TOptions> et IValidatableObject

La classe suivante implémente IValidateOptions<TOptions> :

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string? vor = null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1!);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions permet de déplacer le code de validation hors de Program.cs et dans une classe.

À l’aide du code précédent, la validation est activée dans Program.cs avec le code suivant :

using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection(
                                        MyConfigOptions.MyConfig));

builder.Services.AddSingleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>();

var app = builder.Build();

La validation des options prend également en charge IValidatableObject. Pour effectuer une validation au niveau de la classe d’une classe dans la classe elle-même :

ValidateOnStart

La validation des options s’exécute la première fois qu’une implémentation IOptions<TOptions>, IOptionsSnapshot<TOptions> ou IOptionsMonitor<TOptions> est créée. Pour exécuter la validation des options rapidement, appelez ValidateOnStart dans Program.cs lorsque l’application démarre :

builder.Services.AddOptions<MyConfigOptions>()
    .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
    .ValidateDataAnnotations()
    .ValidateOnStart();

Options de post-configuration

Définissez la post-configuration avec IPostConfigureOptions<TOptions>. La post-configuration s’exécute après chaque configuration de IConfigureOptions<TOptions> :

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
                .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
    myOptions.Key1 = "post_configured_key1_value";
});

PostConfigure permet de post-configurer les options nommées :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
    builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
    builder.Configuration.GetSection("TopItem:Year"));

builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
    myOptions.Name = "post_configured_name_value";
    myOptions.Model = "post_configured_model_value";
});

var app = builder.Build();

Utilisez PostConfigureAll pour post-configurer toutes les instances de configuration :

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
                .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
    myOptions.Key1 = "post_configured_key1_value";
});

Options d'accès dans Program.cs

Pour accéder à IOptions<TOptions> ou à IOptionsMonitor<TOptions> dans Program.cs, appelez GetRequiredService sur WebApplication.Services :

var app = builder.Build();

var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
    .CurrentValue.Option1;

Ressources supplémentaires

Par Kirk Larkin et Rick Anderson.

Le modèle d’options utilise des classes pour fournir un accès fortement typé aux groupes de paramètres associés. Quand les paramètres de configuration sont isolés par scénario dans des classes distinctes, l’application est conforme à deux principes d’ingénierie logicielle importants :

  • Encapsulation :
    • Les classes qui dépendent de paramètres de configuration dépendent uniquement de ceux qu’elles utilisent.
  • Séparation des responsabilités :
    • Les paramètres des différentes parties de l’application ne sont ni dépendants ni associés les uns aux autres.

Ces options fournissent également un mécanisme de validation des données de configuration. Pour plus d'informations, reportez-vous à la section Validation des options.

Cet article fournit des informations sur le modèle d’options dans ASP.NET Core. Pour plus d’informations sur l’utilisation du modèle d’options dans les applications console, consultez Modèle d’options dans .NET.

Lier une configuration hiérarchique

La meilleure méthode pour lire les valeurs de configuration associées consiste à utiliser le modèle d’options. Par exemple, pour lire les valeurs de configuration suivantes :

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

Créez la classe PositionOptions suivante :

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; } = String.Empty;
    public string Name { get; set; } = String.Empty;
}

Classe d’options :

  • elle doit être non abstraite avec un constructeur public sans paramètre.
  • Toutes les propriétés publiques en lecture/écriture du type sont liées.
  • Les champs ne sont pas liés. Dans le code précédent, Position n’est pas lié. Le champ Position est utilisé pour qu’il ne soit pas nécessaire de coder la chaîne "Position" en dur dans l’application lors de la liaison de la classe à un fournisseur de configuration.

Le code suivant :

  • Appelle ConfigurationBinder.Bind pour lier la classe PositionOptions à la section Position.
  • Affiche les données de configuration Position.
public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

Dans le code précédent, par défaut, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

ConfigurationBinder.Get<T> lie et retourne le type spécifié. Il peut être plus pratique d’utiliser ConfigurationBinder.Get<T> que ConfigurationBinder.Bind. Le code suivant illustre la classe ConfigurationBinder.Get<T> avec la classe PositionOptions :

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions? positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

Dans le code précédent, par défaut, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

Une autre approche lors de l’utilisation du modèle d’options consiste à lier la section Position et à l’ajouter au conteneur de service d’injection de dépendances. Dans le code suivant, PositionOptions est ajouté au conteneur de service avec Configure et lié à la configuration :

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();

À l’aide du code précédent, le code suivant lit les options de position :

public class Test2Model : PageModel
{
    private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Title: {_options.Title} \n" +
                       $"Name: {_options.Name}");
    }
}

Dans le code précédent, les modifications apportées au fichier de configuration JSON après le démarrage de l’application ne sont pas lues. Pour lire les modifications après que l’application a démarré, utilisez IOptionsSnapshot.

Interfaces d’options

IOptions<TOptions> :

IOptionsSnapshot<TOptions> :

IOptionsMonitor<TOptions> :

Les scénarios de post-configuration permettent de paramétrer ou de modifier les options après chaque configuration de IConfigureOptions<TOptions>.

IOptionsFactory<TOptions> est chargée de créer les instances d’options. Elle dispose d’une seule méthode Create. L’implémentation par défaut prend toutes les IConfigureOptions<TOptions> et IPostConfigureOptions<TOptions> inscrites et exécute toutes les configurations, puis les post-configurations. Elle fait la distinction entre IConfigureNamedOptions<TOptions> et IConfigureOptions<TOptions> et n’appelle que l’interface appropriée.

IOptionsMonitorCache<TOptions> est utilisée par IOptionsMonitor<TOptions> pour mettre en cache les instances TOptions. IOptionsMonitorCache<TOptions> invalide les instances des options dans le moniteur afin que la valeur soit recalculée (TryRemove). Les valeurs peuvent aussi être introduites manuellement avec TryAdd. La méthode Clear est utilisée quand toutes les instances nommées doivent être recréées à la demande.

Utiliser IOptionsSnapshot pour lire des données mises à jour

Utilisation de IOptionsSnapshot<TOptions>:

  • Les options sont calculées une fois par requête quand le système y accède et sont mises en cache pour toute la durée de vie de la requête.
  • Peut entraîner une pénalité de performances importante, car il s’agit d’un service délimité et est recalculé par requête. Pour plus d’informations, consultez ce problème GitHub et Améliorer les performances de la liaison de configuration.
  • Les modifications apportées à la configuration sont lues après le démarrage de l’application lors de l’utilisation de fournisseurs de configuration qui prennent en charge la lecture des valeurs de configuration mises à jour.

La différence entre IOptionsMonitor et IOptionsSnapshot est que :

  • IOptionsMonitor est un service Singleton qui récupère les valeurs d’option actuelles à tout instant, ce qui est particulièrement utile dans les dépendances Singleton.
  • IOptionsSnapshot est un service délimité et fournit un instantané des options au moment où l’objet IOptionsSnapshot<T> est construit. Les instantanés d’options sont conçus pour être utilisés avec des dépendances temporaires et étendues.

Le code suivant utilise IOptionsSnapshot<TOptions>.

public class TestSnapModel : PageModel
{
    private readonly MyOptions _snapshotOptions;

    public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
    {
        _snapshotOptions = snapshotOptionsAccessor.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_snapshotOptions.Option1} \n" +
                       $"Option2: {_snapshotOptions.Option2}");
    }
}

Le code suivant inscrit une instance de configuration qui MyOptions se lie à :

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

Dans le code précédent, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

IOptionsMonitor

Le code suivant inscrit une instance de configuration à laquelle MyOptions se lie.

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<MyOptions>(
    builder.Configuration.GetSection("MyOptions"));

var app = builder.Build();

L'exemple suivant utilise IOptionsMonitor<TOptions> :

public class TestMonitorModel : PageModel
{
    private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

    public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
                       $"Option2: {_optionsDelegate.CurrentValue.Option2}");
    }
}

Dans le code précédent, par défaut, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

Prise en charge des options nommées à l’aide de IConfigureNamedOptions

Options nommées :

  • Sont utiles lorsque plusieurs sections de configuration se lient aux mêmes propriétés.
  • Respectent la casse.

Examinons le fichier appsettings.json suivant :

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

Au lieu de créer deux classes à lier TopItem:Month et TopItem:Year, la classe suivante est utilisée pour chaque section :

public class TopItemSettings
{
    public const string Month = "Month";
    public const string Year = "Year";

    public string Name { get; set; } = string.Empty;
    public string Model { get; set; } = string.Empty;
}

Le code ci-dessous configure les options nommées :

using SampleApp.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
    builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
    builder.Configuration.GetSection("TopItem:Year"));

var app = builder.Build();

Le code suivant affiche les options nommées :

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

Toutes les options sont des instances nommées. Les instances IConfigureOptions<TOptions> sont traitées comme ciblant l’instance Options.DefaultName, qui est string.Empty. En outre, IConfigureNamedOptions<TOptions> implémente IConfigureOptions<TOptions>. L’implémentation par défaut de IOptionsFactory<TOptions> possède une logique qui utilise chaque élément de manière appropriée. L’option nommée null est utilisée pour cibler toutes les instances nommées au lieu d’une instance nommée spécifique. ConfigureAll et PostConfigureAll utilisent cette convention.

API OptionsBuilder

OptionsBuilder<TOptions> permet de configurer des instances TOptions. OptionsBuilder simplifie la création d’options nommées. En effet, il est le seul paramètre de l’appel AddOptions<TOptions>(string optionsName) initial et n’apparaît pas dans les appels ultérieurs. La validation des options et les surcharges ConfigureOptions qui acceptent des dépendances de service sont uniquement disponibles avec OptionsBuilder.

OptionsBuilder est utilisé dans la section Validation des options.

Pour plus d’informations sur l’ajout d’un dépôt personnalisé, consultez Utiliser AddOptions pour configurer un dépôt personnalisé.

Utiliser les services d’injection de dépendances (DI) pour configurer des options

Les service sont accessibles à partir de l’injection de dépendances pendant la configuration des options de deux manières différentes :

  • Passez un délégué de configuration à Configure sur OptionsBuilder<TOptions>. OptionsBuilder<TOptions> fournit des surcharges de Configure qui permettent d’utiliser jusqu’à cinq services pour configurer des options :

    builder.Services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Créer un type qui implémente IConfigureOptions<TOptions> ou IConfigureNamedOptions<TOptions> et inscrit le type en tant que service.

Nous vous recommandons de transmettre un délégué de configuration à Configure, car il est plus complexe de créer un service. La création d’un type équivaut à ce que fait l’infrastructure lors de l’appel de Configure. L’appel de Configure a pour effet d’inscrire une instance générique temporaire de IConfigureNamedOptions<TOptions>, dont l’un des constructeurs accepte les types de service génériques spécifiés.

Validation des options

La validation des options permet de valider les valeurs d’option.

Examinons le fichier appsettings.json suivant :

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

La classe suivante est utilisé pour lier à la section de configuration "MyConfig" et applique quelques règles DataAnnotations :

public class MyConfigOptions
{
    public const string MyConfig = "MyConfig";

    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public string Key1 { get; set; }
    [Range(0, 1000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public int Key2 { get; set; }
    public int Key3 { get; set; }
}

Le code suivant :

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
            .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();

var app = builder.Build();

La méthode d'extension ValidateDataAnnotations est définie dans le package NuGet Microsoft.Extensions.Options.DataAnnotations. Pour les applications web qui utilisent le kit de développement logiciel (SDK) Microsoft.NET.Sdk.Web, ce package est référencé implicitement à partir de l’infrastructure partagée.

Le code suivant affiche les valeurs de configuration ou les erreurs de validation :

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptions<MyConfigOptions> _config;

    public HomeController(IOptions<MyConfigOptions> config,
                          ILogger<HomeController> logger)
    {
        _config = config;
        _logger = logger;

        try
        {
            var configValue = _config.Value;

        }
        catch (OptionsValidationException ex)
        {
            foreach (var failure in ex.Failures)
            {
                _logger.LogError(failure);
            }
        }
    }

    public ContentResult Index()
    {
        string msg;
        try
        {
            msg = $"Key1: {_config.Value.Key1} \n" +
                  $"Key2: {_config.Value.Key2} \n" +
                  $"Key3: {_config.Value.Key3}";
        }
        catch (OptionsValidationException optValEx)
        {
            return Content(optValEx.Message);
        }
        return Content(msg);
    }

Le code suivant applique une règle de validation plus complexe à l’aide d’un délégué :

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
            .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations()
        .Validate(config =>
        {
            if (config.Key2 != 0)
            {
                return config.Key3 > config.Key2;
            }

            return true;
        }, "Key3 must be > than Key2.");   // Failure message.

var app = builder.Build();

IValidateOptions<TOptions> et IValidatableObject

La classe suivante implémente IValidateOptions<TOptions> :

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string? vor = null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1!);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions permet de déplacer le code de validation hors de Program.cs et dans une classe.

À l’aide du code précédent, la validation est activée dans Program.cs avec le code suivant :

using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection(
                                        MyConfigOptions.MyConfig));

builder.Services.AddSingleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>();

var app = builder.Build();

La validation des options prend également en charge IValidatableObject. Pour effectuer une validation au niveau de la classe d’une classe dans la classe elle-même :

ValidateOnStart

La validation des options s’exécute la première fois qu’une implémentation IOptions<TOptions>, IOptionsSnapshot<TOptions> ou IOptionsMonitor<TOptions> est créée. Pour exécuter la validation des options rapidement, appelez ValidateOnStart dans Program.cs lorsque l’application démarre :

builder.Services.AddOptions<MyConfigOptions>()
    .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
    .ValidateDataAnnotations()
    .ValidateOnStart();

Options de post-configuration

Définissez la post-configuration avec IPostConfigureOptions<TOptions>. La post-configuration s’exécute après chaque configuration de IConfigureOptions<TOptions> :

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
                .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
    myOptions.Key1 = "post_configured_key1_value";
});

PostConfigure permet de post-configurer les options nommées :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
    builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
    builder.Configuration.GetSection("TopItem:Year"));

builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
    myOptions.Name = "post_configured_name_value";
    myOptions.Model = "post_configured_model_value";
});

var app = builder.Build();

Utilisez PostConfigureAll pour post-configurer toutes les instances de configuration :

using OptionsValidationSample.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

builder.Services.AddOptions<MyConfigOptions>()
                .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));

builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
    myOptions.Key1 = "post_configured_key1_value";
});

Options d'accès dans Program.cs

Pour accéder à IOptions<TOptions> ou à IOptionsMonitor<TOptions> dans Program.cs, appelez GetRequiredService sur WebApplication.Services :

var app = builder.Build();

var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
    .CurrentValue.Option1;

Ressources supplémentaires

Par Kirk Larkin et Rick Anderson.

Le modèle d’options utilise des classes pour fournir un accès fortement typé aux groupes de paramètres associés. Quand les paramètres de configuration sont isolés par scénario dans des classes distinctes, l’application est conforme à deux principes d’ingénierie logicielle importants :

  • Encapsulation :
    • Les classes qui dépendent de paramètres de configuration dépendent uniquement de ceux qu’elles utilisent.
  • Séparation des responsabilités :
    • Les paramètres des différentes parties de l’application ne sont ni dépendants ni associés les uns aux autres.

Ces options fournissent également un mécanisme de validation des données de configuration. Pour plus d'informations, reportez-vous à la section Validation des options.

Cette rubrique fournit des informations sur le modèle d’options dans ASP.NET Core. Pour plus d’informations sur l’utilisation du modèle d’options dans les applications console, consultez Modèle d’options dans .NET.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Lier une configuration hiérarchique

La meilleure méthode pour lire les valeurs de configuration associées consiste à utiliser le modèle d’options. Par exemple, pour lire les valeurs de configuration suivantes :

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

Créez la classe PositionOptions suivante :

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; }
    public string Name { get; set; }
}

Classe d’options :

  • elle doit être non abstraite avec un constructeur public sans paramètre.
  • Toutes les propriétés publiques en lecture/écriture du type sont liées.
  • Les champs ne sont pas liés. Dans le code précédent, Position n’est pas lié. La propriété Position est utilisée pour qu’il ne soit pas nécessaire de coder la chaîne "Position" en dur dans l’application lors de la liaison de la classe à un fournisseur de configuration.

Le code suivant :

  • Appelle ConfigurationBinder.Bind pour lier la classe PositionOptions à la section Position.
  • Affiche les données de configuration Position.
public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

Dans le code précédent, par défaut, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

ConfigurationBinder.Get<T> lie et retourne le type spécifié. Il peut être plus pratique d’utiliser ConfigurationBinder.Get<T> que ConfigurationBinder.Bind. Le code suivant illustre la classe ConfigurationBinder.Get<T> avec la classe PositionOptions :

public class Test21Model : PageModel
{
    private readonly IConfiguration Configuration;
    public PositionOptions positionOptions { get; private set; }

    public Test21Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

Dans le code précédent, par défaut, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

Une autre approche lors de l’utilisation du modèle d’options consiste à lier la section Position et à l’ajouter au conteneur de service d’injection de dépendances. Dans le code suivant, PositionOptions est ajouté au conteneur de service avec Configure et lié à la configuration :

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(Configuration.GetSection(
                                        PositionOptions.Position));
    services.AddRazorPages();
}

À l’aide du code précédent, le code suivant lit les options de position :

public class Test2Model : PageModel
{
    private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Title: {_options.Title} \n" +
                       $"Name: {_options.Name}");
    }
}

Dans le code précédent, les modifications apportées au fichier de configuration JSON après le démarrage de l’application ne sont pas lues. Pour lire les modifications après que l’application a démarré, utilisez IOptionsSnapshot.

Interfaces d’options

IOptions<TOptions> :

IOptionsSnapshot<TOptions> :

IOptionsMonitor<TOptions> :

Les scénarios de post-configuration permettent de paramétrer ou de modifier les options après chaque configuration de IConfigureOptions<TOptions>.

IOptionsFactory<TOptions> est chargée de créer les instances d’options. Elle dispose d’une seule méthode Create. L’implémentation par défaut prend toutes les IConfigureOptions<TOptions> et IPostConfigureOptions<TOptions> inscrites et exécute toutes les configurations, puis les post-configurations. Elle fait la distinction entre IConfigureNamedOptions<TOptions> et IConfigureOptions<TOptions> et n’appelle que l’interface appropriée.

IOptionsMonitorCache<TOptions> est utilisée par IOptionsMonitor<TOptions> pour mettre en cache les instances TOptions. IOptionsMonitorCache<TOptions> invalide les instances des options dans le moniteur afin que la valeur soit recalculée (TryRemove). Les valeurs peuvent aussi être introduites manuellement avec TryAdd. La méthode Clear est utilisée quand toutes les instances nommées doivent être recréées à la demande.

Utiliser IOptionsSnapshot pour lire des données mises à jour

À l’aide de IOptionsSnapshot<TOptions>, les options sont calculées une fois par requête lorsque le système y accède et sont mises en cache pour toute la durée de vie de la requête. Les modifications apportées à la configuration sont lues après le démarrage de l’application lors de l’utilisation de fournisseurs de configuration qui prennent en charge la lecture des valeurs de configuration mises à jour.

La différence entre IOptionsMonitor et IOptionsSnapshot est que :

  • IOptionsMonitor est un service Singleton qui récupère les valeurs d’option actuelles à tout instant, ce qui est particulièrement utile dans les dépendances Singleton.
  • IOptionsSnapshot est un service délimité et fournit un instantané des options au moment où l’objet IOptionsSnapshot<T> est construit. Les instantanés d’options sont conçus pour être utilisés avec des dépendances temporaires et étendues.

Le code suivant utilise IOptionsSnapshot<TOptions>.

public class TestSnapModel : PageModel
{
    private readonly MyOptions _snapshotOptions;

    public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
    {
        _snapshotOptions = snapshotOptionsAccessor.Value;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_snapshotOptions.Option1} \n" +
                       $"Option2: {_snapshotOptions.Option2}");
    }
}

Le code suivant inscrit une instance de configuration qui MyOptions se lie à :

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

Dans le code précédent, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

IOptionsMonitor

Le code suivant inscrit une instance de configuration à laquelle MyOptions se lie.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));

    services.AddRazorPages();
}

L'exemple suivant utilise IOptionsMonitor<TOptions> :

public class TestMonitorModel : PageModel
{
    private readonly IOptionsMonitor<MyOptions> _optionsDelegate;

    public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
    {
        _optionsDelegate = optionsDelegate;
    }

    public ContentResult OnGet()
    {
        return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
                       $"Option2: {_optionsDelegate.CurrentValue.Option2}");
    }
}

Dans le code précédent, par défaut, les modifications apportées au fichier de configuration JSON après le démarrage de l’application sont lues.

Prise en charge des options nommées à l’aide de IConfigureNamedOptions

Options nommées :

  • Sont utiles lorsque plusieurs sections de configuration se lient aux mêmes propriétés.
  • Respectent la casse.

Examinons le fichier appsettings.json suivant :

{
  "TopItem": {
    "Month": {
      "Name": "Green Widget",
      "Model": "GW46"
    },
    "Year": {
      "Name": "Orange Gadget",
      "Model": "OG35"
    }
  }
}

Au lieu de créer deux classes à lier TopItem:Month et TopItem:Year, la classe suivante est utilisée pour chaque section :

public class TopItemSettings
{
    public const string Month = "Month";
    public const string Year = "Year";

    public string Name { get; set; }
    public string Model { get; set; }
}

Le code ci-dessous configure les options nommées :

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<TopItemSettings>(TopItemSettings.Month,
                                       Configuration.GetSection("TopItem:Month"));
    services.Configure<TopItemSettings>(TopItemSettings.Year,
                                        Configuration.GetSection("TopItem:Year"));

    services.AddRazorPages();
}

Le code suivant affiche les options nommées :

public class TestNOModel : PageModel
{
    private readonly TopItemSettings _monthTopItem;
    private readonly TopItemSettings _yearTopItem;

    public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
    {
        _monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
        _yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
    }

    public ContentResult OnGet()
    {
        return Content($"Month:Name {_monthTopItem.Name} \n" +
                       $"Month:Model {_monthTopItem.Model} \n\n" +
                       $"Year:Name {_yearTopItem.Name} \n" +
                       $"Year:Model {_yearTopItem.Model} \n"   );
    }
}

Toutes les options sont des instances nommées. Les instances IConfigureOptions<TOptions> sont traitées comme ciblant l’instance Options.DefaultName, qui est string.Empty. En outre, IConfigureNamedOptions<TOptions> implémente IConfigureOptions<TOptions>. L’implémentation par défaut de IOptionsFactory<TOptions> possède une logique qui utilise chaque élément de manière appropriée. L’option nommée null est utilisée pour cibler toutes les instances nommées au lieu d’une instance nommée spécifique. ConfigureAll et PostConfigureAll utilisent cette convention.

API OptionsBuilder

OptionsBuilder<TOptions> permet de configurer des instances TOptions. OptionsBuilder simplifie la création d’options nommées. En effet, il est le seul paramètre de l’appel AddOptions<TOptions>(string optionsName) initial et n’apparaît pas dans les appels ultérieurs. La validation des options et les surcharges ConfigureOptions qui acceptent des dépendances de service sont uniquement disponibles avec OptionsBuilder.

OptionsBuilder est utilisé dans la section Validation des options.

Pour plus d’informations sur l’ajout d’un dépôt personnalisé, consultez Utiliser AddOptions pour configurer un dépôt personnalisé.

Utiliser les services d’injection de dépendances (DI) pour configurer des options

Les service sont accessibles à partir de l’injection de dépendances pendant la configuration des options de deux manières différentes :

  • Passez un délégué de configuration à Configure sur OptionsBuilder<TOptions>. OptionsBuilder<TOptions> fournit des surcharges de Configure qui permettent d’utiliser jusqu’à cinq services pour configurer des options :

    services.AddOptions<MyOptions>("optionalName")
        .Configure<Service1, Service2, Service3, Service4, Service5>(
            (o, s, s2, s3, s4, s5) => 
                o.Property = DoSomethingWith(s, s2, s3, s4, s5));
    
  • Créer un type qui implémente IConfigureOptions<TOptions> ou IConfigureNamedOptions<TOptions> et inscrit le type en tant que service.

Nous vous recommandons de transmettre un délégué de configuration à Configure, car il est plus complexe de créer un service. La création d’un type équivaut à ce que fait l’infrastructure lors de l’appel de Configure. L’appel de Configure a pour effet d’inscrire une instance générique temporaire de IConfigureNamedOptions<TOptions>, dont l’un des constructeurs accepte les types de service génériques spécifiés.

Validation des options

La validation des options permet de valider les valeurs d’option.

Examinons le fichier appsettings.json suivant :

{
  "MyConfig": {
    "Key1": "My Key One",
    "Key2": 10,
    "Key3": 32
  }
}

La classe suivante est liée à la section de configuration "MyConfig" et applique quelques règles DataAnnotations :

public class MyConfigOptions
{
    public const string MyConfig = "MyConfig";

    [RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
    public string Key1 { get; set; }
    [Range(0, 1000,
        ErrorMessage = "Value for {0} must be between {1} and {2}.")]
    public int Key2 { get; set; }
    public int Key3 { get; set; }
}

Le code suivant :

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOptions<MyConfigOptions>()
            .Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();

        services.AddControllersWithViews();
    }

La méthode d'extension ValidateDataAnnotations est définie dans le package NuGet Microsoft.Extensions.Options.DataAnnotations. Pour les applications web qui utilisent le kit de développement logiciel (SDK) Microsoft.NET.Sdk.Web, ce package est référencé implicitement à partir de l’infrastructure partagée.

Le code suivant affiche les valeurs de configuration ou les erreurs de validation :

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IOptions<MyConfigOptions> _config;

    public HomeController(IOptions<MyConfigOptions> config,
                          ILogger<HomeController> logger)
    {
        _config = config;
        _logger = logger;

        try
        {
            var configValue = _config.Value;

        }
        catch (OptionsValidationException ex)
        {
            foreach (var failure in ex.Failures)
            {
                _logger.LogError(failure);
            }
        }
    }

    public ContentResult Index()
    {
        string msg;
        try
        {
             msg = $"Key1: {_config.Value.Key1} \n" +
                   $"Key2: {_config.Value.Key2} \n" +
                   $"Key3: {_config.Value.Key3}";
        }
        catch (OptionsValidationException optValEx)
        {
            return Content(optValEx.Message);
        }
        return Content(msg);
    }

Le code suivant applique une règle de validation plus complexe à l’aide d’un délégué :

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions<MyConfigOptions>()
        .Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
        .ValidateDataAnnotations()
        .Validate(config =>
        {
            if (config.Key2 != 0)
            {
                return config.Key3 > config.Key2;
            }

            return true;
        }, "Key3 must be > than Key2.");   // Failure message.

    services.AddControllersWithViews();
}

IValidateOptions pour la validation complexe

La classe suivante implémente IValidateOptions<TOptions> :

public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
    public MyConfigOptions _config { get; private set; }

    public  MyConfigValidation(IConfiguration config)
    {
        _config = config.GetSection(MyConfigOptions.MyConfig)
            .Get<MyConfigOptions>();
    }

    public ValidateOptionsResult Validate(string name, MyConfigOptions options)
    {
        string vor=null;
        var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
        var match = rx.Match(options.Key1);

        if (string.IsNullOrEmpty(match.Value))
        {
            vor = $"{options.Key1} doesn't match RegEx \n";
        }

        if ( options.Key2 < 0 || options.Key2 > 1000)
        {
            vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
        }

        if (_config.Key2 != default)
        {
            if(_config.Key3 <= _config.Key2)
            {
                vor +=  "Key3 must be > than Key2.";
            }
        }

        if (vor != null)
        {
            return ValidateOptionsResult.Fail(vor);
        }

        return ValidateOptionsResult.Success;
    }
}

IValidateOptions permet de déplacer le code de validation hors de StartUp et dans une classe.

À l’aide du code précédent, la validation est activée dans Startup.ConfigureServices avec le code suivant :

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<MyConfigOptions>(Configuration.GetSection(
                                        MyConfigOptions.MyConfig));
    services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
                              <MyConfigOptions>, MyConfigValidation>());
    services.AddControllersWithViews();
}

Options de post-configuration

Définissez la post-configuration avec IPostConfigureOptions<TOptions>. La post-configuration s’exécute après chaque configuration de IConfigureOptions<TOptions> :

services.PostConfigure<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

PostConfigure permet de post-configurer les options nommées :

services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Utilisez PostConfigureAll pour post-configurer toutes les instances de configuration :

services.PostConfigureAll<MyOptions>(myOptions =>
{
    myOptions.Option1 = "post_configured_option1_value";
});

Accès aux options au démarrage

IOptions<TOptions> et IOptionsMonitor<TOptions> peuvent être utilisées dans Startup.Configure, dans la mesure où les services sont générés avant que la méthode Configure ne s’exécute.

public void Configure(IApplicationBuilder app, 
    IOptionsMonitor<MyOptions> optionsAccessor)
{
    var option1 = optionsAccessor.CurrentValue.Option1;
}

N’utilisez pas IOptions<TOptions> ou IOptionsMonitor<TOptions> dans Startup.ConfigureServices. Un état d’options incohérent peut exister en raison de l’ordre des inscriptions de service.

Package NuGet Options.ConfigurationExtensions

Le package Microsoft.Extensions.Options.ConfigurationExtensions est implicitement référencé dans les applications ASP.NET Core.