Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How Do You Display WebAPI Model Errors in MVC?

0.00/5 (No votes)
4 Jan 2016 1  
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.

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.

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.

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.

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here