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
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.
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 } );
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.Said Another Way
My solution came down to down to separating controllers a bit more so /api/XXXX
didn't get too crowded.
Category1
), and put api controllers within the folder. Widget1
in the Category1
folder gets a default namespace of UI.Category1.Widget1
. /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 myRouteConfig.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;
}
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:
action
similar to MVC) but this is generally not meant to be used.