Reputation: 248
I was wondering if there is an elegant way to add an array of complex types to a RouteValueDictionary or compatible type?
For example, if I have a class and an action:
public class TestObject
{
public string Name { get; set; }
public int Count { get; set; }
public TestObject()
{
}
public TestObject(string name, int count)
{
this.Name = name;
this.Count = count;
}
}
public ActionResult Test(ICollection<TestObjects> t)
{
return View();
}
then I know that if I call this action via the URL "/Test?t[0].Name=One&t[0].Count=1&t[1].Name=Two&t[1].Count=2" that MVC will map those query string parameters back into the ICollection type automatically. However, if I am manually creating a link somewhere using Url.Action(), and I want to pass a RouteValueDictionary of the parameters, when I add an ICollection to the RouteValueDictionary, Url.Action just renders it as the type, like &t=System.Collections.Generic.List.
For example:
RouteValueDictionary routeValDict = new RouteValueDictionary();
List<TestObject> testObjects = new List<TestObject>();
testObjects.Add(new TestObject("One", 1));
testObjects.Add(new TestObject("Two", 2));
routeValDict.Add("t", testObjects);
// Does not properly create the parameters for the List<TestObject> collection.
string url = Url.Action("Test", "Test", routeValDict);
Is there any way to get it to automatically render that collection into the format that MVC also understands how to map, or must I do this manually?
What am I missing, why would they make it so this beautiful mapping exists into an Action but not provide a way to manually work in the reverse direction for creating URLs?
Upvotes: 3
Views: 7732
Reputation: 116
I ran into this problem as well and used Zack's code but found a bug in it. If the IEnumerable is an array of string (string[]) then there is a problem. So i thaught I'd share my extended version.
public static RouteValueDictionary ToRouteValueDictionaryWithCollection(this RouteValueDictionary routeValues)
{
var newRouteValues = new RouteValueDictionary();
foreach(var key in routeValues.Keys)
{
object value = routeValues[key];
if(value is IEnumerable && !(value is string))
{
int index = 0;
foreach(object val in (IEnumerable)value)
{
if(val is string || val.GetType().IsPrimitive)
{
newRouteValues.Add(String.Format("{0}[{1}]", key, index), val);
}
else
{
var properties = val.GetType().GetProperties();
foreach(var propInfo in properties)
{
newRouteValues.Add(
String.Format("{0}[{1}].{2}", key, index, propInfo.Name),
propInfo.GetValue(val));
}
}
index++;
}
}
else
{
newRouteValues.Add(key, value);
}
}
return newRouteValues;
}
Upvotes: 6
Reputation: 248
Well, I am open to other (more elegant) solutions, but I did get it working by taking the extension method found at this q/a: https://stackoverflow.com/a/5208050/1228414 and adapting it to use reflection for complex type properties instead of assuming primitive type arrays.
My code:
public static RouteValueDictionary ToRouteValueDictionaryWithCollection(this RouteValueDictionary routeValues)
{
RouteValueDictionary newRouteValues = new RouteValueDictionary();
foreach (var key in routeValues.Keys)
{
object value = routeValues[key];
if (value is IEnumerable && !(value is string))
{
int index = 0;
foreach (object val in (IEnumerable)value)
{
PropertyInfo[] properties = val.GetType().GetProperties();
foreach (PropertyInfo propInfo in properties)
{
newRouteValues.Add(
String.Format("{0}[{1}].{2}", key, index, propInfo.Name),
propInfo.GetValue(val));
}
index++;
}
}
else
{
newRouteValues.Add(key, value);
}
}
return newRouteValues;
}
Upvotes: 6