31

In the 5-minute video at the following link, at the 1:10 mark, Jon Galloway says that adding a method called DeleteComment to his CommentsController controller class will by convention map automatically to the delete http verb.

How does MVC with WebApi know how to rout the methods to the right verbs? I know the routing in the global.asax.cs file routes requests to the correct controller, but how does a delete request get "mapped by convention" to the delete method, or any method? Especially when there can be more than 1 method for each verb? "By convention" makes me think it's just looking at the first word in a method name ... but if so, it would have to read the signature of the methods to tell two delete methods or two get methods apart ... and where is all this defined?

Video: http://www.asp.net/web-api/videos/getting-started/delete-and-update

Thanks!

Edit: Here is the code in the sample ValuesController class that comes in the WebApi template. This was the source of my original question. How does the "convention" that differentiates between these (and any other methods in the controller) work?

// GET /api/values
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET /api/values/5
    public string Get(int id)
    {
        return value;
    }
Andrew B Schultz
  • 1,512
  • 1
  • 23
  • 41
  • 3
    In WebApi I _think_ you are supposed to have a controller per resource so you wouldn't have two delete methods. – Shane Courtrille May 03 '12 at 21:59
  • 1
    Thanks Shane, that's good info, but what I mean is specifically like a single record method and a multiple-record method. GET is probably a more appropriate example, but you might have a method that gets one record by an ID, and another that gets all the records with a certain FK. But they're both using the GET verb; I don't understand where the routing intelligence that discerns between these exists. – Andrew B Schultz May 04 '12 at 21:17

2 Answers2

60

I apologize in advance, this post strays a bit from what you asked, but all of this bubbled up when I read your question.

WebAPI Matching Semantic
The matching semantic used by (the default routes in) WebAPI is fairly simple.

  1. It matches the name of the action with the verb (verb = GET? look for method name starting with "get")
  2. if a parameter is passed, the api seeks an action with a parameter

So in your code sample a GET request without a parameter matches the Get*( ) function without an parameters. A Get containing and ID looks for a Get***(int id).

Examples
While the matching semantic is simple, it creates some confusion for MVC developers (well at least this developer). Lets look at some examples :

Odd Names - Your get method can be named anything, so long as it starts with "get". So in the case of a widget controller you can name your functions GetStrawberry() and it will still be matched. Think of the matching as something like : methodname.StartsWith("Get")

Multiple Matching Methods - What happens if you have two Get methods with no parameters? GetStrawberry() and GetOrange(). As best I can tell, the function defined first (top of the file) in your code wins ...strange. This has the side effect of making some methods in your controller unreachable (at least with the default routes)....stranger.

NOTE : the beta behaved as above for 'matching multiple methods' - the RC & Release version is a bit more OCD. It throws an error if there are multiple potential matches. This change removes the confusion of multiple ambiguous matches. At the same time, it reduces our ability to mix REST and RPC style interfaces in the same controller, relying on the order & overlapping routes.

What to do?
Well, WebAPI is new and consensus is still coalescing. The community seems to be reaching for REST principles quite a bit. Yet, not every API can or should be RESTful, some are more naturally expressed in an RPC style. REST & what people call REST seems to be the source of quite a bit of confusion, well at least to Roy Fielding.

As a pragmatist, i suspect that many API's will be 70% RESTful, with a smattering of RPC style methods. First, the the controller proliferation alone (given the webapi binding method) is going to drive developers bonkers. Second, WebAPI doesn't really have a built in way to create a nested structure of api paths (meaning: /api/controller/ is easy, but /api/CATEGORY/Sub-Category/Controller is doable, but a pain).

From my perspective, I would love to see the webAPI folder structure control the default API paths... meaning if I create a Category folder in my UI project then /api/Category would be the default path (something parallel to this MVC article).

What did I do?
So, I had a few requirements: (1) to be able to use restful syntax in most case, (2) have some "namespace" separation of controllers (think sub-folders), (3) be able to call additional rpc-like methods when necessary. Implementing these requirements came down to clever routing.

// SEE NOTE AT END ABOUT DataToken change from RC to RTM

Route r;
r = routes.MapHttpRoute( name          : "Category1", 
                         routeTemplate : "api/Category1/{controller}/{id}", 
                         defaults      : new { id = RouteParameter.Optional } );
r.DataTokens["Namespaces"] = new string[] {" UI.Controllers.Category1"};

r = routes.MapHttpRoute( name          : "Category2", 
                         routeTemplate : "api/Category2/{controller}/{id}", 
                         defaults      : new { id = RouteParameter.Optional } );
r.DataTokens["Namespaces"] = new string[] {" UI.Controllers.Category2"};

routes.MapHttpRoute(     name          : "ApiAllowingBL", 
                         routeTemplate : "api/{controller}/{action}/{id}",
                         defaults      : new { id = RouteParameter.Optional } );

routes.MapHttpRoute(     name          : "DefaultApi",  
                         routeTemplate : "api/{controller}/{id}",           
                         defaults      : new { id = RouteParameter.Optional } );
  • The first two routes create "sub-folder" routes. I need to create a route for each sub-folder, but I limited myself to major categories, so I only end up with 3-10 of these. Notice how these routes add the Namespace data token, to restrict what classes are searched for a particular route. This corresponds nicely to the typical namespace setup as you add folders to a UI project.
  • The third route allows specific method names to be called (like traditional mvc). Since web API does away with action name in the URL, it's relatively easy to tell which calls want this route.
  • The last route entry is the default web api route. This catches any classes, particularly ones outside my 'sub-folders'.

