Extends OptionsBuilder<T> and IServiceCollection with nested data annotations validation and eager validation on start.
Get it on NuGet
- Create your options class(es)
- Decorate your options with data annotations
- Call
ConfigureAndValidate<T>(Action<T> configureOptions)on yourIServiceCollection
ConfigureAndValidate will configure your options (calling the base Configure method), but will also check that the built configuration respects the data annotations, otherwise an OptionsValidationException (with details) is thrown as soon as the application is started. No misconfiguration surprise at runtime!
// Startup configuration
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureAndValidate<ApplicationOptions>(options => _configuration.Bind(options));
...
}
// Options classes
public class ApplicationOptions
{
[Required]
public EmailOptions Email { get; set; }
public SecurityOptions Security { get; set; }
...
}
public class EmailOptions
{
[Required]
public string Host { get; set; }
[Required]
public int Port { get; set; }
public bool UseSsl { get; set; }
...
}
public class SecurityOptions
{
[Range(8, int.MaxValue, ErrorMessage = "The {0} must be greater than {1}.")]
public int PasswordMinLength { get; set; } = 8;
...
}If Email:Host is missing from the configuration, the application does not start. The OptionsValidationException holds one failure per error, naming the options class and the full path of the offending member:
DataAnnotation validation failed for 'ApplicationOptions' members: 'Email.Host' with the error: 'The Host field is required.'.
The descent walks public instance properties, one declaration per name (the most derived readable one): object properties, and the contents of collection properties, dictionary entries included. It does not explore string or value-type properties, whose annotations are validated at the parent level, static properties, or the primitive and enum items of a collection. Nor does it explore objects of the following types, which carry no annotations: Type and the other reflection types (MemberInfo, ParameterInfo, Module, Assembly), delegates, Uri, X509Certificate certificates and FileSystemInfo. Each object is visited once, which stops cyclic graphs; beyond 32 levels the descent stops, which bounds computed properties that return a new object on each read. Each nested error is prefixed with the property path (Email.Host).
Since .NET 8, ValidateDataAnnotations() also validates nested objects, but only the members annotated with [ValidateObjectMembers] or [ValidateEnumeratedItems], and never dictionary entries (dotnet/runtime#97873). ValidateDataAnnotationsRecursively() needs no attribute: it walks the whole graph, dictionaries included. A class annotated for the .NET validation validates the same way with this package.
services.ConfigureAndValidate<TOptions>(configureOptions)Is syntactic sugar for
services
.AddOptions<TOptions>()
.Configure(configureOptions)
.ValidateDataAnnotationsRecursively()
.ValidateOnStart() // or ValidateEagerly()
.ServicesThis method register this options instance for validation of its DataAnnotations at the first dependency injection. Nested objects are supported.
This method validates this options instance at application startup rather than at the first dependency injection.
optionsBuilder.ConfigureAndValidate<TOptions>(configureOptions)Is syntactic sugar for
optionsBuilder
.Configure(configureOptions)
.ValidateDataAnnotationsRecursively()
.ValidateOnStart() // or ValidateEagerly()