OptionsPatternValidation 1.5.1
dotnet add package OptionsPatternValidation --version 1.5.1
NuGet\Install-Package OptionsPatternValidation -Version 1.5.1
<PackageReference Include="OptionsPatternValidation" Version="1.5.1" />
<PackageVersion Include="OptionsPatternValidation" Version="1.5.1" />
<PackageReference Include="OptionsPatternValidation" />
paket add OptionsPatternValidation --version 1.5.1
#r "nuget: OptionsPatternValidation, 1.5.1"
#:package OptionsPatternValidation@1.5.1
#addin nuget:?package=OptionsPatternValidation&version=1.5.1
#tool nuget:?package=OptionsPatternValidation&version=1.5.1
OptionsPatternValidation
Extension methods (on IServiceCollection and IConfiguration) that make it easier to wire up IOptions<T> POCOs (plain old C# objects) with validation.
Installation
.NET Core
$ dotnet add package OptionsPatternValidation
Package Manager
PM> Install-Package OptionsPatternValidation
Usage
For most use cases where you want validation, I suggest using the DataAnnotation approach. In my experience, most application settings can be validated with a simple [Required] or [Range(N,M)] attribute on the property.
Create POCOs
Each "section" (top level) of your appsettings.json file will need its own POCO (Plain Old C# Object). This is true no matter which validation approach you use.
If the POCO class name is not identical to the appsettings.json section name, you can use the [SettingsSectionNameAttribute("section-name")] attribute on the POCO class definition to set the mapping.
Under the covers, this package is calling GetSection() in Microsoft.Extensions.Configuration and Bind() in Microsoft.Extensions.Configuration with the same capabilities and restrictions. Prior to .NET 6.0 (which adds the ConfigurationKeyName attribute) the property names on the POCO must match the names in the appsettings JSON or environment variables.
Choose a validation
The POCOs for the options pattern will all get wired up in Startup.ConfigureServices() method in your application.
No validation
If you do not want to wire up validation for the POCO, use the AddSettings<T>(config) method.
services.AddSettings<ExampleAppSettings>(config);
DataAnnotation validation
This method signature implements support for DataAnnotation validation of the POCO. Including recursive validaton of all sub-objects and collections of objects on the POCO.
services.AddValidatedSettings<ExampleAppSettings>(config);
Recursive validation of the object and its child objects is provided via RecursiveDataAnnotationsValidation.
Validation error messages come from the attributes on your POCO, and this package copies them into the OptionsValidationException message as-is. A custom ValidationAttribute that puts the property value in its message (for example a password or connection string) will expose that value in the exception text, and anything that logs the exception. Keep secrets out of attribute error messages, and do not put untrusted text in them.
Validation runs on the POCO after binding. It can only check what the binder produced. If the binder drops a value, the validator never sees it. For example, an array element with an enum name that does not exist (a typo such as "Batery") is dropped from the array without an error. An array that should hold three items then holds two, and validation passes. The same typo on a plain property throws an InvalidOperationException from the binder. To catch dropped elements, put a [MinLength(n)] attribute on an array property, or check the count in an IValidateOptions<T> class. On .NET Framework, [MinLength] throws an InvalidCastException on a List<T> property, so use an array there.
If a validation attribute throws, or a [RegularExpression] times out, the exception reaches your code as thrown. This package does not wrap it in an OptionsValidationException, which matches ValidateDataAnnotations() in Microsoft.Extensions.Options. An exception stops the whole validation run, so the failures from other properties are not reported. Startup, or the first read of the options, fails either way.
The validator has no limit on how deeply objects can nest, and a .NET stack overflow cannot be caught. It ends the process. On .NET 8, a chain of nested objects overflows at roughly 850 levels on a 512 KB stack, 1,700 levels on a 1 MB stack and 13,000 levels on an 8 MB stack. The numbers vary by platform and thread. The configuration binder uses more stack per level than the validator, so it overflows first. Options bound from configuration cannot reach the validator's limit. Only an object graph that your own code builds, such as in Configure() or PostConfigure(), can. Keep those graphs shallow.
The AddValidatedSettings<T>() methods validate the default (unnamed) options instance. If you call RecursivelyValidateDataAnnotations() yourself on an OptionsBuilder<T> that has a name, such as services.AddOptions<T>("primary"), validation runs only for that name. Reading the default instance, or an instance with any other name, skips validation without an error.
IValidateOptions
This approach requires two classes. One is the POCO for the settings. The other is the class that derives from IValidateOptions<T> and implements the Validate() method.
services.AddValidatedSettings<ExampleAppSettings, ExampleAppSettingsValidator>(config);
The IValidationOptions<T> approach is really powerful, but also tedious to use.
Accessing Configuration in Startup
Sometimes in the Startup.ConfigureServices() method you want to access your settings POCOs with validation. This can be performed using the GetValidatedConfigurationSection<T>() method. It does not register the settings class as IOptions<T> in the container, therefore you still need to make a call to AddSettings<T>(config) or AddValidatedSettings<T>(config)
var appSettings = config.GetValidatedConfigurationSection<ExampleAppSettings>();
Note that if the section is completely missing from your configuration, the POCO will still be created and validation will run against the default values within the POCO class.
Experimental
AddEagerlyValidatedSettings<T>(config, out x)
OBSOLETE: Will be removed at some point. Use the GetValidatedConfigurationSection<T>() method instead.
There is an experimental extension method that will eagerly validate the object and also return a reference to the POCO. But it is not compatible with situations where you are using IOptionsMonitor<T> in your code. Because it wires up a snapshot of the configuration at the time of startup, the "on-change" listeners will not fire when underlying configuration values change.
services.AddEagerlyValidatedSettings<ExampleAppSettings>(
configuration,
out var exampleAppSettings
);
Build Status
Nuget Page
https://www.nuget.org/packages/OptionsPatternValidation/
History
This grew out of experiments with the .NET Core options pattern and the desire to simplify how sections in the appsettings.json / .NET configuration system get wired up to POCOs and validation for those POCOs.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Options (>= 8.0.2)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
- RecursiveDataAnnotationsValidation (>= 2.3.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.5.1 | 44 | 10/2/2026 |
| 1.5.0 | 49 | 10/2/2026 |
| 1.4.3 | 52 | 10/2/2026 |
| 1.4.2 | 52 | 10/2/2026 |
| 1.4.1 | 76 | 10/1/2026 |
| 1.4.0 | 207 | 3/29/2026 |
| 1.3.1 | 199 | 3/28/2026 |
| 1.3.0 | 4,227 | 11/23/2022 |
| 1.2.0 | 638 | 7/15/2022 |
| 1.1.4 | 2,105 | 6/23/2022 |
| 1.1.3 | 23,604 | 3/3/2022 |
| 1.1.2 | 4,624 | 1/11/2022 |
| 1.1.1 | 677 | 1/10/2022 |
| 1.0.2 | 12,168 | 3/26/2020 |
| 1.0.1 | 678 | 3/26/2020 |
| 1.0.0 | 721 | 3/20/2020 |
| 0.9.1 | 726 | 3/20/2020 |