Differences Between ASP.NET Web API and ASP.NET MVC – Routing

I’ve done a fair amount of work with ASP.NET MVC over the years, and I understand how routing works in that world. Routing with ASP.NET Web API is bit different though and when I first started with it, some of the basics escaped me. Primarily, there’s an additional level of logic that executes when doing request to route matching. Specifically, the HTTP verb used in the request plays a critical role in determining which controller action method is invoked. Let’s see what happens when I try to simply add an additional method (i.e., endpoint) to a Web API controller.

I’ll create a new MVC 4 Empty application in Visual Studio 2012 with two controllers with empty read/write actions. One controller is a standard MVC controller and the other is a Web API based controller. Just looking at the URL examples above the actions, we can see a difference.

image

The MVC controller shows URL examples matching the default route pattern of “{controller}/{action}/{id}”.

The Web API controller shows URL examples matching it’s default route pattern of “api/{controller}/{id}”.

Forget the difference around the Web API actions being preceded w/ “api”. That’s just a convenient way to avoid routing conflicts between MVC and Web API controllers in the same project.

I want to focus on is the lack of the “{action}” route value in the Web API controller. Notice how the URL examples don’t include the word GET. We have “api/webapi/5” instead of “api/webapi/get/5”. The “get” is missing because it’s implicit for Web API routing. When trying to find a matching “action” in a Web API controller, the verb used in the HTTP request (e.g., GET, POST) plays a critical role.

For example, when a GET request comes in for the Web API controller, the possible matching methods to execute are all methods whose name starts with “GET”. This is by convention, but you can override this by annotating additional methods with an “[HttpGet]” attribute in this case.

Okay, so we understand a fundamental difference in how routing is done out of the box. Now, let me show you one of the pitfalls I ran into when trying to add some additional functionality to a Web API controller.

Let’s say we want to expose an additional endpoint that “gets” a different set of data than the default “Get()” endpoint we already have. The current “Get()” endpoint might return *all* of the data in a database table while our new endpoint returns a subset of that data. We’ll add a new method called “GetSubsetOfData()”.

image

Adding this new method has now broken this controller. If we issue a GET to “/api/WebApi”, we get an HTTP 500 back now.

Here’s the request in Fiddler.

image

Notice the exception text in response details – “Multiple actions were found that match the request”.

Looking back at our controller, we added a new method that by convention is also available to handle a GET request to the Web API controller. The request to “/api/WebApi” could be served by either the “Get()” method or our new “GetSubsetOfData()” method. Resolution is ambiguous here so Web API throws an exception. Thankfully, it’s descriptive.

If we weren’t aware of the routing logic differences between ASP.NET MVC and Web API, we might be tempted to change our request to “/api/webapi/GetSubsetOfData” thinking that explicitly naming the method would help with resolution. It doesn’t.

image

Now we get an HTTP 400 back. Again, the exception details show us what went wrong.

The request /api/webapi/GetSubsetOfData” matches the default Web API route “api/{controller}/{id}” and in our Web API controller, the matching method is “Get(int id)” which means we’re trying to the string value “GetSubsetOfData” to an integer which obviously won’t work.

Okay, so how do we get what we want? The easiest solution would be to add a new route which includes an “action” parameter.

Adding this to our route configuration file, we get:

Now we can successfully request “/api/webapi/GetSubsetOfData“. Progress!

image

But we haven’t resolved the original issue with the ambiguous method selection when just requesting “/api/webapi”. We’ll still get the same HTTP 500 we received before if we request that URL.

You might be thinking we can just update all of the Web API calling code to request “/api/webapi/get” instead. That way we explicitly include the “action” value. But the problem with this approach is that we might not control all of the source code hitting our Web API endpoints. We might be a service (e.g., stock quote data) that’s hit by an unknown set of clients whose code we can’t control and don’t want to break. That’s the big downside here.

One fix I thought would work was the addition of a custom ActionMethodSelectorAttribute. With an ASP.NET MVC controller, we can use a class that derives from this attribute to further control which methods are “candidates” for handling the request, but you can’t do this with a Web API controller. The attribute has no affect if applied to a method. We’re out of luck (currently) trying this approach.

A final workaround which does solve our issue is to drop the separate “GetSubsetOfData()” method and add additional optional parameters to the “Get()” method which can be used as a flag to select either returning all of the data or a subset. The additional parameter would be passed in the query string of the request.

This feels similar to leveraging the OData protocol to tweak the data returned. Web API had limited support for OData in the Beta release of MVC 4, but for RTM, it’s been whacked completely. There’s a CodePlex discussions for the ASP.NET project which provides some details on this reasoning. In short, richer support for OData should be coming in a future update and that’s probably the approach I’ll take once it’s available.

If you’ve made it this far, thanks for following my stumbles. I certainly didn’t think it would be so difficult when I started out. Hopefully, I’ve been able to give you a head start in understanding some of the fundamental routing differences between ASP.NET Web API and MVC.

Hope this helps.

26. July 2012 by Mark Berryman
Categories: MVC, OData, Web API | 6 comments

Comments (6)

  1. Thanks for the post. So far, you’re the only one to attempt to explain this. I wanted to have routes similar to:

    /Services/Filings/225-5
    /Services/FilingsByItemNo/5

    But I could not get this to work. In some ways I think the “Conventions” make this much more difficult than it should be. So, how I finally got this working was changing.

    my route definition:
    config.Routes.MapHttpRoute(
    name: “DefaultApi”,
    routeTemplate: “Services/{action}/{id}”,
    defaults: new { controller = “Services”, id = RouteParameter.Optional }
    );

    my controller actions:
    public class ServicesController : ApiController {

    [HttpGet]
    public string Filings(string id) {
    return “Filings: ” + id;
    }

    [HttpGet]
    public string FilingsByItemNo(string id) {
    return “FilingsByItemNo: ” + id;
    }
    }

    Still not sure if this is the way to do this but it appears to work. And makes the most sense to me. Thanks for helping me get there.

    • Hi Darren,

      Glad to hear the post helped you. Your code looks good for achieving what you want, but you might not want to overwrite the “DefaultApi” route definition. In my example, I added a new route to be able to handle an “{action}” value while leaving the original “DefaultApi” route alone.

      Conventions can trip people up, but in my experience, they’re much preferred over configuration once you learn them. Of course it’s that “learning” process which can be a bit painful.

  2. One controller is a standard MVC controller and the other is a Web API based controller.

  3. ASP.NET MVC controller, we can use a class that derives from this attribute to further control which methods are candidates for handling the request, but you can’t do this with a Web API controller.

  4. Hi Mark Berryman

    It appears that you have totally misunderstood the whole concept of REST and Web Api.

    You’re confusing REST with RPC as in “WCF” or similar. MVC-routing somewhat is a hybrid between RPC and REST, but still extremely far away from the message based pattern that MVC originally was targeted at.

    Web Api is in fact one of the best approaches we’ve seen from Microsoft in the latest many years when it comes to messaging.

    • Why are ApiControllers even called “Controllers” at all? Its a terrible choice of name – they aren’t controllers in the traditional sense of the word.

Leave a Reply

Required fields are marked *

*