Dans un précédent article, je vous ai parlé d’une nouvelle méthode GetRequiredSection. Cette méthode s’assure qu’une section de configuration obligatoire soit bien présente et ajoute la possibilité d’avoir une exception dès le démarrage de votre application.
La prochaine étape serait maintenant de savoir si les propriétés obligatoires ont bien une valeur. Car comme pour les sections, le comportement actuel du framework est que si une propriété ne peut pas être mappée depuis votre configuration (appsettings.json, variable d’environnement,…) alors la valeur de votre propriété sera null et aucun avertissement ou message d’erreur ne sera levé. Le framework part du principe que cela peut être le comportement voulu. Effectivement vous pourriez laisser volontairement une valeur à null dans un environnement précis.
Cependant suite à plusieurs remarques, j’entends que certains aimeraient avoir dès le démarrage de leur application une erreur précise si une valeur n’est pas correctte dans le fichier de configuration. Dans le cas par exemple où une faute de frappe s’est glissé dans fichier de configuration ou variable d’environnement absente dans un environement précis, on aimerait avoir une erreur.
Je suis donc partis sur l’utilisation des DataAnnotations. dans mon sample, j’ai utilisé l’attribut Required. Mais vous pouvez utilisez toute la puissance de cette validation.
using System.ComponentModel.DataAnnotations;
namespace GetRequiredSectionSample.Configurations
{
public class SampleOptions
{
public const string ConfigurationName = "SampleSection";
[Required]
public string SampleProperty { get; set; }
}
}
Nous pourrons valider une instance de cet object avec le Validator.TryValidateObject
var options = new SampleOptions();
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(options, new ValidationContext(options), results, true);
Maintenant que nous pouvons valider un objet(T) que l’on va utiliser en tant que IOptions<T> pour mapper nos configurations. nous allons récupérer toutes les intances dans le conteneur IOC. Mais dans IServiceCollection services de la méthode ConfigureServices de votre Startup.cs, la propriété Value sur le IOptions<T> est toujours à null. Celui-ci étant évalué plus tard. Nous avons donc besoin de récupérer une instance dans IApplicationBuilder.ApplicationServices dans la méthode Configure du Startup.cs

Dans les grandes lignes, je regarde s’il y a dans IServiceCollection des services qui contiennent dans leur nom « IOptionsChangeTokenSource ». Ensuite pour chacun de ces services, je récupére le type de générique(T) en argument de IOptions<T>. Ce qui me permet de créer le type exact IOption<SampleOptions> pour ensuite demander de récupérer son instance dans IApplicationBuilder.ApplicationServices.GetService. J’utilise ici exceptionnellement dynamic car ce qui m’intéresse c’est l’objet SampleOptions qui est évalué dans la propriété Value. Il ne nous reste plus qu’a appeler le Validator.TryValidateObject et selon son retour lever une exception avec la liste des erreurs trouvée. Ce qui nous donne la méthode suivante.
private void CheckConfiguration(IApplicationBuilder app, IServiceCollection services)
{
var optionsServiceDescriptors = services.Where(s => s.ServiceType.Name.Contains("IOptionsChangeTokenSource"));
foreach (var service in optionsServiceDescriptors)
{
var genericTypes = service.ServiceType.GenericTypeArguments;
if (genericTypes.Length > 0)
{
var optionsType = genericTypes[0];
var genericOptions = typeof(IOptions<>).MakeGenericType(optionsType);
dynamic instance = app.ApplicationServices.GetService(genericOptions);
var options = instance.Value;
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(options, new ValidationContext(options), results, true);
if (!isValid)
{
var messages = new List<string> { "Configuration issues" };
messages.AddRange(results.Select(r => r.ErrorMessage));
throw new Exception(string.Join("\n", messages));
}
}
}
}
Pour que cela fonctionne, il faut utiliser la manière suivante pour register vos IOptions
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
_services = services;
// raise exception on startup
var configSection = Configuration.GetRequiredSection(SampleOptions.ConfigurationName);
services.Configure<SampleOptions>(configSection);
L’avantage de cette manière de valider votre configuration est que vous pouvez utiliser tous les attributs existant afin de valider vos parametres. Second avantage, vous avez une exception dès le démarrage de votre application avec un message précis des problèmes.

Un exemple est disponible sur mon github sous la branche add-check-configuration.
2 commentaires sur “Validation de vos configurations .Net Core”