Click here to Skip to main content
15,881,248 members
Articles / Web Development / ASP.NET / ASP.NETvNext

How Do You Display WebAPI Model Errors in MVC?

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
4 Jan 2016CPOL3 min read 10.5K   5  
How do you display WebAPI model errors in MVC?

APIs are everywhere these days. We access the web now on a plethora of devices, from laptops to smart watches. More often than not, they’re a key part of our architecture. What makes them so important? The data. Whether we’re building a mobile app or a thin web client, data is key. Just as important as the data our API does accept is the data it doesn’t accept. The invalid data. The required field we don’t pass across. The password we set to the wrong length. Any API in the wild needs to validate the data we pass in. In the .NET world, we capture those errors using ModelState. Trouble is, ModelState in MVC is a bit different from WebAPI. How can we make them talk to each other?

It's All About the ModelState

The issue stems from the fact that the binding process is different between WebAPI and MVC. WebAPI uses formatters to deserialise the request body. This results in a different structure. In WebAPI, the name of the containing class forms part of the error key. That isn’t the case in MVC. This means we need to translate the WebAPI ModelState within our MVC application somehow. We’ll then be able to display validation messages in our web client. This article is part 3 of a series on calling WebAPI from MVC. In the first part, we discussed calling a WebAPI controller from MVC. In the second part, we examined how to post to a WebAPI controller. Let’s now look at what happens if we need to validate the data we post across.

If you want to follow along, you can grab the Visual Studio solution from the previous article. If you'd rather just download the finished code, you can get it from the link below.

View the original article

What Do We Do If Things Go Bad?

To start with, let’s add a couple of required attributes to the API model. We’ll also return a 401 Bad Request HTTP status code if the model isn’t valid.

C#
public class ProductApiModel
{
    public int ProductId { get; set; } 

    [Required]
    public string Name { get; set; } 

    [Required]
    public string Description { get; set; } 

    public DateTime CreatedOn { get; set; } 
}

public class ProductsController : ApiController
{
    public IHttpActionResult Post(ProductApiModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        // should do some mapping and save a product in a data store somewhere, 
        // but we're not focusing on that for our little demo - just return a random int
        return Ok(7);
    }
}

Notice that we pass the entire ModelState back from the API. This allows us to parse it and extract the errors to display in our MVC application. We’ll hold them in an ErrorState property. Let’s see how that would look.

C#
public abstract class ApiResponse
{
    public bool StatusIsSuccessful { get; set; }
    public ErrorStateResponse ErrorState { get; set; }
    public HttpStatusCode ResponseCode { get; set; }
    public string ResponseResult { get; set; }
}

public class ErrorStateResponse
{
    public IDictionary<string, string[]> ModelState { get; set; }
}

Time To Decode That Error Response Goodness

Now we need a couple of changes to the ClientBase we introduced in the first article so we can decode the error response if it comes.

C#
private static async Task<TResponse> CreateJsonResponse<TResponse>
	(HttpResponseMessage response) where TResponse : ApiResponse, new()
{
    var clientResponse = new TResponse
    {
        StatusIsSuccessful = response.IsSuccessStatusCode,
        ErrorState = response.IsSuccessStatusCode ? null : 
        	await DecodeContent<ErrorStateResponse>(response),
        ResponseCode = response.StatusCode
    };
    if (response.Content != null)
    {
        clientResponse.ResponseResult = await response.Content.ReadAsStringAsync();
    }

    return clientResponse;
}

private static async Task<TContentResponse> 
	DecodeContent<TContentResponse>(HttpResponseMessage response)
{
    var result = await response.Content.ReadAsStringAsync();
    return Json.Decode<TContentResponse>(result);
}

Final Step: Stick It All In the MVC ModelState

That about covers the API side of things. Let’s make some changes to the client to handle any errors that come back. The POST action now needs to check whether the response status was successful. If not, it adds the errors that come back to ModelState. We’ll add this to a base controller in case we need it again. I've also added a standard anti-forgery token.

C#
public class ProductController : BaseController
{
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> CreateProduct(ProductViewModel model)
    {
        var response = await productClient.CreateProduct(model);
        if (response.StatusIsSuccessful)
        {
            var productId = response.Data;
            return RedirectToAction("GetProduct", new { id = productId });
        }

        AddResponseErrorsToModelState(response);
        return View(model);
    }
}

public abstract class BaseController : Controller
{
    protected void AddResponseErrorsToModelState(ApiResponse response)
    {
        var errors = response.ErrorState.ModelState;
        if (errors == null)
        {
            return;
        }

        foreach (var error in errors)
        {
            foreach (var entry in 
                from entry in ModelState
                let matchSuffix = string.Concat(".", entry.Key)
                where error.Key.EndsWith(matchSuffix)
                select entry)
            {
                ModelState.AddModelError(entry.Key, error.Value[0]);
            }
        }
    }
}

So what's going on here? Recall that the model state errors are a little different between WebAPI and MVC? In our example, they look like this:

WebAPI Property MVC Property
model.Name Name
model.Description Description

The LINQ statement above just looks for the MVC property name within the WebAPI property name. If it finds it, it adds the error to that property in ModelState. We match against .Name (for example) in case we have properties like Name and FirstName. If we just matched against Name, we’d be adding the Name error against FirstName.

Don't forget to add validation messages to the form in the CreateProduct view. I won't cover that here but it's included in the VS solution download, available below.

Wrapping Up

We covered quite a bit here. Let’s recap:

  • We returned a BadRequest response if the WebAPI ModelState was invalid
  • We then parsed the errors into an ErrorStateResponse in the ApiHelper
  • For the final step, we added those errors to our MVC ModelState

So What's Up Next?

In the next article, I'll be covering securing the WebAPI project with simple, token-based authentication. Look out for it soon!

View the original article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Levelnis Ltd
United Kingdom United Kingdom
Follow along my journey as I create a newsletter about launching websites. Just message me with "I'm in" and I'll add you

Comments and Discussions

 
-- There are no messages in this forum --