In the previous post, we looked at how to implement IValidateOptions<T> by hand — writing a dedicated validator class, injecting services, and expressing cross-property constraints that Data Annotations can't handle. That approach gives you full control and is the right tool when validation logic is genuinely complex. While researching that post I discovered another feature that's worth knowing about: when your validation can be expressed with Data Annotation attributes, the options validation source generator (available since .NET 8) will write the IValidateOptions<T> implementation for you at compile time. You get the safety of startup validation without the boilerplate, and as a bonus the generated code is reflection-free and AOT-compatible. The problem with runtime data annotations Before the source generator existed, the standard way to add annotation-based validation was ValidateDataAnnotations() : This works, but it uses reflection at runtime to ...
When you build .NET applications with strongly typed configuration, IOptions<T> and its variants give you a clean way to bind appsettings.json sections to C# classes. But binding isn't the same as validating - a missing required value or an out-of-range number will happily bind to a default and silently break your app at runtime. IValidateOptions<T> is the hook .NET provides to fix that. The problem: silent misconfiguration Consider a typical options class: If Host is missing from appsettings.json , your app starts fine. The failure surfaces only when the first email is sent — in production, at 2 AM. Data Annotations ( [Required] , [Range] ) combined with ValidateDataAnnotations() help, but they fall short when you need: Cross-property validation (e.g., Port must be 465 when UseSsl is true ) Async or database-backed checks Conditional logic depending on environment Reusable validators shared across multiple options types This is where I...