I have working code but am hoping there is a better way...
Problem:
- annotate a parameter in a method used in a minimal API in a way that informs OpenAPI (but does not mislead a future developer -- RangeAttribute is misleading)
- do validation of a primitive type
Based on this Using DataAnnotation Model Validation in Minimal Api, I have the following validator
public static RouteHandlerBuilder Validate<T>(this RouteHandlerBuilder builder, IValidator<T?> validator, int? position = null)
{
builder.AddEndpointFilter(RouteHandlerFilter);
return builder;
async ValueTask<object?> RouteHandlerFilter(EndpointFilterInvocationContext invocationContext, EndpointFilterDelegate next)
{
T? argument = position.HasValue ? invocationContext.GetArgument<T>(position.Value) : invocationContext.Arguments.OfType<T>().FirstOrDefault();
ValidationResult? validationResult = await validator.ValidateAsync(argument).ConfigureAwait(false);
return validationResult.IsValid ? await next(invocationContext) : TypedResults.ValidationProblem(validationResult.ToDictionary());
}
}
which is using a Fluent Validations validator
internal class IntRangeValidator : AbstractValidator<int?>
{
public IntRangeValidator(int min = int.MinValue, int max = int.MaxValue)
{
this.RuleFor(i => i).NotNull().InclusiveBetween(min, max);
}
}
which is added to the route via
// IEndpointRouteBuilder app
app.MapGet( /* ... */ ).Validate(new IntRangeValidator(1));
Since .WithOpenApi(operation => { /* ... */ }) is unable to modify the schema of the parameter, the following is used
internal class PositiveSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.ParameterInfo?.GetCustomAttribute<PositiveAttribute>() != null)
{
schema.Minimum = 1;
}
}
}
// SwaggerGenOptions options
options.SchemaFilter<PositiveSchemaFilter>();
Questions:
- Does anyone have a more compact approach that doesn't need as many moving parts?
- Is there some other "magic" method in the route building process that allows access to the parameter info (name, type, custom attributes) that could be used in lieu of the Validate extension method (and without its positional weirdness)?
public static async Task<Results<NotFound, Ok<WeatherForecast>>> GetForecast( [FromRoute] [Positive] int id, IWeatherRepo repo, CancellationToken cancellationToken)