I have been using Minimal APIs since it was released in .NET 6. For our validation I've been using the manual approach as follows:
app.MapPost("api/user", async ([FromService] IValidator<UserDto> validator, [FromBody] UserDto user) =>
{
var validationResult = await validator.ValidateAsync(user);
if (!validationResult.IsValid)
{
return Results.BadRequest(string.Join("/n", validationResult.Errors));
}
...
})
With the new release of .NET 7 including Filters. I have gone ahead and implemented some of the features. I've created the custom validation filter as follows:
public class ValidationFilter<T> : IEndpointFilter where T : class
{
private readonly IValidator<T> _validator;
public ValidationFilter(IValidator<T> validator)
{
_validator = validator;
}
public async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var obj = context.Arguments.FirstOrDefault(x => x?.GetType() == typeof(T)) as T;
if (obj is null)
{
return Results.BadRequest();
}
var validationResult = await _validator.ValidateAsync(obj);
if (!validationResult.IsValid)
{
return Results.BadRequest(string.Join("/n", validationResult.Errors));
}
return await next(context);
}
}
I can now use the above by calling AddEndPointFilter<T>()
so something like:
app.MapPost("api/user", (..) => { ... }).AddEndPointFilter<ValidationFilter>();
The above works great. However, I have some RuleSet()
in my FluentValidation which I include in a PUT
request. So my question is, how can I pass the RuleSets to my ValidationFilter
?
One way is to leverage the ability to provide metadata for endpoint. Something along this lines:
public class RuleSetMetadata<T>
{
public RuleSetMetadata(string ruleSet)
{
RuleSet = ruleSet;
}
public string RuleSet { get; set; }
}
Setup:
app.MapPost("api/user", (Example e) => e)
.AddEndpointFilter<ValidationFilter<Example>>()
.WithMetadata(new RuleSetMetadata<Example>("Test"))
And changes to the implementation:
public class ValidationFilter<T> : IEndpointFilter where T : class
{
// ...
public async ValueTask<object> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
string? ruleSet = null;
if (context.HttpContext.GetEndpoint()?.Metadata.GetMetadata<RuleSetMetadata<T>>() is {} meta)
{
ruleSet = meta.RuleSet;
}
var validationResult = ruleSet is null
? await _validator.ValidateAsync(obj)
: await _validator.ValidateAsync(obj, options => options.IncludeRuleSets(ruleSet));
// ...
}
}
Another way is to look into AddEntpointFilterFactory
and implement some parameter handling via attributes (can be used
in conjunction with var grp = app.MapGroup(""); grp.AddEndpointFilterFactory(...)
).