Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to perform async ModelState validation with FluentValidation in Web API?

I setup a web api project to use FluentValidation using the webapi integration package for FluentValidation. Then I created a validator that uses CustomAsync(...) to run queries against the database.

The issue is that the validation seems to deadlock when awaiting for the database task. I did some investigation, it seems that the MVC ModelState API is synchronous, and it calls a synchronous Validate(...) method that makes FluentValidation to call task.Result, causing the deadlock.

Is it correct to assume that async calls won't work well with webapi integrated validation?

And if that is the case, what is the alternative? WebApi ActionFilters seem to support for async processing. Do I need to build my own filter to handle the validation manually or is there something already there to do that that I'm not seeing?

like image 713
Natan Avatar asked Oct 22 '15 10:10

Natan


People also ask

Can we use Fluent Validation in Web API?

So in summary, we can now write validation objects for our WebAPI request objects, unit test them, and supply context information so that they have all the information you need to perform your validation.

How do you use FluentValidation?

To run the validator, instantiate the validator object and call the Validate method, passing in the object to validate. Customer customer = new Customer(); CustomerValidator validator = new CustomerValidator(); ValidationResult result = validator. Validate(customer);

How can validation errors be handled in Web API?

Handling Validation Errors Web API does not automatically return an error to the client when validation fails. It is up to the controller action to check the model state and respond appropriately. If model validation fails, this filter returns an HTTP response that contains the validation errors.


1 Answers

I ended up creating a custom filter and skipped built-in validation entirely:

public class WebApiValidationAttribute : ActionFilterAttribute
{
    public WebApiValidationAttribute(IValidatorFactory factory)
    {
        _factory = factory;
    }

    IValidatorFactory _factory;

    public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        if (actionContext.ActionArguments.Count > 0)
        {
            var allErrors = new Dictionary<string, object>();

            foreach (var arg in actionContext.ActionArguments)
            {
                // skip null values
                if (arg.Value == null)
                    continue;

                var validator = _factory.GetValidator(arg.Value.GetType());

                // skip objects with no validators
                if (validator == null)
                    continue;

                // validate
                var result = await validator.ValidateAsync(arg.Value);

                // if there are errors, copy to the response dictonary
                if (!result.IsValid)
                {
                    var dict = new Dictionary<string, string>();

                    foreach (var e in result.Errors)
                        dict[e.PropertyName] = e.ErrorMessage;

                    allErrors.Add(arg.Key, dict);
                }
            }

            // if any errors were found, set the response
            if (allErrors.Count > 0)
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, allErrors);
                actionContext.Response.ReasonPhrase = "Validation Error";
            }
        }
    }
}
like image 134
Natan Avatar answered Sep 19 '22 22:09

Natan