Cas d'utilisation de la configuration dans ASP.NET Core

Pour obtenir la configuration de l'application, il est courant d'utiliser la méthode d'accès par mot-clé (clé-valeur). Mais ce n'est pas toujours pratique. Parfois, vous devez utiliser des objets prêts à l'emploi dans votre code avec des valeurs déjà définies et avec la possibilité de mettre à jour les valeurs sans redémarrer l'application. Cet exemple fournit un modèle pour l'utilisation de la configuration comme middleware pour les applications ASP.NET Core.



Nous vous recommandons de vous familiariser avec le matériel: Metanit - Configuration , Fonctionnement de la configuration dans .NET Core .



Formulation du problème



Vous devez implémenter une application ASP NET Core avec la possibilité de mettre à jour la configuration au format JSON au moment de l'exécution. Lors d'une mise à jour de configuration, les sessions en cours d'exécution doivent continuer à fonctionner avec les options de configuration précédentes. Après la mise à jour de la configuration, les objets utilisés doivent être mis à jour / remplacés par de nouveaux.



La configuration doit être désérialisée, il ne doit y avoir aucun accès direct aux objets IConfiguration depuis les contrôleurs. L'exactitude des valeurs lues doit être vérifiée, si elles sont absentes, elles doivent être remplacées par des valeurs par défaut. L'implémentation doit fonctionner dans un conteneur Docker.



Travail de configuration classique



GitHub: ConfigurationTemplate_1



Le projet est basé sur le modèle ASP NET Core MVC. Le fournisseur de configuration JsonConfigurationProvider est utilisé pour travailler avec les fichiers de configuration JSON . Pour ajouter la possibilité de recharger la configuration de l'application pendant le fonctionnement, ajoutez le paramètre: "reloadOnChange: true".



Dans le fichier Startup.cs, remplacez:



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


Sur



public Startup(IConfiguration configuration)
 {         
   var builder = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
   configuration = builder.Build();
   Configuration = configuration;
  }


.AddJsonFile - ajoute un fichier JSON, reloadOnChange: true indique que lorsque les paramètres du fichier de configuration sont modifiés, ils seront rechargés sans qu'il soit nécessaire de recharger l'application.



Contenu du fichier appsettings.json :



