Friday, July 3, 2026

.NET Options Validation in Application Startup

In this post, let's have a look at how we can validate options in application startup in a .NET application.

The Options pattern lets us bind a configuration section to a strongly-typed class. On top of that, we can validate the bound values so that a missing/incorrect configuration fails fast at application startup rather than blowing up at some random point at runtime when the options are first used. All of this lives in Microsoft.Extensions.Options, so it works the same in Console apps, Worker Services, ASP.NET Core and any other .NET host. 

Let's have a look at a simple example.

Say we have the following appsettings.json.
{
  "WeatherApi": {
    "BaseUrl": "Something",
    "TimeoutSeconds": 300,
    "Cache": {
      "DurationSeconds": 7200
    }
  }
}
And the following options classes and registration.
using Microsoft.Extensions.Options;
using System.ComponentModel.DataAnnotations;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddOptions<WeatherApiOptions>()
    .Bind(builder.Configuration.GetSection(WeatherApiOptions.SectionName))
    .ValidateDataAnnotations()
    .ValidateOnStart();

// Omitted for brevity

WebApplication app = builder.Build();

// Omitted for brevity

app.Run();

public class WeatherApiOptions
{
    public const string SectionName = "WeatherApi";

    [Required]
    [Url]
    public string BaseUrl { get; set; } = string.Empty;

    [Range(1, 60)]
    public int TimeoutSeconds { get; set; }

    [Required]
    [ValidateObjectMembers]
    public CacheOptions Cache { get; set; } = new();
}

public class CacheOptions
{
    [Range(30, 3600)]
    public int DurationSeconds { get; set; }
}
Here ValidateDataAnnotations() validates the DataAnnotation attributes on our options type. By default though, that validation is lazy, it only runs the first time someone accesses IOptions<WeatherApiOptions>.Value. That means a misconfigured application would happily start up and only fail later at runtime when the options are first used. ValidateOnStart() fixes that by forcing the validation to run eagerly at application startup, so we fail fast. (This kicks in as long as something actually starts the host, e.g. app.Run().)

Something to note is, the ValidateDataAnnotations() only validates the top-level options type. It does not recurse into nested objects (or into items of a collection). So the DataAnnotation attributes on CacheOptions are silently ignored. And for that, from .NET 8, onwards, two new attributes have been added to Microsoft.Extensions.Options:
  • [ValidateObjectMembers] - recursively validates the DataAnnotation attributes on a nested object.
  • [ValidateEnumeratedItems] - recursively validates the DataAnnotation attributes on each item of a collection.
The application now fails at startup, and notice that all the validation failures, including the ones on the nested object are reported at once.
Microsoft.Extensions.Options.OptionsValidationException: 

DataAnnotation validation failed for 'WeatherApiOptions' members: 
'BaseUrl' with the error: 'The BaseUrl field is not a valid fully-qualified http, https, or ftp URL.'.;
DataAnnotation validation failed for 'WeatherApiOptions' members:
'TimeoutSeconds' with the error: 'The field TimeoutSeconds must be between 1 and 60.'.;
DataAnnotation validation failed for 'WeatherApiOptions.Cache' members:
'DurationSeconds' with the error: 'The field DurationSeconds must be between 30 and 3600.'.
Hope this helps.

Happy Coding.

Regards,
Jaliya