1+ using FluentValidation ;
2+ using Microsoft . Extensions . Options ;
3+
4+ var builder = WebApplication . CreateBuilder ( args ) ;
5+
6+ // Register the validator
7+ builder . Services . AddScoped < IValidator < SlackApiSettings > , SlackApiSettingsValidator > ( ) ;
8+
9+ builder . Services . AddOptions < SlackApiSettings > ( )
10+ . BindConfiguration ( "SlackApi" )
11+ . ValidateFluentValidation ( ) // <- Enable validation
12+ . ValidateOnStart ( ) ; // <- Validate on app start
13+
14+ // Explicitly register the settings object by delegating to the IOptions object
15+ builder . Services . AddSingleton ( resolver =>
16+ resolver . GetRequiredService < IOptions < SlackApiSettings > > ( ) . Value ) ;
17+
18+ var app = builder . Build ( ) ;
19+
20+ // app.MapGet("/", (IOptions<SlackApiSettings> options) => options.Value);
21+ app . MapGet ( "/" , ( SlackApiSettings options ) => options ) ;
22+
23+ app . Run ( ) ;
24+
25+ public class SlackApiSettings
26+ {
27+ public string ? WebhookUrl { get ; set ; }
28+ public string ? DisplayName { get ; set ; }
29+ public bool ShouldNotify { get ; set ; }
30+ }
31+
32+ public class SlackApiSettingsValidator : AbstractValidator < SlackApiSettings >
33+ {
34+ public SlackApiSettingsValidator ( )
35+ {
36+ RuleFor ( x => x . DisplayName ) . NotEmpty ( ) ;
37+ RuleFor ( x => x . WebhookUrl )
38+ . NotEmpty ( )
39+ // .MustAsync((_, _) => Task.FromResult(true)) 👈 can't use async validators
40+ . Must ( uri => Uri . TryCreate ( uri , UriKind . Absolute , out _ ) )
41+ . When ( x => ! string . IsNullOrEmpty ( x . WebhookUrl ) ) ;
42+ }
43+ }
44+
45+ public static class OptionsBuilderFluentValidationExtensions
46+ {
47+ /// <summary>
48+ /// Register this options instance for validation using FluentValidation
49+ /// Note that you _can't_ use async validators, or you will get an exception at runtime.
50+ /// </summary>
51+ /// <typeparam name="TOptions">The options type to be configured.</typeparam>
52+ /// <param name="optionsBuilder">The options builder to add the services to.</param>
53+ /// <returns>The <see cref="OptionsBuilder{TOptions}"/> so that additional calls can be chained.</returns>
54+ public static OptionsBuilder < TOptions > ValidateFluentValidation < TOptions > ( this OptionsBuilder < TOptions > optionsBuilder ) where TOptions : class
55+ {
56+ optionsBuilder . Services . AddSingleton < IValidateOptions < TOptions > > (
57+ provider => new FluentValidationOptions < TOptions > ( optionsBuilder . Name , provider ) ) ;
58+ return optionsBuilder ;
59+ }
60+ }
61+
62+ public class FluentValidationOptions < TOptions > : IValidateOptions < TOptions > where TOptions : class
63+ {
64+ private readonly IServiceProvider _serviceProvider ;
65+ private readonly string ? _name ;
66+
67+ public FluentValidationOptions ( string ? name , IServiceProvider serviceProvider )
68+ {
69+ _serviceProvider = serviceProvider ;
70+ _name = name ;
71+ }
72+
73+ public ValidateOptionsResult Validate ( string ? name , TOptions options )
74+ {
75+ // Null name is used to configure all named options.
76+ if ( _name != null && _name != name )
77+ {
78+ // Ignored if not validating this instance.
79+ return ValidateOptionsResult . Skip ;
80+ }
81+
82+ // Ensure options are provided to validate against
83+ ArgumentNullException . ThrowIfNull ( options ) ;
84+
85+ // Validators are registered as scoped, so need to create a scope,
86+ // as we will be called from the root scope
87+ using var scope = _serviceProvider . CreateScope ( ) ;
88+ var validator = scope . ServiceProvider . GetRequiredService < IValidator < TOptions > > ( ) ;
89+ var results = validator . Validate ( options ) ;
90+ if ( results . IsValid )
91+ {
92+ return ValidateOptionsResult . Success ;
93+ }
94+
95+ string typeName = options . GetType ( ) . Name ;
96+ var errors = new List < string > ( ) ;
97+ foreach ( var result in results . Errors )
98+ {
99+ errors . Add ( $ "Fluent validation failed for '{ typeName } .{ result . PropertyName } ' with the error: '{ result . ErrorMessage } '.") ;
100+ }
101+
102+ return ValidateOptionsResult . Fail ( errors ) ;
103+ }
104+ }
0 commit comments