I've managed to figure out how to do this without using a custom binding. The trick is realising that if I have things like Supplier.VendorId="XXXX"
as part of the query string of the action link that gets rendered then it gets mapped correctly.
I used reflection to see what HtmlHelper.ActionLink()
does when it's passed an object, which is that it creates an instance of RouteValueDictionary<string, object>
, which creates a key for each property of the object.
This default implementation is close to what I want, but I need it to deal with properties of properties.
Luckly there's an overload of ActionLink()
that takes a RouteValueDictionary<string, object>
directly, so that left me with the problem of constructing one with the Property.SubProperty
type keys correctly.
I ended up with the code below, which firstly uses the RouteValueDictonary
's constructor to get a key for each property.
Then it removes any that won't get bound according to the Bind
attribute (if the class has one), which tidies up the resulting querystring quite a bit.
The main part it does though is to look for any properties of type ...Model
(the type name ending with "Model") and add that object's properties to the dictionary. I needed to use some rule for whether to recurse or not otherwise it would try and walk the properties of things like lists of objects etc. I figure that I'm already using a convention for my model classes, so I could stick to it.
public static RouteValueDictionary ToRouteValueDictionary(this object obj)
{
var Result = new RouteValueDictionary(obj);
// Find any ignored properties
var BindAttribute = (BindAttribute)obj.GetType().GetCustomAttributes(typeof(BindAttribute), true).SingleOrDefault();
var ExcludedProperties = new List<string>();
if (BindAttribute != null)
{
ExcludedProperties.AddRange(BindAttribute.Exclude.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
}
// Remove any ignored properties from the dictionary
foreach (var ExcludedProperty in ExcludedProperties)
{
Result.Remove(ExcludedProperty);
}
// Loop through each property, recursively adding sub-properties that end with "Model" to the dictionary
foreach (var Property in obj.GetType().GetProperties())
{
if (ExcludedProperties.Contains(Property.Name))
{
continue;
}
if (Property.PropertyType.Name.EndsWith("Model"))
{
Result.Remove(Property.Name);
var PropertyValue = Property.GetValue(obj, null);
if (PropertyValue != null)
{
var PropertyDictionary = PropertyValue.ToRouteValueDictionary();
foreach (var Key in PropertyDictionary.Keys)
{
Result.Add(string.Format("{0}.{1}", Property.Name, Key), PropertyDictionary[Key]);
}
}
}
}
return Result;
}