diff --git a/ioptions-validation-minivalidation/IOptionsValidationMiniValidation.csproj b/ioptions-validation-minivalidation/IOptionsValidationMiniValidation.csproj new file mode 100644 index 0000000..94077f2 --- /dev/null +++ b/ioptions-validation-minivalidation/IOptionsValidationMiniValidation.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/ioptions-validation-minivalidation/Program.cs b/ioptions-validation-minivalidation/Program.cs new file mode 100644 index 0000000..39ce2e5 --- /dev/null +++ b/ioptions-validation-minivalidation/Program.cs @@ -0,0 +1,88 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; +using MiniValidation; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddOptions() + .BindConfiguration("MySettings") + .ValidateMiniValidation() // <- Enable validation + .ValidateOnStart(); // <- Validate on app start + +// Explicitly register the settings object by delegating to the IOptions object +builder.Services.AddSingleton(resolver => + resolver.GetRequiredService>().Value); + +var app = builder.Build(); + +// app.MapGet("/", (IOptions options) => options.Value); +app.MapGet("/", (MySettings options) => options); + +app.Run(); + +public class MySettings +{ + [Required] + public string DisplayName { get; set; } + + [Required] + public NestedSettings Nested { get; set; } + + public class NestedSettings + { + [Required] + public string Value { get; set; } + + [Range(1, 100)] + public int Count { get; set; } + } +} + +public static class MiniValidationExtensions +{ + public static OptionsBuilder ValidateMiniValidation( + this OptionsBuilder optionsBuilder) where TOptions : class + { + optionsBuilder.Services.AddSingleton>( + new MiniValidationValidateOptions(optionsBuilder.Name)); + return optionsBuilder; + } +} + + public class MiniValidationValidateOptions + : IValidateOptions where TOptions : class + { + public MiniValidationValidateOptions(string? name) + { + Name = name; + } + + public string? Name { get; } + + public ValidateOptionsResult Validate(string? name, TOptions options) + { + // Null name is used to configure all named options. + if (Name != null && Name != name) + { + // Ignored if not validating this instance. + return ValidateOptionsResult.Skip; + } + + // Ensure options are provided to validate against + ArgumentNullException.ThrowIfNull(options); + + if (MiniValidator.TryValidate(options, out var validationErrors)) + { + return ValidateOptionsResult.Success; + } + + var typeName = options.GetType().Name; + var errors = new List(); + foreach (var (member, memberErrors) in validationErrors) + { + errors.Add($"DataAnnotation validation failed for '{typeName}' member: '{member}' with errors: '{string.Join("', '", memberErrors)}'."); + } + + return ValidateOptionsResult.Fail(errors); + } + } diff --git a/ioptions-validation-minivalidation/Properties/launchSettings.json b/ioptions-validation-minivalidation/Properties/launchSettings.json new file mode 100644 index 0000000..cf6f48b --- /dev/null +++ b/ioptions-validation-minivalidation/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:27028", + "sslPort": 44379 + } + }, + "profiles": { + "StronglyTypedValidation": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7282;http://localhost:5101", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/ioptions-validation-minivalidation/appsettings.json b/ioptions-validation-minivalidation/appsettings.json new file mode 100644 index 0000000..9d46e9d --- /dev/null +++ b/ioptions-validation-minivalidation/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "MySettings": { + "DisplayName": "Display", + "Nested" : { + "Count": 0 + } + } +}