Fluent Validation with Web Api 2

Full source code here.

I wrote blog post in 2015 on using the Fluent Validation NuGet package for complex validation needs. In the post the validator checked that a create person request had at least one active primary phone and at least one active primary email. Using Fluent Validation this was easy.

The blog post used a simple console application, but I now realize that a lot of people are having difficulty using this in Web API, especially when they have to consume the response from Web Api and look for potential errors from the Fluent Validation package.

I see an approach put forward Matthew Jones, but I don’t like the response rewriting. If you are making a request to a Web Api for a Person, you are no longer getting a Person, you’re getting a ResponsePackage with Person as an object inside. It causes problems with testing - calling the action method directly from a test return a different object then when called via a web request.

1public class ResponsePackage  
2{
3    public List Errors { get; set; }
4
5    public object Result { get; set; } 
6}

This requires quite a bit of extra work on the client side to get at the Result object. Testing is also complicated because a test calling the action method directly get a different response than a request being rewritten. The rewriting also applies to all responses from the WebApi.

I propose a slightly different solution.

Step 1

Add the FluentValidation.WebApi NuGet package to the Web Api project and wire it up in the WebApiConfig class.

 1public static class WebApiConfig
 2{
 3    public static void Register(HttpConfiguration config)
 4    {
 5        //Fluent Validation
 6        config.Filters.Add(new ValidateModelStateFilter());
 7        FluentValidationModelValidatorProvider.Configure(config);
 8        
 9        //snip..    
10    }
11}

Step 2

Create a model and validator in a Models project.

 1[Validator(typeof(PersonCreateRequestModelValidator))] 
 2public class PersonCreateRequestModel
 3{
 4    public Guid PersonId { get; set; }
 5    public string Firstname { get; set; }
 6    public string Lastname { get; set; }
 7}
 8	
 9public class PersonCreateRequestModelValidator : AbstractValidator
10{
11    //Simple validator that checks for values in Firstname and Lastname
12    public PersonCreateRequestModelValidator()
13    {
14        RuleFor(r => r.Firstname).NotEmpty();
15        RuleFor(r => r.Lastname).NotEmpty();
16    }
17}

Step 3

Create the Web Api endpoint.

1public IHttpActionResult Post([FromBody]PersonCreateRequestModel requestModel)
2{
3    // If we get this far we have a vaild model.
4    // If we then saved the person to the database we would get an id for the person and return it to the caller.
5    requestModel.PersonId = Guid.NewGuid();
6
7    return Ok(requestModel.PersonId);
8}

Step 4

Create a client to call the Web Api endpoints. In my example I use a console app, but you could use MVC or another Web Api project.

Create a HttpClient client to call the web service.

1private HttpClient GetHttpClient()
2{
3    var httpClient = new HttpClient();
4    httpClient.BaseAddress = new Uri(@"http://localhost:5802/api/");
5    httpClient.DefaultRequestHeaders.Accept.Clear();
6    httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
7    return httpClient;
8}

Call the web service using the client and examine the response for success, HttpStatusCode.OK, or failure, any other status code.

 1private async <Task> PostToService(PersonCreateRequestModel model)
 2{
 3    var httpClient = GetHttpClient();
 4    string requestEndpoint = "person"; // full request will be http://localhost:5802/api/person
 5    HttpResponseMessage response = await httpClient.PostAsJsonAsync(requestEndpoint, model);
 6
 7    WebApiResponse wrappedResponse;
 8    
 9    if (response.StatusCode == HttpStatusCode.OK)
10    {
11        var id = await response.Content.ReadAsAsync();
12        wrappedResponse = new WebApiResponse(id, response.StatusCode);
13    }
14    else
15    {
16        var errors = await response.Content.ReadAsStringAsync();
17        wrappedResponse = new WebApiResponse(errors, response.StatusCode, true);
18    }
19    return wrappedResponse;
20}

Success or failure, I wrap the repsonse (in the client) without losing anything from the web service. WebApiResponse is a generic class and as such takes any type. The wrapped response is then returned to the caller to do with as they wish.

 1public class WebApiResponse
 2{
 3    public WebApiResponse(T apiResponse, HttpStatusCode httpStatusCode)
 4    {
 5        ApiResponse = apiResponse;
 6        HttpStatusCode = httpStatusCode;
 7    }
 8
 9    public WebApiResponse(string error, HttpStatusCode httpStatusCode, bool isError) // isError is just a way to differentiate the two constructors. If <code>T</code> were a string this constructor would always be called. 
10    {
11        Error = error;
12        HttpStatusCode = httpStatusCode;
13    }
14    public T ApiResponse { get; set; }
15    public HttpStatusCode HttpStatusCode { get; set; }
16    public string Error { get; set; }
17}

The major benefits I see in the approach are that it is simple, very flexible, does not change anything coming back from the web service, testing is unaffected, and you are altering the Microsoft provided response pipeline.

Full source code here.

comments powered by Disqus

Related