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

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:

image

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();

Print | posted on Wednesday, November 05, 2008 9:16 PM

Feedback


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 9:38 PM josh

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.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 9:45 PM João Bragança

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


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 9:46 PM 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).


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 9:50 PM Ayende Rahien

Ben,
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.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 9:57 PM 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.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 10:43 PM Haacked

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.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 10:46 PM Haacked

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.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 10:46 PM Ayende Rahien

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


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 10:48 PM Haacked

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.*"}


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 11:11 PM Demis

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


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/5/2008 11:24 PM 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());

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.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/6/2008 12:04 AM Josh N

Ayende,

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



Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/6/2008 12:30 AM Stephen

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.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/6/2008 12:46 AM Andrew Peters

Just a little bit of Rails love creeping into MVC. The key difference? With Rails I expect to have to use the docs and, luckily, they are great:

api.rubyonrails.org/.../UrlHelper.html#M001190


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/6/2008 1:19 AM Neil Mosafi

I'm sure a simple ///<summary> 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.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/6/2008 1:50 AM Ayende Rahien

Andrew,
There are different expectations for different platforms and languages.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/6/2008 3:24 AM Jeff Handley

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


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/6/2008 4:00 AM configurator

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"...


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/6/2008 5:39 AM Haacked

This is just one of those areas we made a trade-off in favor of usability over discoverability.

rickosborne.org/.../usability-vs-discoverability/
www.scottberkun.com/.../26-the-myth-of-discover...


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/6/2008 9:43 AM Torkel

I also found problems in the routing API, I created a custom fluent interface by overloading the Route class:

www.codinginstinct.com/.../...luent-interface.html
www.codinginstinct.com/.../...e-in-mvccontrib.html


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/6/2008 5:02 PM 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.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/7/2008 5:05 AM Paul Stovell

I would have done:

routes.Add("Default", "{controller}/{action}/id")
.WithDefaults(controller => "Home", action => "View")
.WithConstraints(
controller => controller != "Secret",
action => Regex.Match(action, "del*"),
id => id >= 0);

WithDefaults would be declared as taking a params array of Func. WithConstraints would take a params array of Func. 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.


Gravatar

# re: A case study of bad API design: ASP.Net MVC Routing 11/7/2008 5:07 AM Paul Stovell

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

Comments have been closed on this topic.