Eager validation – Options, Settings, and Configuration

Eager validation has been added to .NET 6 and allows catching incorrectly configured options at startup time in a fail-fast mindset.The Microsoft.Extensions.Hosting assembly adds the ValidateOnStart extension method to the OptionsBuilder<TOptions> type.There are different ways of using this, including the following, which binds a configuration section to an options class:

services.AddOptions<Options>()
    .Configure(o => /* Omitted configuration code */)
    .ValidateOnStart()
;

The highlighted line is all we need to apply our validation rules during startup. I recommend using this as your new default so you know that options are misconfigured at startup time instead of later at runtime, limiting unexpected issues.Now that we know that, let’s look at how to configure options validation.

Data annotations

Let’s start by using System.ComponentModel.DataAnnotations types to decorate our options with validation attributes. We activate this feature with the ValidateDataAnnotations extension method. This also works with eager validation by chaining both methods.

If you are unfamiliar with DataAnnotations, they are attributes used to validate EF Core and MVC model classes. Don’t worry, they are very explicit, so you should understand the code.

To demonstrate this, let’s look at the skeleton of two small tests:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.ComponentModel.DataAnnotations;
using Xunit;
namespace OptionsValidation;
public class ValidateOptionsWithDataAnnotations
{
    [Fact]
    public void Should_pass_validation() { /*omitted*/ }
    [Fact]
    public void Should_fail_validation() { /*omitted*/ }
    private class Options
    {
        [Required]
        public string?
MyImportantProperty { get; set; }
    }
}

The preceding code shows that the MyImportantProperty property of the Options class is required and cannot be null (highlighted line). Next, we look at the test cases.The first test is expecting the validation to pass:

[Fact]
public void Should_pass_validation()
{
    // Arrange
    var services = new ServiceCollection();
    services.AddOptions<Options>()
        .Configure(o => o.MyImportantProperty = “A value”)
        .ValidateDataAnnotations()
        .ValidateOnStart() // eager validation
    ;
    var serviceProvider = services.BuildServiceProvider();
    var options = serviceProvider
        .GetRequiredService<IOptionsMonitor<Options>>();
    // Act & Assert
    Assert.Equal(
        “Some important value”,
        options.CurrentValue.MyImportantProperty
    );
}

The test simulates the execution of a program where the IoC container creates the options class, and its consumer (the test) leverages it. The highlighted line sets the property to “A value”, making the validation pass. The code also enables eager validation (ValidateOnStart) on top of the validation of data annotations (ValidateDataAnnotations).The second test is expecting the validation to fail:

[Fact]
public void Should_fail_validation()
{
    // Arrange
    var services = new ServiceCollection();
    services.AddOptions<Options>()
        .ValidateDataAnnotations()
        .ValidateOnStart() // eager validation
    ;
    var serviceProvider = services.BuildServiceProvider();
        // Act & Assert
        var error = Assert.Throws<OptionsValidationException>(
            () => options.CurrentValue);
        Assert.Collection(error.Failures,
            f => Assert.Equal(“DataAnnotation validation failed for ‘Options’ members: ‘MyImportantProperty’ with the error: ‘The MyImportantProperty field is required.’.”, f)
        );
    );
}

In the preceding code, the MyImportantProperty is never set (highlighted code), leading to the validation failing and throwing an OptionsValidationException. The test simulates catching that exception.

The eager validation does not work in the tests because it is not an ASP.NET Core program but xUnit test cases (Fascts).

That’s it—.NET does the job for us and validates our instance of the Options class using the data annotation like you can do when using EF Core or MVC model.Next, we explore how to create validation classes to validate our options objects manually.

Leave a Reply

Your email address will not be published. Required fields are marked *



         


          Terms of Use | Accessibility Privacy