James Curran pointed me at one interesting flaw with my implementation of the DefaultValueAttribute for MonoRail I blogged about some weeks ago. This tipped me off to actually read the MonoRail code to find out how exactly MonoRail selects what overload of a ActionMethod to call.
MonoRail’s approach is as simple as it is brilliant, and reading the code that does this is a very pleasant experience. It took about 5 minutes to figure out the following:
If there are multiple public methods in a SmartDispatcherController that match the request’s action, MonoRail calculates a score of parameter points of each overload, picking the “heaviest” and executes it.
How that score is calculated is quite simple: Every matched parameter gets 10 points, unmatched 0.
But there’s more detail to this:
Every regular parameter (types not defining a attribute of IParameterBinder) where the parameter-name could be matched to the request parameter’s key, MR assumes assumes a weight of 10
In detail this means: Given the following ActionMethod with two parameters:
public void Test(string category, int page)
{
}
Monorail will assign 10 points if the key “category” could be found in the server’s request object (Request["category"]) and another 10 if a parameter key called “page” is also present.
So the following call /Test.rails?category=beer&page=1 would account for 20 parameter points, whereas omitting page would result in only 10 points. MonoRail will then pick the method with the highest score of matched parameter points and call it with those parameters.
Now, obviously the following would lead to a disambiguation:
/Test.rails?category=beer
public void Test(string category, int page)
{
}
public void Test(string category)
{
}
Category is present in both cases and page is unmatched, so both methods get 10 points and no useful distinction can be made. This is where MonoRail will award a bonus of 5 points to a method where all parameters could be matched. Thus giving Test(string) 15 points and Test(string, int) only 10, leading to the right match.
Now, in case of a parameter that is decorated with a IParameterBinder attribute (like ARFetch, DataBind etc) calculating those parameter points is delegated to the attribute class that then returns a score following it’s own logic (e.g.: if one attribute collects data from multiple request parameters it could return more than 10)
Let’s look at a sample implementation of CalculateParamPoints of the ARFetchAttribute:
public virtual int CalculateParamPoints(IEngineContext context, IController controller, IControllerContext controllerContext, ParameterInfo parameterInfo)
{
String paramName = RequestParameterName ?? parameterInfo.Name;
return context.Request.Params.Get(paramName) != null ? 10 : 0;
}
As you can see, ARFetch follows the usual MonoRail behavior and will return 10 in case it’s parameter-name could be matched, or 0 otherwise.
Still, all this doesn’t negate the fact that you could end up with ambiguities between action methods. In case many methods received the same number of parameter points MonoRail will simply call the first.
Oh, and did I mention that ASP.NET MVC can overload only on a per-http-verb basis? (Given that that’s a quite finite number of exactly 5)