Created
February 17, 2024 09:41
-
-
Save marcin-burak/28d2a46b9bb2ed2ad7da50f552e77844 to your computer and use it in GitHub Desktop.
ASP.NET options startup validation with FluentValidation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class OptionsBuilderFluentValidationExtensions | |
{ | |
public static OptionsBuilder<TOptions> ValidateWithFluentValidation<TOptions>(this OptionsBuilder<TOptions> optionsBuilder) where TOptions : class | |
{ | |
optionsBuilder.Services.AddSingleton<IValidateOptions<TOptions>>(serviceProvider => | |
new FluentValidationOptions<TOptions>(optionsBuilder.Name, serviceProvider.GetRequiredService<IValidator<TOptions>>()) | |
); | |
return optionsBuilder; | |
} | |
} | |
public sealed class FluentValidationOptions<TOptions>(string? name, IValidator<TOptions> validator) : IValidateOptions<TOptions> where TOptions : class | |
{ | |
private readonly IValidator<TOptions> _validator = validator; | |
public string? Name { get; } = name; | |
public ValidateOptionsResult Validate(string? name, TOptions options) | |
{ | |
if (Name is not null && Name != name) | |
{ | |
return ValidateOptionsResult.Skip; | |
} | |
ArgumentNullException.ThrowIfNull(options); | |
var validationResult = _validator.Validate(options); | |
if (validationResult.IsValid) | |
{ | |
return ValidateOptionsResult.Success; | |
} | |
var optionsTypeName = typeof(TOptions).Name; | |
var errors = validationResult.Errors.Select(error => error.ErrorMessage).ToArray(); | |
return ValidateOptionsResult.Fail(errors); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal static class Extensions | |
{ | |
public static IServiceCollection AddOptionsByConvention<TOptions>(this IServiceCollection services, bool skipStartupValidation = false) where TOptions : class, new() | |
{ | |
var options = services.AddOptions<TOptions>().BindConfiguration(GetConfigurationSectionName<TOptions>()); | |
if (skipStartupValidation) | |
{ | |
return services; | |
} | |
options.ValidateWithFluentValidation().ValidateOnStart(); | |
return services; | |
} | |
public static TOptions GetOptionsByConvention<TOptions>(this IConfiguration configuration) where TOptions : class, new() | |
{ | |
var options = Activator.CreateInstance<TOptions>(); | |
configuration.Bind(GetConfigurationSectionName<TOptions>(), options); | |
return options; | |
} | |
private static string GetConfigurationSectionName<TOptions>() where TOptions : class, new() | |
{ | |
var optionsTypeNameWithoutOptionsSuffix = typeof(TOptions).Name[..^7]; | |
return optionsTypeNameWithoutOptionsSuffix; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment