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?
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.
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);
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.
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";
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With