- 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.
These values can either be validated on startup or through runtime.
C# coding challenges
We'll explain what's changed, why it's faster and how to set it up.
Validating options prior to .NET 8
The appsettings.json
file has the flexibility to add a number of different configuration options.
In this example, we have added a 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.
There are also two validate methods that we use. The 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.
But the validation does take a performance hit as it uses reflection. Depending on how complex the validation is will determine how much of a performance hit an application will take.
There is a different way of doing this with .NET 8. It involves creating a new partial class which will generate code needed for the validation.
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.
In-addition, the class needs to be implemented with the IValidateOptions
interface, passing in the AgeRestrictionOptions
class as it's generic type.
At this stage, the code will not compile as the IValidateOptions
interface is expecting a Validate
method.
However, by adding the [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.
This includes a 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
.
First, the original configuration needs to be removed and replaced with the following:
// 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.
In-order to validate it, we need to add the 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.
However, there is a workaround.
When the web application is built in 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
name
parameter specifies the property name to validate. This can be specified asNULL
if we want to validate all properties. - The
options
parameter is essentially the values from theappsettings.json
file stored in theAgeRestrictionOptions
class.
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.