2014-09-24

More MVC and JSON.Net - Exceptions

In my last episode with MVC, I was trying to reconcile MVC 5.0 with WebAPI 5.1 and the fact that the former used a broken JSON library, while the latter used the better Newtonsoft JSON library. I implemented a model binder and a value provider factory to get the two projects in sync, and all was right with the world.

As the project has gone on, we've come across instances where we've needed to call MVC controllers and get JSON responses back. We have created WebAPI controllers in some cases, but the way the project is structured, we've needed to post MVC models as JSON via AJAX back to MVC methods where we cannot reference them in the WebAPI project (otherwise we'd have a circular reference).

There are probably a few ways to restructure this to work. Ripping the view models out to a library that can be referenced by both MVC and WebAPI was one idea, for instance. But that would've been a lot of work and caused bigger deployment headaches that I don't want to get into here. (A smaller project might've gotten away with it, though.)

Returning JSON to the client is almost as easy as having your MVC controller return a JsonResult — the MVC Controller class has its own built-in Json() method to do just that. Except, of course, it uses the JSON serializer we never want to see again. In this case, I created my own JsonNetResult class (based on a StackOverflow question, naturally), and my controller just calls this.JsonNetResult(resultObj); to do the magic.

The remaining issue, though, is when it came to exceptions. While WebAPI, on a JSON request, returns an error response formatted in JSON (that jQuery's ajax method parses easily), MVC gives you that nice, big, friendly "yellow screen of death" with the error formatted in HTML (or a generic message when you get to production). Not too useful when you want your JavaScript to report back some detail about what went wrong.

The solution, here, was to use an exception filter. The JsonNetExceptionFilter class checks to see if the incoming request was specified as JSON (using very similar code to the model binder), and if so, it handles the error on its own. We were throwing HTTP error messages using two different types of objects, depending on whether the code was copied from WebAPI or MVC (since they each have their own namespaces for this kind of thing). I check for these types so that I can set the response code to something besides the default 500 (400 Bad Request is used quite a bit), and I set the content to a JsonNetResult object with the data being a message formatted in something similar to the WebAPI format (and thus parsed by the same JavaScript code).

I considered leaving it here, so that we would have to decorate every JSON method with this handler as well as specifying it returned a JsonNetResult (unless there's a way to find out programmatically in the filter whether the current controller method returns a JsonNetResult, but I failed to find that); but ultimately, I decided to just override the default error handler site-wide (since it falls back to the base class for non-JSON requests, it shouldn't be an issue). That was done by editing the MVC app's RegisterGlobalFilters method to read:

filters.add(new JsonNetExceptionFilter());

Now, whenever we want to return an exception back to a JSON request, all we have to do is throw an exception:

throw new System.Web.HttpException((int)System.Net. HttpStatusCode.BadRequest, "Bad request data.");

Processing the return message in jQuery is left as an exercise to the reader (though if you use WebAPI, you probably already know). ;)