In this week's newsletter I will show you how to easily add validation to the strongly typed configuration objects injected with IOptions
.
The Options pattern allows us to use classes to provide strongly typed configuration values in our application at runtime.
But you have no guarantee that the configuration values injected with IOptions
will be correctly read from the application settings.
Let's see how we can introduce validation for IOptions
and make sure the application settings are correct.
Strongly Typed Configuration
I first want to define a simple class that will represent our strongly typed configuration. Let's say we want to integrate with the GitHub API, so we create a GitHubSettings
class to hold our configuration:
public class GitHubSettings
{
public string AccessToken { get; init; }
public string RepositoryName { get; init; }
}
Inside of our appsettings.json
file we need to create a section to hold our configuration values:
"GitHubSettings": {
"AccessToken": "access-token-value",
"RepositoryName": "youtube-projects"
}
And with this in place, we can configure our GitHubSettings
:
builder.Services.Configure<GitHubSettings>(
builder.Configuration.GetSection("GitHubSettings"));
Finally, our GitHubSettings
is properly configured and we can inject it with IOptions<GitHubSettings>
.
What Could Go Wrong?
If we leave the implementation like this, we're moving the responsibility for providing the correct configuration values to the developer. I'm not saying we are the problem, but I've forgotten to add application settings a few times. I'm sure this happened to you also.
Here are just a few things that can go wrong:
- Passing an incorrect section name to
IConfiguration.GetSection
- Forgetting to add the settings values in
appsettings.json
- Typo in a property name in the class or in the configuration
- Unbindale properties without a setter
- Data type mismatch resulting in incompatible values
Depending on which one of these mistakes is made, the application will behave differently at runtime.
The best case scenario is that the incorrect application settings cause a runtime exception, and you realize you have a problem and fix it.
The worst case scenario, and this happens more often than you may think, is that the application silently fails. The application settings aren't correctly set on the value provided by IOptions
, but you don't get a runtime exception. The problem may go undetected for some time.
How do we solve this?
Validation For The Options Pattern
There is a simple way to introduce validation to the settings class using data annotations. We just add the validation attributes that we need to the properties of the settings class.
For example, we can add the Required
attribute to the GitHubSettings
properties:
public class GitHubSettings
{
[Required]
public string AccessToken { get; init; }
[Required]
public string RepositoryName { get; init; }
}
We have to slightly change how we configure the GitHubSettings
:
builder.Services
.AddOptions<GitHubSettings>()
.BindConfiguration("GitHubSettings")
.ValidateDataAnnotations();
A few things to note here:
-
AddOptions
- returns anOptionsBuilder<TOptions>
that binds to theGitHubSettings
class -
BindConfiguration
- binds the values from the configuration section -
ValidateDataAnnotations
- enables validation using data annotations
With this in place, if we try to inject GitHubSettings
with any of the properties missing a value, we will get a runtime exception.
You can also define a custom delegate for the validation logic, instead of using data annotations:
builder.Services
.AddOptions<GitHubSettings>()
.BindConfiguration("GitHubSettings")
.Validate(gitHubSettings =>
{
if (string.IsNullOrEmpty(gitHubSettings.AccessToken))
{
return false;
}
return true;
});
Running Validation At Application Start
It would be great if we could run validation on the configuration values when our application is starting, instead of at runtime.
We can do that by calling ValidateOnStart
method when configuring our settings class:
builder.Services
.AddOptions<GitHubSettings>()
.BindConfiguration("GitHubSettings")
.ValidateDataAnnotations()
.ValidateOnStart(); // 👈 the magic happens here
When we start the application, the validation will run on GitHubSettings
and an exception is thrown if validation fails. The validation exception will look something like this:
Unhandled exception. Microsoft.Extensions.Options.OptionsValidationException:
DataAnnotation validation failed for 'GitHubSettings' members:
This shortens the feedback loop, and you will know right away that you have a problem. This is much better than finding out that you have a problem at runtime, like in the previous examples.
Closing Thoughts
The Options pattern is very flexible and allows us to use strongly typed settings in ASP.NET Core.
If you want to see how to implement the Options pattern, I made a video about it where I go into the details.I covered the differences between IOptions
, IOptionsSnapshot
and IOptionsMonitor
.
And now you know how to use the ValidateOnStart
method, which was introduced in .NET 6, to validate your application settings on app start up. This allows you to learn about configuration issues as soon as possible, instead of at runtime.
I also made a video showing how to add validation to the Option pattern.
P.S. Whenever you're ready, there are 2 ways I can help you:
Pragmatic Clean Architecture: This comprehensive course will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture. Join 950+ students here.
Patreon Community: Think like a senior software engineer with access to the source code I use in my YouTube videos and exclusive discounts for my courses. Join 820+ engineers here.