DefaultValue attribute for Castle MonoRail
August 4th, 2009 . by Daniel HölblingWhile reading through ScottGu’s announcement of the ASP.NET MVC 2 Preview 1 I noticed this rather interesting little feature that’s in there:
MonoRail is much smarter about action methods than MVC so there are already things going on with default values through routing etc. But this particular thing wasn’t in the framework until now. So I took Ken Egozi’s sample about using IParameterBinder to implement the DefaultValueAttribute in MonoRail.
The result in syntax is identical to ASP.NET MVC 2 P1 and it was very easy to do:
public void Browse([DefaultValue("beer")] string category, [DefaultValue(1)] int page)
{
}
How is this done? Well, I suggest you read Ken Egozi’s post since he does a much better job at explaining that thing. Anyway, here is the code to make that happen:
using System;
using System.Reflection;
using Castle.MonoRail.Framework;
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public class DefaultValueAttribute : Attribute, IParameterBinder
{
private readonly object value;
public DefaultValueAttribute(object value)
{
this.value = value;
}
public int CalculateParamPoints(IEngineContext context, IController controller, IControllerContext controllerContext, ParameterInfo parameterInfo)
{
var token = context.Request[parameterInfo.Name];
if (CanConvert(parameterInfo.ParameterType, token))
return 10;
return 0;
}
private static bool CanConvert(Type targetType, string token)
{
if (token == null)
return false;
try
{
Convert.ChangeType(token, targetType);
return true;
}
catch (FormatException)
{
return false;
}
}
public object Bind(IEngineContext context, IController controller, IControllerContext controllerContext, ParameterInfo parameterInfo)
{
string token = context.Request[parameterInfo.Name];
Type type = parameterInfo.ParameterType;
if (CanConvert(type, token))
return Convert.ChangeType(token, type);
return value;
}
}




I not sure about this, (as neither you nor Ken really explains CalculateParamPoints), but I think you are saying that, given the method:
public void Browse([DefaultValue("beer")] string category, int page)
then,
/Home/Browser.rails?category=xxxx&page=2
is a better match for that method than
/Home/Browser.rails?page=2
However, with the DefaultValue, both are equally good. The DefaultValue attribute shouldn’t affect how good a match a request is to a method, so it really should just return 0 for everything.
Very good point. I’ll try that out and possibly update the post.
Hmm.. actually a tough question.
I’m not totally sold on making it’s weight 0. I see your point with the DefaultValue being a value after all so it shouldn’t matter if a parameter is passed or not, but setting it to 0 would also take away all the weight from that method overload..
I’ll think about it a bit further and maybe we’ll continue this discussion on the castle mailing list
Anyway, thanks for your comment!
Thinking about this more, you are right.
If we return 0, then
/Home/Browser.rails?page=2
would probably be a non-match for
public void Browse([DefaultValue("beer")] string category, int page)
which is not what we want.
However, if we return 10, then for
/Home/Browser.rails?page=2&source=db
then
public void Browse([DefaultValue("beer")] string category, int page)
and
public void Browse(int page, string source)
become equally good, which is also not what we want.
I think always returning 5 may work best, but mostly I think that this is going to require a bit more investigation of how CalculateParamPoints works. (I smell at least one of us getting a blog post out of that)
[...] Curran pointed me at one interesting flaw with my implementation of the DefaultValueAttribute for MonoRail I blogged [...]