Introduction
To make sure your settings are correctly validated, .NET provides a few built-in resources. With the help of these tools, you can quickly verify the properties of the options without having to perform any tasks like:
var issuer = builder.Configuration.GetValue<string>("JwtConfigSettings:Issuer");
if (string.IsNullOrEmpty(issuer))
{
throw new ArgumentNullException(nameof(issuer));
}
Or
var issuer = builder.Configuration.GetValue<string>("JwtConfigSettings:Issuer");
ArgumentNullException.ThrowIfNull(nameof(issuer));
Let’s check with the options validations ways of doing it.
Data Annotations
Using data annotations is a simple approach to do that.
We use the attribute we used to mark the properties that we want to have validated.
public class JwtConfigSettings
{
public const string Key = "JwtConfigSettings";
[Required(ErrorMessage = "Issuer is required.")]
[Range(1, 20, ErrorMessage = "Issuer limit is exceeded.")]
public string Issuer { get; set; }
[Required(ErrorMessage = "Audience is required.")]
public string Audience { get; set; }
[Required(ErrorMessage = "Validate expiration is required.")]
public bool ValidateExpiration { get; set; }
[Required(ErrorMessage = "Sign-in key is required.")]
public string SignInKey { get; set; }
}
If you want to validate this, then we need to add the ValidateDataAnnotation extension method after the binding of the configuration.
builder.Services
.AddOptions<JwtConfigSettings>()
.Bind(builder.Configuration.GetSection(JwtConfigSettings.Key))
.ValidateDataAnnotations();
Every time the JwtConfigSettings properties are injected or utilized, this will validate them.
You can add the ValidateOnStart() function if you would prefer to validate it when the program starts, which makes more sense in most situations.
builder.Services
.AddOptions<JwtConfigSettings>()
.Bind(builder.Configuration.GetSection(JwtConfigSettings.Key))
.ValidateDataAnnotations()
.ValidateOnStart();;
If we want to check custom validations, then we need to use the Validate method:
builder.Services
.AddOptions<JwtConfigSettings>()
.Bind(builder.Configuration.GetSection(JwtConfigSettings.Key))
.ValidateDataAnnotations()
.Validate(option => option.Audience.Length <= 20, "Audience limit is exceeded.")
.ValidateOnStart();
Using the IValidateOptions<T> interface, we can also clean this up and assign the validation to the appropriate service. It helps us to simply use dependency injection and to decouple our validation from the configuration.
To do that, we must construct a new class, JwtConfigSettingsValidation, and require it to implement the IValidateOptions<JwtConfigSettings> interface. The Validate method, which allows you to return a ValidateOptionsResult, must then be implemented. If validation is unsuccessful, return Fail(message) or ValidateOptionsResult. if the validation is successful, success.
public class JwtConfigSettingsValidation : IValidateOptions<JwtConfigSettings>
{
private readonly ILogger<JwtConfigSettingsValidation> _logger;
public JwtConfigSettingsValidation(ILogger<JwtConfigSettingsValidation> logger)
{
_logger = logger;
}
public ValidateOptionsResult Validate(string name, JwtConfigSettings options)
{
_logger.LogInformation("Validating options");
if (options.Audience.Length >= 20)
{
return ValidateOptionsResult.Fail("Audience limit is exceeded.");
}
return ValidateOptionsResult.Success;
}
}
We need to register this implementation in the Program.cs file to validate the configuration.
builder.Services
.AddTransient<IValidateOptions<JwtConfigSettings>, JwtConfigSettingsValidation>();
I have added below configuration for the applications.json
{
"JwtConfigSettings": {
"Issuer": "",
"Audience": "Jaimin Jaimin Jaimin Jaimin Jaimin Jaimin Jaimin Jaimin",
"ValidateExpiration": false,
"SignInKey": "Jaimin sign in key"
}
}
Let’s run the application and validate the configuration.
We learned the new technique and evolved together.
Happy coding. :)