Intro
An important part of any ASP.NET web application is how we handle our project settings, and a very strong emphasis should be placed on how to validate those settings. Think about what happens when you miss setting a proper int configuration value — would its default of 0 be acceptable?
Using Configure to Register an Option
Adding a configuration model in ASP.NET starts with a plain POCO and a call to Configure:
public class OptionMongoDb
{
public string? Host { get; set; }
public int Port { get; set; }
}{
"Mongo": {
"Host": "localhost",
"Port": 27017
}
}builder.Services
.Configure<OptionMongoDb>(builder.Configuration.GetSection("Mongo"));The option is now available everywhere via IOptions<OptionMongoDb>:
[ApiController]
[Route("api/options")]
public class OptionsController : ControllerBase
{
private readonly IOptions<OptionMongoDb> _options;
public OptionsController(IOptions<OptionMongoDb> options)
{
_options = options;
}
[HttpGet]
public IActionResult Get()
{
return Ok();
}
}What's Behind the Configure Method
Decompiling Configure reveals that it calls AddOptions internally and registers an IConfigureOptions<TOption> service that binds the option model to the configuration section. This means you don't need to call services.AddOptions() yourself — it's called automatically, and the internal use of TryAdd ensures no duplicates.
public class OptionRedis
{
public string? Host { get; set; }
public int Port { get; set; }
}
public class OptionRedisConfigure : IConfigureOptions<OptionRedis>
{
private readonly IConfiguration _configuration;
public OptionRedisConfigure(IConfiguration configuration)
{
_configuration = configuration.GetSection("Redis");
}
public void Configure(OptionRedis options)
{
options.Host = _configuration.GetValue<string>("Host")
?? throw new Exception("The host value is not provided");
options.Port = _configuration.GetValue<int?>("Port")
?? throw new Exception("The port value is not provided");
}
}builder.Services
.AddSingleton<IConfigureOptions<OptionRedis>, OptionRedisConfigure>();Using IConfigureOptions to Configure an Option
Since Configure just registers an IConfigureOptions<TOption> service under the hood, you can provide your own implementation — useful when configuration requires validation or transformation at bind time rather than relying on direct section binding.
Define the option model and implement IConfigureOptions:
public class OptionRedis
{
public string? Host { get; set; }
public int Port { get; set; }
}
public class OptionRedisConfigure : IConfigureOptions<OptionRedis>
{
private readonly IConfiguration _configuration;
public OptionRedisConfigure(IConfiguration configuration)
{
_configuration = configuration.GetSection("Redis");
}
public void Configure(OptionRedis options)
{
options.Host = _configuration.GetValue<string>("Host")
?? throw new Exception("The host value is not provided");
options.Port = _configuration.GetValue<int?>("Port")
?? throw new Exception("The port value is not provided");
}
}Register the services — note the explicit AddOptions() call is required here since we're not using the Configure shorthand:
builder.Services
.AddSingleton<IConfigureOptions<OptionRedis>, OptionRedisConfigure>();Using IValidateOptions to Validate Option Values
With this method, validation is performed when a property on the option object is accessed — not when the object is configured.
Implement IValidateOptions<TOption> to add a dedicated validation service:
public class ValidateOptionsRedis : IValidateOptions<OptionRedis>
{
public ValidateOptionsResult Validate(string name, OptionRedis options)
{
if (options.Host == "localhost")
{
return ValidateOptionsResult.Fail("Localhost is not a valid production host");
}
return ValidateOptionsResult.Success;
}
}Register both the configure and validate services:
builder.Services
.AddSingleton<IConfigureOptions<OptionRedis>, OptionRedisConfigure>();
builder.Services
.AddSingleton<IValidateOptions<OptionRedis>, ValidateOptionsRedis>();Validate with Annotations
A cleaner alternative is to use data annotation attributes directly on the option model, and then use the AddOptions<> builder which exposes ValidateDataAnnotations and ValidateOnStart:
public class OptionsKafka
{
[Required]
[MinLength(1)]
public string? Host { get; set; }
public int Port { get; set; }
}builder.Services.AddOptions<OptionsKafka>()
.BindConfiguration("Kafka")
.ValidateDataAnnotations()
.ValidateOnStart();"Annoyingly, there's no clean way to bind
IValidateOptionsto theOptionBuilderor make it run at startup. We have too many ways to describe the same thing — instead of focusing on business logic, we struggle understanding all these different styles."