{
  "AppSettings": {
    "Parameter1": "Parameter1 ABC",
    "Parameter2": "Parameter2 ABC"  
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}


Les contrôleurs d'application utiliseront le service: ServiceABC au lieu d'accéder directement à la configuration. ServiceABC est une classe qui prend les valeurs initiales du fichier de configuration. Dans cet exemple, la classe ServiceABC contient une seule propriété Title . Contenu du



fichier ServiceABC.cs :



public class ServiceABC
{
  public string Title;
  public ServiceABC(string title)
  {
     Title = title;
  }
  public ServiceABC()
  { }
}


Pour utiliser ServiceABC, vous devez l'ajouter en tant que service middleware à votre application. Ajoutez le service en tant que AddTransient, qui est créé à chaque fois que vous y accédez, à l'aide de l'expression:
services.AddTransient<IYourService>(o => new YourService(param));
Idéal pour les services légers qui ne consomment ni mémoire ni ressources. La lecture des paramètres de configuration dans Startup.cs est effectuée à l'aide de IConfiguration , qui utilise une chaîne de requête spécifiant le chemin complet de l'emplacement de la valeur, exemple: AppSettings: Parameter1.



Dans le fichier Startup.cs, ajoutez:



public void ConfigureServices(IServiceCollection services)
{
  //  "Parameter1"    ServiceABC
  var settingsParameter1 = Configuration["AppSettings:Parameter1"];
  //  "Parameter1"            
  services.AddScoped(s=> new ServiceABC(settingsParameter1));
  //next
  services.AddControllersWithViews();
}


Un exemple d'utilisation du service ServiceABC dans un contrôleur, la valeur Parameter1 sera affichée sur la page html.



Pour utiliser le service dans les contrôleurs, ajoutez-le au constructeur, fichier HomeController.cs



public class HomeController : Controller
{
  private readonly ILogger<HomeController> _logger;
  private readonly ServiceABC _serviceABC;
  public HomeController(ILogger<HomeController> logger, ServiceABC serviceABC)
    {
      _logger = logger;
      _serviceABC = serviceABC;
    }
  public IActionResult Index()
    {
      return View(_serviceABC);
    }


Ajouter le fichier ServiceABC de visibilité du service _ViewImports.cshtml



@using ConfigurationTemplate_1.Services


Modifions Index.cshtml pour afficher le paramètre Paramètre1 sur la page.



@model ServiceABC
@{
    ViewData["Title"] = "Home Page";
}
    <div class="text-center">
        <h1>   ASP.NET Core</h1>
        <h4>   </h4>
    </div>
<div>        
    <p> ServiceABC,  
          Parameter1 = @Model.Title</p>
</div>


Commençons l'application:







Résultat



Cette approche résout partiellement le problème. Cette solution ne permet pas d'appliquer les modifications de configuration pendant l'exécution de l'application. le service reçoit la valeur du fichier de configuration uniquement au démarrage, puis ne fonctionne qu'avec cette instance. Par conséquent, les modifications ultérieures du fichier de configuration n'entraîneront pas de modifications dans l'application.



Utilisation d'IConfiguration comme Singleton



GitHub: ConfigurationTemplate_2 La



deuxième option est de mettre IConfiguration (en tant que Singleton) dans les services. Par conséquent, IConfiguration peut être appelée à partir de contrôleurs et d'autres services. Lors de l'utilisation d'AddSingleton, le service est créé une fois et lors de l'utilisation de l'application, l'appel est dirigé vers la même instance. Utilisez cette méthode avec une extrême prudence, car des fuites de mémoire et des problèmes de multithreading peuvent survenir.



Remplaçons le code de l'exemple précédent dans Startup.cs par un nouveau, où

services.AddSingleton<IConfiguration>(Configuration);
ajoute IConfiguration en tant que Singleton aux services.



public void ConfigureServices(IServiceCollection services)
{
  //  IConfiguration     
  services.AddSingleton<IConfiguration>(Configuration);
  //  "ServiceABC"                          
  services.AddScoped<ServiceABC>();
  //next
  services.AddControllersWithViews();
}


Changer le constructeur du service ServiceABC pour accepter l'IConfiguration



public class ServiceABC
{        
  private readonly IConfiguration _configuration;
  public string Title => _configuration["AppSettings:Parameter1"];        
  public ServiceABC(IConfiguration Configuration)
    {
      _configuration = Configuration;
    }
  public ServiceABC()
    { }
}


Comme dans la version précédente, ajoutez le service au constructeur et ajoutez un lien vers l'espace de noms
, HomeController.cs



public class HomeController : Controller
{
  private readonly ILogger<HomeController> _logger;
  private readonly ServiceABC _serviceABC;
  public HomeController(ILogger<HomeController> logger, ServiceABC serviceABC)
    {
      _logger = logger;
      _serviceABC = serviceABC;
    }
  public IActionResult Index()
    {
      return View(_serviceABC);
    }


ServiceABC _ViewImports.cshtml:



@using ConfigurationTemplate_2.Services;


Index.cshtml Parameter1 .



@model ServiceABC
@{
    ViewData["Title"] = "Home Page";
}
<div class="text-center">
    <h1>   ASP.NET Core</h1>
    <h4> IConfiguration  Singleton</h4>
</div>
<div>
    <p>
         ServiceABC,  
          Parameter1 = @Model.Title
    </p>
</div>




Lançons l' application:







Le service ServiceABC ajouté au conteneur à l'aide d'AddScoped signifie qu'une instance de la classe sera créée à chaque demande de page. En conséquence, une instance de la classe ServiceABC sera créée à chaque requête http avec le rechargement de la configuration IConfiguration , et de nouvelles modifications dans appsettings.json seront appliquées.

Ainsi, si pendant le fonctionnement de l'application, changez le paramètre Paramètre1 sur «NOUVEAU !!! Paramètre1 ABC ”, la prochaine fois que vous accéderez à la page de démarrage, la nouvelle valeur du paramètre sera affichée.



Rafraîchissons la page après avoir modifié le fichier appsettings.json :







Résultat



L'inconvénient de cette approche est la lecture manuelle de chaque paramètre. Et si vous ajoutez une validation de paramètre, la vérification ne sera pas effectuée après la modification du fichier appsettings.json, mais à chaque fois que vous utilisez ServiceABC , ce qui est une action inutile. Dans le meilleur des cas, les paramètres ne doivent être validés qu'une seule fois après chaque modification de fichier.



Désérialisation de la configuration avec validation (option IOptions)



GitHub: ConfigurationTemplate_3

En savoir plus sur les options ici .



Cette option élimine le besoin d'utiliser ServiceABC . Au lieu de cela, la classe AppSettings est utilisée , qui contient les paramètres du fichier de configuration et de l'objet ClientConfig . L'objet ClientConfig doit être initialisé après la modification de la configuration, car un objet prêt à l'emploi est utilisé dans les contrôleurs.

ClientConfig est une classe qui interagit avec des systèmes externes, dont le code ne peut pas être modifié. Si vous désérialisez uniquement les données de la classe AppSettings , ClientConfigsera nul. Par conséquent, il est nécessaire de s'abonner à l'événement de configuration de lecture et d'initialiser l'objet ClientConfig dans le gestionnaire .



Pour transférer la configuration non pas sous forme de paires clé-valeur, mais en tant qu'objets de certaines classes, nous utiliserons l'interface IOptions . En outre, IOptions, contrairement à ConfigurationManager, vous permet de désérialiser des sections individuelles. Pour créer l'objet ClientConfig , vous devrez utiliser IPostConfigureOptions , qui est exécuté une fois que toute la configuration a été traitée. IPostConfigureOptions sera exécuté à chaque fois que la configuration est lue, le plus récemment.



Créons ClientConfig.cs :



public class ClientConfig
{
  private string _parameter1;
  private string _parameter2;
  public string Value => _parameter1 + " " + _parameter2;
  public ClientConfig(ClientConfigOptions configOptions)
    {
      _parameter1 = configOptions.Parameter1;
      _parameter2 = configOptions.Parameter2;
    }
}


Il prendra des paramètres en tant que constructeur sous la forme d'un objet ClientConfigOptions :



public class ClientConfigOptions
{
  public string Parameter1;
  public string Parameter2;
} 


Créons la classe de paramètres AppSettings et définissons la méthode ClientConfigBuild () qu'elle contient , qui créera l'objet ClientConfig .



Fichier AppSettings.cs :



public class AppSettings
{        
  public string Parameter1 { get; set; }
  public string Parameter2 { get; set; }        
  public ClientConfig clientConfig;
  public void ClientConfigBuild()
    {
      clientConfig = new ClientConfig(new ClientConfigOptions()
        {
          Parameter1 = this.Parameter1,
          Parameter2 = this.Parameter2
        }
        );
      }
}


Créons un gestionnaire de configuration qui sera traité en dernier. Pour ce faire, il doit être hérité de IPostConfigureOptions . Le dernier PostConfigure appelé exécutera ClientConfigBuild () , qui créera ClientConfig .



Fichier ConfigureAppSettingsOptions.cs :



public class ConfigureAppSettingsOptions: IPostConfigureOptions<AppSettings>
{
  public ConfigureAppSettingsOptions()
    { }
  public void PostConfigure(string name, AppSettings options)
    {            
      options.ClientConfigBuild();
    }
}


Il ne reste plus qu'à apporter des modifications dans Startup.cs , les modifications n'affecteront que la fonction ConfigureServices (services IServiceCollection) .



Tout d'abord, lisons la section AppSettings dans appsettings.json



// configure strongly typed settings objects
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);


De plus, pour chaque demande, une copie d' AppSettings sera créée pour la possibilité d'appeler le post-traitement:



services.AddScoped(sp => sp.GetService<IOptionsSnapshot<AppSettings>>().Value);


Ajoutons un post-traitement de la classe AppSettings en tant que service:



services.AddSingleton<IPostConfigureOptions<AppSettings>, ConfigureAppSettingsOptions>();


Code ajouté à Startup.cs



public void ConfigureServices(IServiceCollection services)
{
  // configure strongly typed settings objects
  var appSettingsSection = Configuration.GetSection("AppSettings");
  services.Configure<AppSettings>(appSettingsSection);
  services.AddScoped(sp => sp.GetService<IOptionsSnapshot<AppSettings>>().Value);                                    
  services.AddSingleton<IPostConfigureOptions<AppSettings>, ConfigureAppSettingsOptions>();            
  //next
  services.AddControllersWithViews();
}


Pour accéder à la configuration, il suffira d'injecter simplement AppSettings depuis le contrôleur .



Fichier HomeController.cs :



public class HomeController : Controller
{
  private readonly ILogger<HomeController> _logger;
  private readonly AppSettings _appSettings;
  public HomeController(ILogger<HomeController> logger, AppSettings appSettings)
    {
      _logger = logger;
      _appSettings = appSettings;
    }


Changement Let Index.cshtml pour afficher la valeur paramètre du lientConfig objet



@model AppSettings
@{
    ViewData["Title"] = "Home Page";
}
<div class="text-center">
    <h1>   ASP.NET Core</h1>
    <h4>    ( IOptions)</h4>
</div>
<div>
    <p>
         ClientConfig,  
         = @Model.clientConfig.Value
    </p>
</div>










Lançons l'application: Si pendant le fonctionnement de l'application, changez le paramètre Parameter1 en “NEW !!! Paramètre1 ABC "et Paramètre2 sur" NOUVEAU !!! Paramètre2 ABC ", la prochaine fois que vous accéderez à la page initiale, la nouvelle propriété Value s'affichera :







Résultat



Cette approche vous permet de désérialiser toutes les valeurs de configuration sans itérer manuellement sur les paramètres. Chaque requête http fonctionne avec sa propre instance d'AppSettings et de lientConfig, ce qui élimine la situation de collisions. IPostConfigureOptions garantit qu'il est exécuté en dernier lorsque toutes les options ont été relues. L'inconvénient de cette solution est la création constante d'une instance de ClientConfig pour chaque requête, ce qui n'est pas pratique car en fait, ClientConfig ne doit être recréé qu'après les modifications de configuration.



Désérialisation de la configuration avec validation (sans utiliser IOptions)



GitHub: L' approche d'utilisation de ConfigurationTemplate_4



utilisant IPostConfigureOptions conduit à la création de l'objet ClientConfig à chaque fois que vous recevez une requête du client. Ce n'est pas assez rationnel car chaque demande fonctionne avec un état ClientConfig initial, qui change uniquement lorsque le fichier de configuration appsettings.json est modifié. Pour ce faire, nous abandonnerons IPostConfigureOptions et créerons un gestionnaire de configuration qui ne sera appelé que lorsque appsettings.json changera, par conséquent ClientConfig ne sera créé qu'une seule fois, puis l'instance ClientConfig déjà créée sera donnée pour chaque demande.



Créer une classe SingletonAppSettingsconfiguration (Singleton) à partir de laquelle une instance de paramètres sera créée pour chaque requête.



Fichier SingletonAppSettings.cs :



public class SingletonAppSettings
{
  public AppSettings appSettings;  
  private static readonly Lazy<SingletonAppSettings> lazy = new Lazy<SingletonAppSettings>(() => new SingletonAppSettings());
  private SingletonAppSettings()
    { }
  public static SingletonAppSettings Instance => lazy.Value;
}


Revenons à la classe Startup et ajoutons une référence à l'interface IServiceCollection .

Il sera utilisé dans la méthode de gestion de la configuration



public IServiceCollection Services { get; set; }


Let changement de ConfigureServices (services IServiceCollection) et passer une référence à IServiceCollection .



Fichier Startup.cs :



public void ConfigureServices(IServiceCollection services)
{
  Services = services;
  //  AppSettings  
  var appSettings = Configuration.GetSection("AppSettings").Get<AppSettings>();
  appSettings.ClientConfigBuild();


Créons une configuration Singleton et ajoutons-la à la collection de services:



SingletonAppSettings singletonAppSettings = SingletonAppSettings.Instance;
singletonAppSettings.appSettings = appSettings;
services.AddSingleton(singletonAppSettings);     


Ajoutons l'objet AppSettings en tant que Scoped, avec chaque demande, une copie du Singleton sera créée:



services.AddScoped(sp => sp.GetService<SingletonAppSettings>().appSettings);


Configurez complètement les services (services IServiceCollection) :



public void ConfigureServices(IServiceCollection services)
{
  Services = services;
  //  AppSettings  
  var appSettings = Configuration.GetSection("AppSettings").Get<AppSettings>();
  appSettings.ClientConfigBuild();
  SingletonAppSettings singletonAppSettings = SingletonAppSettings.Instance;
  singletonAppSettings.appSettings = appSettings;
  services.AddSingleton(singletonAppSettings);             
  services.AddScoped(sp => sp.GetService<SingletonAppSettings>().appSettings);
  //next
  services.AddControllersWithViews();
}


Ajoutez maintenant un gestionnaire pour la configuration dans Configure (application IApplicationBuilder, IWebHostEnvironment env) . Un jeton est utilisé pour suivre la modification dans le fichier appsettings.json. OnChange est la fonction à appeler lorsque le fichier change. Gestionnaire de configuration OnChange () :



ChangeToken.OnChange(() => Configuration.GetReloadToken(), onChange);


Tout d'abord, nous lisons le fichier appsettings.json et désérialisons la classe AppSettings . Ensuite, à partir de la collection de services, nous obtenons une référence au Singleton qui stocke l'objet AppSettings et le remplaçons par un nouveau.



private void onChange()
{                        
  var newAppSettings = Configuration.GetSection("AppSettings").Get<AppSettings>();
  newAppSettings.ClientConfigBuild();
  var serviceAppSettings = Services.BuildServiceProvider().GetService<SingletonAppSettings>();
  serviceAppSettings.appSettings = newAppSettings;
  Console.WriteLine($"AppSettings has been changed! {DateTime.Now}");
}


Dans le HomeController, nous allons injecter un lien vers AppSettings, comme dans la version précédente (ConfigurationTemplate_3)
HomeController.cs:



public class HomeController : Controller
{
  private readonly ILogger<HomeController> _logger;
  private readonly AppSettings _appSettings;
  public HomeController(ILogger<HomeController> logger, AppSettings appSettings)
    {
      _logger = logger;
      _appSettings = appSettings;
    }


Index.cshtml Value lientConfig:



@model AppSettings
@{
    ViewData["Title"] = "Home Page";
}
<div class="text-center">
    <h1>   ASP.NET Core</h1>
    <h4>    (  IOptions)</h4>
</div>
<div>
    <p>
         ClientConfig,  
        = @Model.clientConfig.Value
    </p>
</div>




Lançons







l' application: Après avoir sélectionné le mode de lancement en tant qu'application console, dans la fenêtre de l'application, vous pouvez voir un message sur le déclenchement de l'événement de changement de fichier de configuration:







Et les nouvelles valeurs:







Résultat



Cette option est meilleure que d'utiliser IPostConfigureOptions car vous permet de créer un objet uniquement après avoir modifié le fichier de configuration, et non à chaque demande. Le résultat est une réduction du temps de réponse du serveur. Une fois le jeton déclenché, l'état du jeton est réinitialisé.



Ajout de valeurs par défaut et validation de la configuration



GitHub: ConfigurationTemplate_5



Dans les exemples précédents, si le fichier appsettings.json est manquant, l'application lèvera une exception, donc rendons le fichier de configuration facultatif et ajoutons les paramètres par défaut. Lors de la publication d'une application de projet créée à partir d'un modèle dans Visula Studio, le fichier appsettings.json sera situé dans le même dossier avec tous les binaires, ce qui n'est pas pratique lors du déploiement sur Docker. Le fichier appsettings.json a été déplacé vers le config / :



.AddJsonFile("config/appsettings.json")


Pour pouvoir lancer l'application sans appsettings.json, définissez le paramètre optiona l sur true , ce qui dans ce cas signifie que la présence de appsettings.json est facultative.



Fichier Startup.cs :



public Startup(IConfiguration configuration)
{
  var builder = new ConfigurationBuilder()
     .AddJsonFile("config/appsettings.json", optional: true, reloadOnChange: true);
  configuration = builder.Build();
  Configuration = configuration;
}


Ajoutez à Public Void ConfigureServices (services IServiceCollection) à la ligne de désérialisation de configuration le cas de la gestion de l'absence du fichier appsettings.json:



 var appSettings = Configuration.GetSection("AppSettings").Get<AppSettings>() ?? new AppSettings();


Ajoutons la validation de la configuration basée sur l'interface IValidatableObject . Si les paramètres de configuration sont manquants, la valeur par défaut sera utilisée. Héritons de la



classe AppSettings de IValidatableObject et implémentons la méthode:



public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)


Fichier AppSettings.cs :



public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
  List<ValidationResult> errors = new List<ValidationResult>();
  if (string.IsNullOrWhiteSpace(this.Parameter1))
    {
      errors.Add(new ValidationResult("   Parameter1.  " +
        "   DefaultParameter1 ABC"));
      this.Parameter1 = "DefaultParameter1 ABC";
    }
    if (string.IsNullOrWhiteSpace(this.Parameter2))
    {
      errors.Add(new ValidationResult("   Parameter2.  " +
        "   DefaultParameter2 ABC"));
      this.Parameter2 = "DefaultParameter2 ABC";
    }
    return errors;
}


Ajoutez une méthode pour appeler la vérification de configuration à appeler à partir du fichier Startup.cs de la classe de démarrage :





private void ValidateAppSettings(AppSettings appSettings)
{
  var resultsValidation = new List<ValidationResult>();
  var context = new ValidationContext(appSettings);
  if (!Validator.TryValidateObject(appSettings, context, resultsValidation, true))
    {
      resultsValidation.ForEach(
        error => Console.WriteLine($" : {error.ErrorMessage}"));
      }
    }


Ajoutons un appel à la méthode de validation de configuration dans ConfigureServices (services IServiceCollection). S'il n'y a pas de fichier appsettings.json, vous devez initialiser l'objet AppSettings avec les valeurs par défaut.



Fichier Startup.cs :



var appSettings = Configuration.GetSection("AppSettings").Get<AppSettings>() ?? new AppSettings();


Vérification des paramètres. Si la valeur par défaut est utilisée, un message indiquant le paramètre sera affiché dans la console.



 //Validate            
this.ValidateAppSettings(appSettings);            
appSettings.ClientConfigBuild();


Modifions la vérification de la configuration dans onChange ()



private void onChange()
{                        
  var newAppSettings = Configuration.GetSection("AppSettings").Get<AppSettings>() ?? new AppSettings();
  //Validate            
  this.ValidateAppSettings(newAppSettings);            
  newAppSettings.ClientConfigBuild();
  var serviceAppSettings = Services.BuildServiceProvider().GetService<SingletonAppSettings>();
  serviceAppSettings.appSettings = newAppSettings;
  Console.WriteLine($"AppSettings has been changed! {DateTime.Now}");
}


Si vous supprimez la clé Parameter1 du fichier appsettings.json , après avoir enregistré le fichier, un message concernant l'absence du paramètre apparaîtra dans la fenêtre de l'application de la console:







Résultat



Changer le chemin d'accès à l'emplacement des configurations dans le dossier config est une bonne solution. vous permet de ne pas mélanger tous les fichiers en un seul tas. Le dossier de configuration est défini uniquement pour le stockage des fichiers de configuration. Simplification de la tâche de déploiement et de configuration de l'application pour les administrateurs grâce à la validation de la configuration. Si vous ajoutez la sortie des erreurs de configuration au journal, l'administrateur, si des paramètres incorrects sont spécifiés, recevra des informations précises sur le problème, et non pas comme les programmeurs ont récemment commencé à écrire dans une exception: «Une erreur s'est produite» .



Il n'y a pas d'option idéale pour travailler avec la configuration, tout dépend de la tâche à accomplir, chaque option a ses avantages et ses inconvénients.



Tous les modèles de configuration sont disponibles ici .



Littérature:



  1. Corriger ASP.NET Core
  2. METANIT - Configuration. Principes de base de la configuration
  3. Singleton Design Pattern C # .net core
  4. Rechargement de la configuration dans .NET Core
  5. Rechargement des options fortement typées sur les modifications de fichiers dans ASP.NET Core RC2
  6. Configuration d'application ASP.NET Core via IOptions
  7. METANIT - Passage de la configuration via IOptions
  8. Configuration d'application ASP.NET Core via IOptions
  9. METANIT - Auto-validation du modèle



All Articles