WebAPI for the MVC Guy

Audio and video plugs in handSo 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

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:

...
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);
      }
    }
  }

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

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:

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());
}

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:

 

Comments

Gravatar

Marcin Dobosz Wednesday, February 22, 2012

Cool stuff. Quick comments
1. The WebAPI item template will automatically install the required Nuget packages.
2. Don't use Task.Factory.StartNew for perf reasons. Run synchronously and return a completed Task.

Gravatar

Eric Williams Wednesday, February 22, 2012

And for a better and faster JSON Formatter use Servicestack.Text, https://gist.github.com/1881241

Gravatar

Shawn Wildermuth Wednesday, February 22, 2012

Marcin,

Cool, I didn't see the item template. Good info.

As for, the Task.Factory - since it's the async version of the override, can I assume that it is being called async for us?

Gravatar

Mike Wilson Wednesday, February 22, 2012

Shawn,

You mention you dislike the DataContractJsonSerializer class, but didn't explain why. I'm curious on why that is, would you mind explaining a bit?

Gravatar

Jeff Ammons Wednesday, February 22, 2012

Good podcast on the WebAPI bits over at Herding Code: http://herdingcode.com/?p=396

Jon Galloway interviews Brad Wilson.

Shawn, I'm not sure if 100% of the async bits are working if you are using .net 4.0. In the podcast it sounds like there is some more work to be done in that area before release. I was driving while listening so bits of the discussion may have been drowned out by honking, cursing, and screaming.

Gravatar

Shawn Wildermuth Wednesday, February 22, 2012

Mike,

Two problems I've faced are with DateTime serialization and anonymous serialization.

Gravatar

Shawn Wildermuth Wednesday, February 22, 2012

Jeff,

Thanks for the info!

Gravatar

ms440 Thursday, February 23, 2012

Hi Shawn,

In his recent post "Using JSON.NET with ASP.NET Web API" Henrik Nielsen is using Json.NET as MediaTypeFormatter. I though it could be interesting for you. Take a look.

Thanks a lot for your post. As always, I'm learning a lot from you.

Gravatar

ms440 Thursday, February 23, 2012

oops, forgot the url. Here it is: http://blogs.msdn.com/b/henrikn/archive/2012/02/18/using-json-net-with-asp-net-web-api.aspx

Gravatar

Shawn Wildermuth Thursday, February 23, 2012

Thanks ms440 but I used that blog post as a reference too.

Gravatar

Anonymous Coward Thursday, February 23, 2012

Hi Shawn,

I noticed that you're using a return statement in your "Where" clause's lambda expression in line 18 of your Global.asax file. Is there a reason why you're doing this (short-circuiting execution or something similar) or was this just a typo? I've never seen this done before and I can't see any obvious benefit. Also, you could've gotten rid of the Where + FirstOrDefault combination and just used the FirstOrDefault with a lambda expression as parameter instead.

Gravatar

Shawn Wildermuth Thursday, February 23, 2012

Anon:

I did that because I wrote that at 4am last night....no actual reason ;)

Gravatar

Peter Johansson Thursday, February 23, 2012

Hi,

Could you please let me know the best way to secure a service like this? I want to make sure that the client calling my rest based service is a known client. I also want to make sure that the solution used to secure the service is platform independent in terms of the client calling the service. Any pointers?

Gravatar

Glenn Block Sunday, February 26, 2012

Hi Shawn

Nice post :-)

Here's a simple way (hack) to get rid of the formatter rather than doing the for loop. This just came to me and I tested and it works:

GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();

Our conneg algorithm looks at the media types hanging of the formatter. This just clears them for the XmlFormatter making it no up.

i can't believe I didn't figure this out before!

Gravatar

Shawn Wildermuth Sunday, February 26, 2012

Glenn,

Thanks! Great tip.

Gravatar

Shawn Wildermuth Sunday, February 26, 2012

Peter,

It matters your client. I would say Forms Auth if the client are users of a smart client (e.g. Silverlight or something). Else OAuth is a good solution. I'll be working up a blog post eventually on what we did for FirstInked once we're done.

Gravatar

Lasse Monday, February 27, 2012

Nice, can't wait to get home and check this out. A have a quick question though - i see you're using the route "api/1.0" which indicates that you want to version the api, but how do you actually do this? If we for example create another route, say "api/2.0", how can we be sure that the request goes to my "Api/2.0/ProductController" and not "Api/1.0/ProductController"?

Gravatar

Shawn Wildermuth Monday, February 27, 2012

Lasse,

My plan (though this is all new to me) is to have separate controllers for v2 if necessary. I might end up using Areas for versions if needed and defer to controllers by deriving and overriding but I am not positive yet.

Gravatar

Sagy Monday, March 19, 2012

Hi,
For some reason my default formatter is Json. If I add a line similiar to
GlobalConfiguration.Configuration.formatters.Remove(formatters.JsonFormatter)
I'm getting an exception:
The 'XmlSerializer' serializer cannot serialize the type 'DelegatingEnumerable`1'.

Thanks,
Sagy

Gravatar

Shawn Wildermuth Monday, March 19, 2012

Sagy,

Formatters are used based on accept headers. When you remove the JSON formatter, you're suggesting that it fallback to XML in every case regardless of the accept header.

Gravatar

Raviteja Thursday, March 22, 2012

Hi,
i want to count the items in the result and append to the result.
That i want to do in customformatter. can you help me out with this.

Thanks,
Raviteja

Gravatar

George Diab Saturday, May 19, 2012

things are working well, but I have a question. when trying to do a PUT (for example) the OnReadFromStreamAsync catches the request but never sends it to my Controller. What am I doing wrong?

Gravatar

Shawn Wildermuth Saturday, May 19, 2012

George,

You're likely calling it from a webpage. Most browsers only support GET/POST, not PUT/DELETE/OPTIONS (etc.)

Gravatar

George Diab Saturday, May 19, 2012

Using Fiddler. The PUT worked when I was using the built-in mvc4 json formatter

Gravatar

Shawn Wildermuth Saturday, May 19, 2012

Then it likely that the json you're sending isn't correct as far as the new formatter is concerned. This is often the case with dates. Are you using any?

Gravatar

George Diab Saturday, May 19, 2012

yes, although it did seem so create my object properly. I'll confirm that it is a date issue. if it is, how should I handle?

(and thank you for your help!)

Gravatar

George Diab Monday, May 21, 2012

Yup. that was it. Thank you so much.

Gravatar

George Diab Monday, May 21, 2012

althought there is a new error with PUT when using a custom MediaTypeFormatter. this seems to be a bug in the beta? Have you come across this? http://geekswithblogs.net/jdothoffman/archive/2012/03/06/mvc-4-beta-web-api-bug-with-binding-route-values.aspx


Leave a Comment

*
*
*