In this article, we’ll look at how to use source generators to validate IOptions and ensure they meet the required configuration expectations.

To download the source code for this article, you can visit our GitHub repository.

Let’s dive in!

Why We Need to Validate IOptions

In ASP.NET Core we have ample choice when it comes to configuration providers. This gives us freedom when choosing a way to set our configuration data. But what happens if the configuration data isn’t properly set and doesn’t meet our requirements? This can lead to security problems, runtime failures, or unexpected behavior.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!
If you want to have a detailed overview of the Options Pattern, check out our article ASP.NET Core Configuration – Options Pattern.

When we validate the configuration data in our applications, we prevent misconfigurations. Moreover, by adding validation, we enforce constraints and ensure our applications operate as expected.  

In this article, we’ll opt for the options pattern:

public class NotificationOptions
{
    [Required]
    public string Sender { get; init; }
    [Required]
    public bool EnableSms { get; init; }
    [Required]
    public bool EnableEmail { get; init; }
    [Required]
    [Range(1, 10)]
    public int MaxNumberOfRetries { get; init; }
}

We create the NotificationOptions class and add several properties that we need for the proper handling of notifications. We also utilize attributes to decorate the properties, specifying different conditions they must adhere to.

Do you want to know how to validate the Options Pattern the old-fashioned way? Then check out our article ASP.NET Core Configuration – Options Validation.

Next, we add our settings:

"NotificationOptions": {
  "Sender": "Code-Maze",
  "EnableSms": false,
  "EnableEmail": true,
  "MaxNumberOfRetries": 3
}

Inside our appsettings.json file, we add the NotificationOptions section and its corresponding properties.

Let’s see how we can enforce validation by utilizing source generators!

How to Use Source Generators to Validate IOptions

With .NET 8, we can utilize source generators to create validators:

[OptionsValidator]
public partial class ValidateNotificationOptions : IValidateOptions<NotificationOptions>
{
}

We create the ValidateNotificationOptions class and then implement the IValidateOptions<TOptions> interface. The next step is to make the class  partial and decorate it with the OptionsValidator attribute. By using the attribute with an empty partial class that implements the IValidateOptions<TOptions> interface, we instruct the compiler to use source generators and create an implementation of that interface for us.

You are not familiar with source generators? Then check out our article Source Generators in C#.

Next, we register our options:

builder.Services.AddOptions<NotificationOptions>()
    .BindConfiguration(nameof(NotificationOptions));

In our Program class, we use the AddOptions() and BindConfiguration() methods. with the former, we register our NotificationOptions class, and with the latter we bind them to the corresponding section of the appsettings.json file.

We have one more step:

builder.Services.AddSingleton<IValidateOptions<NotificationOptions>, ValidateNotificationOptions>()

Here, we register our IValidateOptions<TOptions> interface implementation with a Singleton lifetime. Now, every time we request an IOptions<NotificationOptions> instance, the compiler will try to construct it and validate its properties based on the attributes we’ve used. If any values don’t adhere to the defined constraints an exception will be thrown at runtime.

IOptions Validation During Startup When Using Source Generators

Runtime exceptions can be very problematic and cause unwanted problems. Let’s see how to remedy them:

builder.Services.AddSingleton<IValidateOptions<NotificationOptions>, ValidateNotificationOptions>()
    .AddOptionsWithValidateOnStart<NotificationOptions>();

Again, in the Program class, we add the AddOptionsWithValidateOnStart<TOptions>() method to the call. This will validate our NotificationOptions class during application startup and prevent unwanted runtime errors. 

If you are familiar with the Options Pattern but don’t know how to mock it when writing tests, you should check our article How to Mock IOptions in ASP.NET Core.

This will still cause our program to crash if there are problems with our configuration data. However unpleasant this might be, it will ensure that we cannot ship our application until it is properly configured.

Conclusion

In this article we explored how using source generators to validate IOptions is essential for maintaining application integrity and security. By embracing validation during application startup, we proactively address configuration issues, ensuring we only deploy properly configured applications. While this may introduce initial runtime exceptions, it’s a necessary step to guarantee proper configuration before deployment. Overall, robust validation practices are essential for maintaining the integrity and effectiveness of our applications.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!