2

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?

Izzy
  • 6,740
  • 7
  • 40
  • 84

2 Answers2

2

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(...)).

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
1

Consider using https://github.com/benfoster/o9d-aspnet for a fluent validation based filter for minimal APIs

davidfowl
  • 37,120
  • 7
  • 93
  • 103