ASP.NET Web API provides an IUrlHelper interface and the corresponding UrlHelper class as a general, built-in mechanism you can use to generate links to your Web API routes. In fact, it’s similar in ASP.NET MVC, so this pattern has been familiar to most devs for a while.

The main problem of it is that it’s based on magic strings, as, to generate a link, the route name has to be passed as a string literal. Moreover, all the parameters that are required to built up the link, are simply a set of name-values, represented by a dictionary or an anonymous object, which is hardly optimal. Code is not coherent, refactoring becomes a pain and in general error potential is high.

My friend, and one of the most respected folks in the Web API community, Pedro Felix, has created a library called Drum, designed to avoid the pitfalls of the UrlHelper, allowing you to build links for Web API direct routing (introudced in Web API 2) in a strongly typed way.

In either case, this is replacing your usual call to MapHttpAttributeRoutes.

The registration shown above, wires up all the necessary Drum infrastructure and enables you to reach into the current HttpRequestMessage at any time, and obtain an instance of UriMaker, which you can then use for constructing URIs in a strongly typed manner. To do that, use the relevant extension method, as shown below:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

publicclassMyController:ApiController

{

publicHttpResponseMessage GetAll()

{

//omitted for brevity

}

publicHttpResponseMessage GetById(intid)

{

//omitted for brevity

}

}

//generate links to this controller accordingly:

varuriMaker=request.TryGetUriMakerFor<MyController>();

Uri link=uriMaker.UriFor(c=>c.GetAll());

Uri differentLink=uriMaker.UriFor(c=>c.GetById(7));

For links requiring a body (complex parameter), you can use the helper Param to stub any value, since they would not affect URI anyway:

C#

1

2

3

4

5

6

7

8

9

publicclassMyController:ApiController

{

publicHttpResponseMessage Put(intid,MyModel model)

{

//omitted for brevity

}

}

Uri link=uriMaker.UriFor(c=>c.Put(7,Param<MyModel>.Any));

The main advantage of such approach is obviously you now have compile-time safety of your links.

Under the hood

Drum stores the instance of a UriMakerContext in the Properties dictionary of the HttpConfiguration (as a singleton). That class is a de facto factory for UriMaker instances, which are directly responsible for building up URIs whenever you need them.

When you call the MapHttpAttributeRoutesAndUseUriMaker extension method against your HttpConfiguration, the library will wrap your IDirectRouteProvider (if you don’t hve a custom one, it will simply wrap the built-in attribute routing) with its own internal IDirectRouteProvider (a classic example of a decorator pattern), and will create a mapping between all routes and all of the MethodInfo to which the routes are pointing.

This mapping is stored in the private field of UriMakerContext and is later used by UriMaker to generate links – as it will use the MethodInfo from your expression (such as i.e. uriMaker.UriFor(c => c.GetAll());) to look up the corresponding route. Then, internally, using the route name, UriMaker uses UrlHelper to construct the link.

To do that, UriMaker obviously needs the route parameters (route values) as well – so it converts the parameters that you pass into the strongly typed expression, into Dictionary<string, object>, which is what is required by UrlHelper.

It’s all pretty clever if you ask me, and works really well.

Next

I am very excited about Pedro’s work. It’s always these little add-ins and libraries that you can throw into your framework, to solve some of its deficiencies (such as magic string link generation that Web API offers), that make your everyday coding enjoyable.

I already had a small contribution to the library, and I’m sure Pedro will be happy to hear what you think, and maybe what you’d change or suggest.

Hyperlinkr doesn’t work with direct routing (Web API 2), only the old centralised routing, Drum on the other hand works *only* with direct routing.

IMHO direct routing is far superior, and should be the only routing mechanism used going forward

http://abatishchev.ru abatishchev

I’d say that convention (global routing) should go over configuration (custom attribute routing). With later urls become unpredictable what decreases maintainability.

http://www.strathweb.com/ Filip W

convention in Web API is stuff like verb based dispatching – which is still part of direct routing.

On the other hand, global routing is not convention, it’s simply matching controllers by name – this can;t be called convention, since it has to be defined up front (the catch-all routes that is). Moreover, far worse than that, is the fact that all this happens in a way that the route is completely disassociated from the resource it represents. This artificial divide is completely against the nature of building APIs

as a result, global routing is *anything but* maintainable.
building any kind of large API with hierarchical routing is virtually impossible to maintain and troubleshoot with global routing.

there is a reason why pretty much every reasonable web framework (rails, express and other node.js fx-es, nancy, servicestack etc) use routes that are tied to resources not centralized

http://abatishchev.ru abatishchev

Initially I thought global routing configuration isn’t so bad and is similar to the convention like controllers are under Controllers and views are under Views. But you convinced me and I agree it’s too disconnected and disassociated from the rest

Let’s devide it into two parts: GlobalConfiguration.Routes (bad thing) and /{controller}/{action} (in my opinion and my general point – good thing). First should go away, and latter should stay.
That’s what I called convention (over configuration), and breaking it makes things worse.
With attribute routing you can either break it (specify RPC like route only, e.g. /products/search/barcode) or persist it (have at least REST like route in place and optimally add whatever you want or need). Having the default route makes things better.

http://friendfeed.com/jokeyxero jokeyxero

Brilliant! Thank you and Pedro for this! With Microsoft’s heavy focus on enabling “RESTful APIs” you’d think they would have a better built-in solution for creating links. This will help a lot until they figure it out. Maybe we should add a suggestion for moving this approach/code into the default stack.