The Web API framework is intended for NET Developers who wish to build web APIs on top of the .NET Framework, and it is being suggested by Microsoft as being an ideal platform for building RESTful applications based in ASP.NET MVC4. Although Web API can provide a convenient shortcut to writing APIs more easily, it isn’t essential.
We’ll be demonstrating how you can use MVC3 to simulate some of the features you find in Web API. In particular, we’ll be showing how you can do content negotiation and, specifically, JSON serialization. I’ll show that by building a thin layer you can achieve the same cleanness of controller methods you get in Web API. You can return POCO objects from your controller methods and have them auto-magically serialized to JSON. With a bit of extra effort, you can even add true content negotiation and decide about the serialization format on the fly on a per-request basis.
JSON Serialization in MVC3
In ASP.NET MVC you can return JSON data from a controller method by wrapping the object into a JsonResult object. The base Controller class has the Json method that returns a newly created instance of JsonResult. Here’s how it can be coded:
1 2 3 4 5 6 7 8 9 10 |
public class BookController : Controller { // Injected in some way ... private IBookRepository _repository; public JsonResult List() { return Json(_repository.FindAll(), JsonRequestBehavior.AllowGet); } } |
In MVC, controller methods are forced to return a type that inherits from ActionResult; JsonResult is just one of these. As the name suggests, the response returned by the controller method is meant to be the result of executing an action as requested via HTTP.
In fact, nothing prevents you from writing and successfully compiling the following code:
1 2 3 4 5 6 7 8 9 10 |
public class BookController : Controller { // Injected in some way ... private IBookRepository _repository; public IEnumerable<Book> List() { return _repository.FindAll(); } } |
In this second code snippet, the method List returns directly a list of Book objects without an explicit preliminary JSON serialization.
Now Web API makes a point of letting you write controller methods as in the preceding code: no explicit JSON serialization is requested, but JSON serialization takes place under the hood per the default configuration.
The purpose of this article is to show that, just by tweaking the MVC infrastructure, we can make the preceding code work beautifully also in MVC3, without needing Web API.
The ContentResult Class
MVC is designed from the ground up to return ActionResult objects from controller methods. This is what always happens regardless of what the controller methods really return. When a controller method returns a type that doesn’t inherit from ActionResult, then the object returned is wrapped into a newly created instance of the ContentResult class. If the returned value is null instead an instance of the type, then the EmptyResult class is actually returned. This will guarantee that the MVC framework is guaranteed to receive an ActionResult object because both EmptyResult and ContentResult inherit from ActionResult.
The ContentResult class serves as an ActionResult-compliant container of the object returned by the controller method. It is not really a very smart container. It doesn’t really hold the instance, but only whatever it is that is returned by the method ToString on the returned object. Put another way, when a controller method returns an object that is not an ActionResult, all that the client gets is a string representation of the originally returned type. For example, consider the following method:
1 2 3 4 |
public IEnumerable List() { return new List {"Silvia", "Bob", "Alice", "Joe"}; } |
When invoked over the web, it returns a plain string such as “System.Collections.Generic.List`1[System.String]”. Such a string results from calling ToString on the newly created List<String> object.
How can we turn this string into a JSON string? And how can we do it automatically without modifying the source of the method? Well, once you get to know a bit of the internal architecture of ASP.NET MVC, it’s fairly easy. The problem is reduced to the task of replacing ContentResult with a smarter class that is aware of multiple content formatters and which doesn’t default to basic string serialization.
How to Override ContentResult?
The first option I tried was that of using an action filter. An action filter allows you to hook up the execution of each controller method. You can run your code before and/or after the method executes and you can also observe and modify the state at will. In the action filter, you override OnActionExecuting to add code before the method runs and override OnActionExecuted if you want to post-process the action results.
An action filter, however, doesn’t let you to intervene at just the right time; an action filter gives you hooks when it’s too early or too late. If you override OnActionExecuting, then you know nothing about the results being computed. If you override OnActionExecuted, then you have no chance to capture the real value being serialized-the list of strings in the previous example. All that you get in this case is instead the ContentResult instance with the return of calling ToString on the list of strings.
To change the way in which a non-ActionResult response is treated, you need to put your hands into the mechanism that governs the execution of a controller action. You need to replace the default controller action invoker.
Replacing the Default Action Invoker
The action invoker is an internal component that governs the execution of any controller method. The invoker loads any action filters bound to the action, executes the action, grabs the result and then manages to process the action result whatever that means-typically creating the HTML view or, more generally, the HTTP response packet.
The action invoker is the component that is ultimately responsible for packaging a non-ActionResult return value into a ContentResult object. To intervene at this stage, you need to create a custom action invoker and force your controller to use it instead of the default one. Let’s Suppose that you created a SimultWebApiActionInvoker class; here’s how to bind it to a given controller:
1 2 3 4 |
public YourController() { ActionInvoker = new SimulWebApiActionInvoker(); } |
The action invoker is exposed through a get/set property called ActionInvoker and exposed from the base Controller class. You typically set the custom invoker in the constructor of your app-specific controller class. What needs to be modified in the custom action invoker? You need to override the CreateActionResult method, as shown below:
public class SimulWebApiActionInvoker : ControllerActionInvoker{ protected override ActionResult CreateActionResult( ControllerContext controllerContext, ActionDescriptor actionDescriptor, Object actionReturnValue) { if (actionReturnValue == null) return new EmptyResult(); var actionResult = actionReturnValue as ActionResult;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class SimulWebApiActionInvoker : ControllerActionInvoker { protected override ActionResult CreateActionResult( ControllerContext controllerContext, ActionDescriptor actionDescriptor, Object actionReturnValue) { if (actionReturnValue == null) return new EmptyResult(); var actionResult = actionReturnValue as ActionResult; if (actionResult == null) { return new JsonResult { Data = actionReturnValue, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; } return base.CreateActionResult(controllerContext, actionDescriptor, actionReturnValue); } } |
The method is responsible for ensuring that, whatever the return value of the action method may be, it is packaged as an ActionResult; So if the action return value is neither null nor an ActionResult object, it is then packaged as a JsonResult object.
With this simple change in the controller class, you can now write action methods that return POCO objects that are automatically serialized to JSON. Great, but, one might argue, this still would require replacing the action invoker in just about every controller class; and this may add noise to the code. However, with just a bit of refactoring, you nearly get the same code as you would use with newest ASP.NET Web API.
A Bit of Refactoring
If you’re experienced with the ASP.NET Web API, you’ll know that any Web API controller must be derived from an ad hoc base class, the ApiController class. The action of the ApiController is to return data that is serialized and then sent to the client. It is a useful feature of ASP.NET MVC that you can create your own small class library with the aforementioned action invoker class and a new ApiController class, as below:
1 2 3 4 5 6 7 |
public class ApiController : Controller { public ApiController() { ActionInvoker = new SimulWebApiActionInvoker(); } } |
At this point, the controller class becomes like the following:
1 2 3 4 5 6 7 |
public class YourController : ApiController { public IEnumerable<String> List() { return new List<String> {"Silvia", "Bob", "Alice", "Joe"}; } } |
This is MVC3 code but it is undistinguishable from basic Web API code. Needless to say, Web API is not limited to this. It also offers a nice feature called content negotiation.
The Business Case for Content Negotiation
At some point in the past months, I asked the ASP.NET team to give me some concrete business reasons for picking up the ASP.NET Web API. As this API comes with .NET 4.5-which has not been released yet-I needed a strong reason to inject a beta technology in a web project. The most compelling reason that they offered for choosing Web API was its support for content negotiation. Now, content negotiation was precisely my problem at the time.
I was building a service that was expected to offer the same content, both as XML to some Flash clients, and as JSON to some mobile clients. In Web API, content negotiation refers to having client and server work together to determine the right format for data being returned, whether it is XML, JSON, or something else. The client specifies which formats it can accept through the Accept HTTP header and the server returns appropriate response if possible. You can extend this basic mechanism by adding new formatters for ad hoc XML schemas or other formats and even replace completely the standard strategy of content negotiation.
Certainly, content negotiation is a good point in favor of Web API, and an interesting feature. However, in my case, it wasn’t possible to use HTTP headers from Flash, and the content negotiation strategy I really needed could be coded easily in MVC because I could just add a parameter to the URL to request XML or JSON.
The custom action invoker was just further extended in order to check the HTTP request stream and look into the querystring parameters (or, while we are about it, the HTTP headers or cookies as well). Before packaging the return value into a JsonResult, you check whether JSON is the requested format:
1 2 3 |
var request = controllerContext.RequestContext.HttpContext.Request; var requestedContent = request.Params["f"] ?? String.Empty; var shouldSerializeJson = content.Equals("json"); |
If you support multiple serialization formats, then you will have ad hoc action result types through which you shape up the HTTP response. All this code goes into your custom extension to the ASP.NET MVC platform and doesn’t spoil controller methods.
The Bottom Line
My opinion is that Web API is not going to change much for experienced ASP.NET MVC developers. I get the feeling that Web API intends to push a pure REST approach to web development, in which the action you perform from a client, whatever it is, is expressed through a REST semantic. I believe that this is unlikely to become the primary way of doing things.
You really only need a new technology when this new technology enables you do to things that were impractical before. Sometimes, just by digging deeper into the tools you already use, you can get the results just as effectively and at a lower cost.
I’m not saying I’ll never use Web API; for sure I don’t expect to be using it until I fully embrace .NET 4.5 and ASP.NET MVC4.
Load comments