- Home
- .NET tutorials
- Validating appsettings becomes much faster with .NET 8
Validating appsettings becomes much faster with .NET 8
Published: Tuesday 19 December 2023
.NET 8 has introduced a faster way to validate option values from the appsettings.json configuration.
Validating options prior to .NET 8
The appsettings.json file has the flexibility to add a number of different configuration options.
AgeRestriction section to appsettings.json with values for the MinimumAge and MaximumAge.
{
...,
"AgeRestriction": {
"MinimumAge": 18,
"MaximumAge": 44
}
}
These values have been added as properties to an AgeRestrictionOptions class. We've added data annotation attributes to each one for validation. Both of them are required and need to be within a range of 18-65.
// AgeRestrictionOptions.cs
public class AgeRestrictionOptions
{
[Required]
[Range(18, 65)]
public int MinimumAge { get; set; }
[Required]
[Range(18, 65)]
public int MaximumAge { get; set; }
}
To bind the appsettings.json values to the AgeRestrictionOptions class in an ASP.NET Core app, we need to add some addition configuration options to the Program.cs file.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
...
builder.Services.AddOptions<AgeRestrictionOptions>()
.BindConfiguration("AgeRestriction")
.ValidateDataAnnotations()
.ValidateOnStart();
...
var app = builder.Build();
...
The AddOptions method requires a generic type which is the class that we bind our appsettings value to. The BindConfiguration method specifies the section in the appsettings.json where we are getting the values from.
ValidateDataAnnotations method ensures that the options are validating when they are used. An additional ValidateOnStart method ensures that these options are validated before the application is started.
Taking a performance hit
It must be noted that using these methods to bind and validate configuration options with a .NET 8 project will work perfectly fine.
Validating options with a code-generated class
The first step is to create an options validator class. This needs to be marked as partial as there will be another partial class that will be code generated.
IValidateOptions interface, passing in the AgeRestrictionOptions class as it's generic type.
IValidateOptions interface is expecting a Validate method.
[OptionsValidator] attribute to the class, it resolves the compile issues.
// AgeRestrictionOptionsValidator.cs
[OptionsValidator]
public partial class AgeRestrictionOptionsValidator
: IValidateOptions<AgeRestrictionOptions>
{
}
Code generation
At this point when we build the application, it generates another partial AgeRestrictionOptionsValidator class.
Validate method which contains the code to validate options.
partial class AgeRestrictionOptionsValidator
{
/// <summary>
/// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>Validation result.</returns>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "8.0.9.3103")]
[System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
Justification = "The created ValidationContext object is used in a way that never call reflection")]
public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RoundTheCode.OptionsValidation.Options.AgeRestrictionOptions options)
{
...
}
}
Configuration in Program.cs
With the options validator setup, we need to make some additional changes to the configuration in Program.cs.
// Program.cs
using Microsoft.Extensions.Options;
using RoundTheCode.OptionsValidation.Options;
var builder = WebApplication.CreateBuilder(args);
...
/* This needs to be removed
builder.Services.AddOptions<AgeRestrictionOptions>()
.BindConfiguration("AgeRestriction")
.ValidateDataAnnotations()
.ValidateOnStart();
*/
/* Add this */
builder.Services.Configure<AgeRestrictionOptions>(
builder.Configuration.GetSection("AgeRestriction")
);
...
var app = builder.Build();
...
This will bind the values from appsettings.json to the AgeRestrictionOptions class, but it will not validate it.
AgeRestrictionOptionsValidator class with a singleton service lifetime to the IoC container.
// Program.cs
using Microsoft.Extensions.Options;
using RoundTheCode.OptionsValidation.Options;
var builder = WebApplication.CreateBuilder(args);
...
/* Add this */
builder.Services.Configure<AgeRestrictionOptions>(
builder.Configuration.GetSection("AgeRestriction")
);
builder.Services.AddSingleton<IValidateOptions<AgeRestrictionOptions>, AgeRestrictionOptionsValidator>();
...
var app = builder.Build();
...
This will now validate on runtime whenever the options are called.
What about validating on startup?
At present, there is no documented way of validating appsettings.json values through code generation in .NET 8.
Program.cs, we have access to the IServiceProvider instance. As a result, we can resolve instances that are contained in the IoC container.
// Program.cs
using Microsoft.Extensions.Options;
using RoundTheCode.OptionsValidation.Options;
var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build(); // This builds the application and we have access to the IServiceProvider instance
...
That means we can get a reference to the options and options validator before the application is run.
using Microsoft.Extensions.Options;
using RoundTheCode.OptionsValidation.Options;
var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build(); // This builds the application and we have access to the IServiceProvider instance
// Validate on startup
var ageRestrictionOptions = app.Services.GetRequiredService<IOptions<AgeRestrictionOptions>>();
var ageRestrictionOptionsValidator = app.Services.GetRequiredService<IValidateOptions<AgeRestrictionOptions>>();
...
app.Run();
When creating the options validator, it generated a partial class of the same name with a Validate method. This method requires two parameters:
- The
nameparameter specifies the property name to validate. This can be specified asNULLif we want to validate all properties. - The
optionsparameter is essentially the values from theappsettings.jsonfile stored in theAgeRestrictionOptionsclass.
As a result, we can call the Validate method before the application is running and it will throw an exception if it does not validate properly.
// Program.cs
using Microsoft.Extensions.Options;
using RoundTheCode.OptionsValidation.Options;
var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build(); // This builds the application and we have access to the IServiceProvider instance
// Validate on startup
var ageRestrictionOptions = app.Services.GetRequiredService<IOptions<AgeRestrictionOptions>>();
var ageRestrictionOptionsValidator = app.Services.GetRequiredService<IValidateOptions<AgeRestrictionOptions>>();
ageRestrictionOptionsValidator.Validate(null, ageRestriction.Value); // Validates before the application is run.
...
app.Run();
Watch the video
Watch our video where we implement the previous way of setting up validation on configuration values in an ASP.NET Core Web API and what changes we need to make to set it up using code generation.
Related pages
Data annotations has some awesome additions in .NET 8
Model validation for ASP.NET Core Web API now includes data annotations such as allowed and denied values as well as Base64 string validation.