Quantcast
Viewing all articles
Browse latest Browse all 132

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.

public class ResponsePackage  
{
    public List Errors { get; set; }

    public object Result { get; set; } 
}

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.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //Fluent Validation
        config.Filters.Add(new ValidateModelStateFilter());
        FluentValidationModelValidatorProvider.Configure(config);
        
        //snip..    
    }
}
Step 2

Create a model and validator in a Models project.

[Validator(typeof(PersonCreateRequestModelValidator))] 
public class PersonCreateRequestModel
{
    public Guid PersonId { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}
	
public class PersonCreateRequestModelValidator : AbstractValidator
{
    //Simple validator that checks for values in Firstname and Lastname
    public PersonCreateRequestModelValidator()
    {
        RuleFor(r => r.Firstname).NotEmpty();
        RuleFor(r => r.Lastname).NotEmpty();
    }
}

Step 3

Create the Web Api endpoint.

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

    return Ok(requestModel.PersonId);
}
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.

private HttpClient GetHttpClient()
{
    var httpClient = new HttpClient();
    httpClient.BaseAddress = new Uri(@"http://localhost:5802/api/");
    httpClient.DefaultRequestHeaders.Accept.Clear();
    httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    return httpClient;
}

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

private async <Task> PostToService(PersonCreateRequestModel model)
{
    var httpClient = GetHttpClient();
    string requestEndpoint = "person"; // full request will be http://localhost:5802/api/person
    HttpResponseMessage response = await httpClient.PostAsJsonAsync(requestEndpoint, model);

    WebApiResponse wrappedResponse;
    
    if (response.StatusCode == HttpStatusCode.OK)
    {
        var id = await response.Content.ReadAsAsync();
        wrappedResponse = new WebApiResponse(id, response.StatusCode);
    }
    else
    {
        var errors = await response.Content.ReadAsStringAsync();
        wrappedResponse = new WebApiResponse(errors, response.StatusCode, true);
    }
    return wrappedResponse;
}

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.

public class WebApiResponse
{
    public WebApiResponse(T apiResponse, HttpStatusCode httpStatusCode)
    {
        ApiResponse = apiResponse;
        HttpStatusCode = httpStatusCode;
    }

    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. 
    {
        Error = error;
        HttpStatusCode = httpStatusCode;
    }
    public T ApiResponse { get; set; }
    public HttpStatusCode HttpStatusCode { get; set; }
    public string Error { get; set; }
}

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.


Viewing all articles
Browse latest Browse all 132

Trending Articles