-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Open
Open
Copy link
Labels
api-suggestionEarly API idea and discussion, it is NOT ready for implementationEarly API idea and discussion, it is NOT ready for implementationarea-Extensions-Options
Milestone
Description
Background and motivation
Today, if I create a record
with required
properties for my configuration type, and bind from a configuration section to that record type, I still need to write my own IValidateOptions
to do null-checks by hand, or I need to apply System.ComponentModel.DataAnnotations
attributes to every member to duplicatively mark it as required.
Consider this concrete example:
public record MyConfigurationType
{
public required string RequiredMember { get; init; }
}
services.AddOptions<MyConfigurationType>()
.Bind(configuration.GetSection("MyConfiguration"))
.ValidateOnStart();
{
"MyConfiguration": {
"foo": "bar"
}
}
The application will start without any errors, despite RequiredMember
not being set.
I can make it work by writing:
public class MyConfigurationTypeConfigurationValidation : IValidateOptions<MyConfigurationType>
{
public ValidateOptionsResult Validate(string name, MyConfigurationType options)
{
if (options.RequiredMember == null)
{
return ValidateOptionsResult.Fail("RequiredMember must be provided");
}
return ValidateOptionsResult.Success;
}
}
and then registering this IValidateOptions
, but it's cumbersome to have to do this for every property.
You basically want something like this, that is generic:
public class MyConfigurationTypeConfigurationValidation : IValidateOptions<MyConfigurationType>
{
public ValidateOptionsResult Validate(string name, MyConfigurationType options)
{
var type = options.GetType();
foreach (var property in type.GetProperties()
.Where(p => p.GetCustomAttributes(typeof(RequiredMemberAttribute), false).Any()))
{
var value = property.GetValue(options);
if (value == null)
{
return ValidateOptionsResult.Fail(
$"The property '{property.Name}' is required but was not provided.");
}
}
return ValidateOptionsResult.Success;
}
}
API Proposal
public static OptionsBuilder<TOptions> ValidateRequiredMembers<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] TOptions>(this OptionsBuilder<TOptions> optionsBuilder) where TOptions : class
API Usage
services.AddOptions<MyConfigurationType>()
.Bind(configuration.GetSection("MyConfiguration"))
.ValidateRequiredMembers() // looks for RequiredMemberAttribute
.ValidateOnStart();
Alternative Designs
No response
Risks
No response
Metadata
Metadata
Assignees
Labels
api-suggestionEarly API idea and discussion, it is NOT ready for implementationEarly API idea and discussion, it is NOT ready for implementationarea-Extensions-Options