Said Another Way
My solution came down to down to separating controllers a bit more so /api/XXXX didn't get too crowded.

  • I create a folder in my UI project(lets say Category1), and put api controllers within the folder.
  • Visual studio naturally sets class namespaces based on folder. So Widget1 in the Category1 folder gets a default namespace of UI.Category1.Widget1.
  • Naturally, I wanted api URLs to reflect the folder structure (/api/Category1/Widget). The first mapping you see above accomplishes that, by hard coding /api/Category1 into the route, then the namespace token restricts classes that will be searched for a matching controller.

NOTE: as of the release DataTokens are null by default. I'm not sure if this is a bug, or a feature. So I wrote a little helper method and added to my RouteConfig.cs file....

r.AddRouteToken("Namespaces", new string[] {"UI.Controllers.Category1"});

private static Route AddRouteToken(this Route r, string key, string[] values) {
  //change from RC to RTM ...datatokens is null
if (r.DataTokens == null) {
       r.DataTokens = new RouteValueDictionary();
    }
    r.DataTokens[key] = values;
    return r;
}

NOTE 2: even thought this is a WebAPI 1 post, as @Jamie_Ide points out in the comments the above solution doesn't work in WebAPI 2 because IHttpRoute.DataTokens has no setter. To get around this you can use a simple extension method like this:

private static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraints, string[] namespaceTokens)
{   
    HttpRouteValueDictionary    defaultsDictionary      = new HttpRouteValueDictionary(defaults);
    HttpRouteValueDictionary    constraintsDictionary   = new HttpRouteValueDictionary(constraints);
    IDictionary<string, object> tokens                  = new Dictionary<string, object>();
                                tokens.Add("Namespaces", namespaceTokens);

    IHttpRoute route = routes.CreateRoute(routeTemplate, defaultsDictionary, constraintsDictionary, dataTokens: tokens, handler:null);
    routes.Add(name, route);

    return route;
}
EBarr
  • 11,826
  • 7
  • 63
  • 85
  • 1
    Thanks EBarr, that's very helpful. Your solution at the end is a bit over my head, but the first part definitely answered my question, and I think the last part will be extremely useful to revisit after I've learned a bit more. I had suspected that there was some sort of comparison between a method name in your controller class and the http verbs, but until someone confirmed it and told me how it worked I didn't really believe my own suspicion. Thanks! – Andrew B Schultz May 08 '12 at 19:45
  • @AndrewBSchultz - glad to help. ..posting a bit better solution explanation. – EBarr May 08 '12 at 22:02
  • That's a very useful answer, thank you! Just a quick question, instead of using the custom routes with the "Namespaces" datatoken, could you do it with the traditional MVC "Areas"? – Marco May 23 '12 at 14:07
  • Personally, I've only puttered with areas as I haven't felt much need for them. Apparently, with a little work, you can use areas: http://stackoverflow.com/questions/9836030/mvc-4-webapi-controllers-splitted-by-areas and http://stackoverflow.com/questions/9485934/how-do-i-register-a-controller-that-has-been-created-in-an-area – EBarr May 23 '12 at 14:22
  • 1
    if you would like to have urls broken into subfolders (or I have heard it referred to as 'by namespace') like http://localhost:xxxx/api/mySubfolder/myController and you are putting your controllers in subfolders under controllers you can use {path}. Example:---- file: MyProject\Controllers\Users\AdminUsersController.cs---- maps to uri: http://localhost:xxxx/api/Users/AdminUsers---- routing code: RouteTable.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{path}/{controller}/{id}", defaults: new { id = RouteParameter.Optional }); – Mario Feb 28 '13 at 16:14
  • @Mario -- I know this is old, but do you have a working sample or can point any documentation? I've circled back and this does not seem to work, and looking at the webapi 1.0 source code I don't see how it could. Path is design time knowledge; no idea how the run time could infer or understand a path attribute. – EBarr Feb 12 '14 at 23:00
  • Great explanation. This was very confusing and even counter-intuitive when I first dealt with it. I assumed Web API would behave in a manner more typical of ASP.NET-MVC action/controller routing when called. Very good explanation. Web API 2 gives extra flexibility to use multiple `Get` actions with attribute routing. – Dave Alperovich Feb 19 '14 at 23:51
  • 3
    In Web Api 2, IHttpRoute.DataTokens has no setter so this solution no longer works. I'm starting to get the hint that Web Api really does not want to accommodate this style of routing. You can always organize your controllers into folders, and you can define routes to access them, however they are still accessible via the default route and duplicate names will throw runtime exceptions. – Jamie Ide Feb 21 '14 at 15:21
3

This comes up quite often. And there are different views on that. I personally have not subscribed to any particular idea for now but it seems the one with one-controller-per-resource to be the most popular among the REST community.

So basically you can:

  1. Expose action in your route (Web API treats the word action similar to MVC) but this is generally not meant to be used.
  2. Define methods with different parameters as per this post
  3. As I said, most recommended is using one controller per resource. So even in the Web API sample, the controller for collection of an entity is different for the controller for the entity itself. Read this post by Rob Conery and here is Glenn's answer.
Aliostad
  • 80,612
  • 21
  • 160
  • 208
  • Thanks Aliostad good articles and answer - but one followup: I'm specifically asking about this (this comes in the sample code int he ValuesController class in a WebApi template project). I don't think this is what Glenn is talking about, because this is all in one controller: I forgot these comments don't format well. I've appended the code I meant to paste here to my original post. – Andrew B Schultz May 04 '12 at 21:30