Cover

WebAPI for the MVC Guy

February 22, 2012
No Comments.

So as some of you know, I’ve spent a lot of the last year working on a web project. I’ve been using ASP.NET MVC3 and it’s going well. I am at the point where we are creating the mobile apps. I service them, I need an API (which will eventually be available as a public API too). I had started creating using MVC and simple routes but I was urged to look at the new Web API stack that is installed with the new ASP.NET MVC4 installer.

NOTE: To write this blog post, I got a lot of Twitter help from Glenn Block, Darrel Miller and and Rick Strahl!

Adding WebAPI to your Project

There are a number of demos out there that work great, but for me I don’t want to upgrade to MVC 4 yet (since at the time of this writing, it’s just in Beta, though it does have a GoLive license I believe). But I want to minimize the possibility of introducing bugs. So I want to use Web API to my existing MVC3 project. It’s actually really easy.

Assuming you’ve already installed the ASP.NET MVC 4 installer (get it here), you can use Nuget to install just the WebAPI pieces. You can use the Nuget Package Manager dialog to do it (search for “webapi” and look on the 2nd page):

2-22-2012 2-09-58 AM

Or just use the Package Manager Console and type:

Install-Package AspNetWebApi

```csharp

You now have all the assemblies required.  But now you need to wire it up.

**Creating Your First Web API**

To create your first API, you’ll need to first add a route to your Global.asax file.  First add a using (or Imports for VB) to the System.Web.Http namespace (as this adds some extension methods you’ll need). Then call the MapHttpRoute method which allows you to create routes to the Web API controllers as shown here:


```csharp
...
using System.Web.Http;

namespace WebApiForTheMvcGuy
{
  public class MvcApplication : System.Web.HttpApplication
  {
    ...
    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

      routes.MapHttpRoute("Default API Route",
        "api/1.0/{controller}/{id}",
        new
        {
          id = RouteParameter.Optional
        });

      routes.MapRoute(
          "Default", // Route name
          "{controller}/{action}/{id}", // URL with parameters
          new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
      );

    }
    ...
  }
}

Note that we have this controller before the default MapRoute of MVC so that the “api/1.0” part of the route doesn’t get caught in the route list. This implies that these routes are just part of the route list like your standard MVC routes.

Next you need an Web API controller.  The HttpRoute I show here will work with any controller, but you’ll likely need multiple routes for different styles of API calls. The best way to add the new controller is to use the Add New Item dialog:

2-22-2012 2-25-43 AM

This creates the skeleton of the project. At this point, you can actually navigate to the API to test it:

2-22-2012 2-31-34 AM

Great, we have the Web API working. I could delve forth into a discussion of how the Web API stuff works, but I think Jon Galloway’s videos do a great job of this so go watch them right now:

Ok, you’re back! Notice that the result is XML in our example. Why? Because the browser sends the Accept header of “text/xml” and not one for JSON (since the browser can’t display JSON natively). This is different from how you might have exposed data via MVC controllers. Your calls from JavaScript will include the “application/json” accept header so it should return JSON. While I could use Fiddler or other ways to force the JSON header, but wanted it to return JSON in every case. I also did not want to deal with the JSON serialization that is used by default. I wanted to see how to use the same JavaScriptSerializer that MVC uses so that any old code I had didn’t have serialization craziness.

Fun With Formatters

To get the JSON-only format for my Web APIs that I want, there are a number of approaches. But the easiest is to just remove the XML formatter from the Web API’s configuration:

  public class MvcApplication : System.Web.HttpApplication
  {
    ...
    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();

      RegisterGlobalFilters(GlobalFilters.Filters);
      RegisterRoutes(RouteTable.Routes);

      // The Web API Configuration Object
      var config = GlobalConfiguration.Configuration;

      // Remove the XML Formatter
      var xmlFormatter = config.Formatters
        .Where(f => 
          {
            return f.SupportedMediaTypes.Any(v => v.MediaType == "text/xml");
          })
        .FirstOrDefault();

      if (xmlFormatter != null)
      {
        config.Formatters.Remove(xmlFormatter);
      }
    }
  }

```csharp

I’m searching for the formatter that supports XML and removing it. When I do this, the Web API stack will default back to JSON as shown here:

![2-22-2012 3-02-08 AM](http://wilderminds.blob.core.windows.net/img/2-22-2012%203-02-08%20AM_3.png "2-22-2012 3-02-08 AM")

**A Better JSON Formatter**

As stated earlier, I am not alone in my dislike for the DataContractJsonSerializer class. Unfortunately this is the default. To fix that, we can create a new formatter for JSON and include it first in the Formatters for Web API. This means it’ll be used before the built-in one (though I decided to leave it there for back up in case there were content-types I wasn’t aware of).  Here is the formatter class:


```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.IO;
using System.Web.Script.Serialization;
using System.Net;

namespace WebApiForTheMvcGuy.Formatters
{
  // Adapted from Rick Strahl's he mentioned on Twitter
  // http://codepaste.net/dfz984
  public class JavaScriptSerializerFormatter : MediaTypeFormatter
  {
    public JavaScriptSerializerFormatter()
    {
      SupportedMediaTypes.Add(
        new MediaTypeHeaderValue("application/json"));
    }

    protected override bool CanWriteType(Type type)
    {
      return true;
    }

    protected override bool CanReadType(Type type)
    {
      return true;
    }

    protected override Task<object> OnReadFromStreamAsync(Type type, 
      Stream stream, 
      HttpContentHeaders contentHeaders, 
      FormatterContext formatterContext)
    {
      var task = Task.Factory.StartNew(() =>
      {
        using (var rdr = new StreamReader(stream))
        {
          var json = rdr.ReadToEnd();

          JavaScriptSerializer ser = new JavaScriptSerializer();

          object result = ser.Deserialize(json, type);

          return result;
        }
      });

      return task;
    }

    protected override Task OnWriteToStreamAsync(Type type, 
      object value, 
      Stream stream, 
      HttpContentHeaders contentHeaders, 
      FormatterContext formatterContext, 
      TransportContext transportContext)
    {
      var task = Task.Factory.StartNew(() =>
      {
        JavaScriptSerializer ser = new JavaScriptSerializer();

        string json = ser.Serialize(value);

        byte[] buf = System.Text.Encoding.Default.GetBytes(json);
        stream.Write(buf, 0, buf.Length);
        stream.Flush();

      });

      return task;
    }
  }
}

And then back in the configuration in Global.asax, I inserted the new formatter:

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();

  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);

  ...

  // Insert our JSON Formatter First
  config.Formatters.Insert(0, new JavaScriptSerializerFormatter());
}

```csharp

Now I can write my API using the new Web API stack and not have to upgrade to MVC 4 (yet) as well as make some minor modifications to the way it works to be more like my MVC JSON routes I already use.  Cool?

Here is the code:

- [http://wilderminds.blob.core.windows.net/downloads/webapiforthemvcguy.zip](http://wilderminds.blob.core.windows.net/downloads/webapiforthemvcguy.zip)