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

1

8

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?

Mcanally answered 22/10, 2015 at 10:27 Comment(0)
M
9

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";
            }
        }
    }
}
Mcanally answered 18/11, 2015 at 9:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.