Ayende @ Rahien

My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:


+972 52-548-6969

, @ Q c

Posts: 6,124 | Comments: 45,475

filter by tags archive

A case study of bad API design: ASP.Net MVC Routing

time to read 2 min | 301 words

I am doing a spike in ASP.Net MVC now (and I'll talk about this at length at another time). I hit the wall when I wanted to do something that is trivially simple in MonoRail, limit a routing parameter to be a valid integer.

Luckily, just looking at the API signature told me that this is a supported scenario:


Unfortunately, that is all that it told me. This method accept an object. And there is no hint of documentation to explain what I am suppose to do with it. A bit of thinking suggested that I am probably supposed to pass an anonymous type with the key as the route parameter and the value is some sort of a constraint. But what sort of a constraint.

Type information is one of those things that static language actually do, and from experience in both dynamic and static languages, while it is often a PITA to specify types, it actually help for people who read the code. Not often, I'll admit, but it is helpful for the uninitiated.

I am... unused to having this type of problem in C#.

So I did what any developer would do, hit google and tried to find some information about it. Didn't work.

I pulled reflector and started to track down what is going on there. Following a maze of untyped paths that I have not seen the like since the 1.1 days, I finally figured out that the value that I need to push is an instance of IRouteConstraint.

Obvious, isn't it?

In short, and the reason of this post. I am seeing a lot of parameter signatures that look like that, and have barely defined semantics. I would file this under C#.Abuse();



ouch. I think a person from the MVC team is a reader of yours (not naming names), and is pretty open and responsive. perhaps something can be done.

João Bragança

I thought anonymous types couldn't implement interfaces. So why doesn't the method only accept IRouteConstraint?

Ben Scheirman

Yeah, that isn't at all discoverable.

Since your route values are just arbitrary name/value pairs, it accepts an anonymous dictionary for the constraints, which are typically regex.

something like this:

routes.MapRoute("foo", "blog/archive/{year}/{month}/{day}",

new {controller="blog", action="postsByDate"},

new {year=@"\d[4]", month=@"\d2", day=@"\d2"}

public class BlogController : Controller


public ActionResult PostsByDate(int year, int? month, int? day)





I agree that this syntax is not at all discoverable, but once you know it.... (I'd much prefer a fluent interface over this).

Ayende Rahien


There is absolutely no way I would figure out this thing out.

Even from reading the code, I found out that I needed to implement the interface.

Yuck even more.

Jeremy D. Miller

I think it's one of those cases where you really need to write your own API wrapper around routing to make it more discoverable. It's a very MS-ish API.


Then use one of the other overloads take in a RouteValueDictionary. They are very discoverable.

The object overloads was a trade-off we made in that we like the object initializer syntax for its terseness. Sure, it's not discoverable, but the other overloads are discoverable.

I forget who said it, but one rule of usable design I've heard is that if you can't make it discoverable, at least make it memorable. The idea being that now that you know this is the pattern for routes, you'll never forget it.


Whoops, the MapRoute extension methods don't have overloads that take in a proper RouteValueDictionary. My bad.

I meant, you can use routes.Add(...) which is much more explicit. The MapRoute extensions are a facade for those who like that approach using anonymous objects as dictionaries.

Ayende Rahien


There is no difference between an API that takes a dictionary of untyped values and anonymous types


Also, you can pass in a value as a string, in which case it's a regex, or a value that implement IRouteConstraint for custom constraints. For example:

new {id="\d+", method=new HttpMethodConstraint("GET"), action="foo.*"}


Its still in Beta, and overall I think its very well architected.

Andrey Shchekin

I saw this some time ago, and this is ****the place that makes me careful about 'dynamic' support.

And all what is needed to fix this a method to cast an anonymous type to an interface, like this

routes.MapRoute(..., ..., new { Controller="...", Action="..." }.Cast <i...());

This will allow developer to define some properties as required (part of the interface) and some as optional.

Even simpler would be just to use a fluent interface.

Josh N


Another example of ASP.NET MVC making the case that dynamic languages are a better general purpose language.


Yea its one part of the mvc I've come to hate, its all started on a cute little concept of being able to generate dictionarys from anon objects.. a fluent interface sat on top of standard set of classes would have more code but a lot more understandable.

Neil Mosafi

I'm sure a simple /// comment would have helped here, but I generally hate seeing these kinds of APIs in C# - it s powerful enough that you can constrain people with static types. So much for intention revealing code.

Ayende Rahien


There are different expectations for different platforms and languages.

Jeff Handley

This was part of my problem with the HtmlHelper too. The (over)use of anonymous types can lead to usability problems.


While I agree that this is a terrible syntax, google is quite helpful in this case. It showed me how to send a regex, how to make a custom constraint, and even how to use HttpMethodConstraint to put a constraint on the HttpMethod (post or get). And all this just because I read this blog post and googled "mvc routing constraints"...

Steve Wagner

I think this is only a problem of documentation. If these method are get an nice in line help which show you how to use it, its all not so hard as well.

Paul Stovell

I would have done:

routes.Add("Default", "{controller}/{action}/id")

.WithDefaults(controller => "Home", action => "View")


    controller => controller != "Secret", 

    action => Regex.Match(action, "del*"), 

    id => id >= 0);

WithDefaults would be declared as taking a params array of Func <string,> . WithConstraints would take a params array of Func <string,> . The funcs would be auto-wrapped into an IConstraint or whatever.

Intellisense would say: this takes a list of lambda's that accept a string and return a bool. The only implied part is that the "key" of the lambda must match the parameter name.

Paul Stovell

Dang HTML... Should have read "Func(Of String, String)" and "Func(Of String, Bool)", but in C#.

Comment preview

Comments have been closed on this topic.


  1. The design of RavenDB 4.0: Making Lucene reliable - 2 hours from now
  2. RavenDB 3.5 whirl wind tour: I’ll find who is taking my I/O bandwidth and they SHALL pay - about one day from now
  3. The design of RavenDB 4.0: Physically segregating collections - 2 days from now
  4. RavenDB 3.5 Whirlwind tour: I need to be free to explore my data - 3 days from now
  5. RavenDB 3.5 whirl wind tour: I'll have the 3+1 goodies to go, please - 6 days from now

And 13 more posts are pending...

There are posts all the way to May 30, 2016


  1. RavenDB 3.5 whirl wind tour (14):
    02 May 2016 - You want all the data, you can’t handle all the data
  2. The design of RavenDB 4.0 (13):
    28 Apr 2016 - The implications of the blittable format
  3. Tasks for the new comer (2):
    15 Apr 2016 - Quartz.NET with RavenDB
  4. Code through the looking glass (5):
    18 Mar 2016 - And a linear search to rule them
  5. Find the bug (8):
    29 Feb 2016 - When you can't rely on your own identity
View all series



Main feed Feed Stats
Comments feed   Comments Feed Stats