In my ASP.NET MVC 4 application I can filter on multiple tags. In HTML, it looks like this:
<form>
<label>
<input type="checkbox" name="tag" value="1">One
</label>
<label>
<input type="checkbox" name="tag" value="2">Two
</label>
<label>
<input type="checkbox" name="tag" value="3">Three
</label>
<input type="submit" name="action" value="Filter">
</form>
When checking the first and third checkbox, the querystring is serialized as ?tag=1&tag=3
and my controller nicely passes an object with the type of the following class:
// Filter class
public class Filter {
public ICollection<int> tag { get; set; }
}
// Controller method
public ActionResult Index(AdFilter filter)
{
string url = Url.Action("DoFilter", filter);
// url gets this value:
// "/controller/Index?tag=System.Collections.Generic.List%601%5BSystem.Int32%5D"
// I would expect this:
// "/controller/Index?tag=1&tag=3"
...
}
However, a call to Url.Action
results in the typename of the collection being serialized, instead of the actual values.
How can this be done?
The standard infrastructure of MVC can handle the multi-keys described as input. Is there not standard infrastructure that can handle it the other way around? Am I missing something?
You can do it in the following way:
The
TypeHelper2.ObjectToDictionary
is a modified version of an internal method of .NET, and can be found in this two file gist.Changed behavior: when an item implements
IEnumerable
, for each item an entry is created in the returned dictionary with as key"Name[index]"
(the index is 0 based). This is possible because the binder of the MVC controller can handle bothtag=1&tag=3
andtag[0]=1&tag[1]=3
query strings.An easy yet no so elegant solution can be:
You using the overload of
Url.Action()
that accepts a object to generate the route parameters. Internally this works by using reflection to build a dictionary of each properties name and its.ToString()
value.In the case of a simple value type this might be
Name = "doekman"
(which becomes/DoFilter?Name=doekman
), but in the case of a property which is a complex object or a collection it returns the typename (i.e. the.ToString()
value). No recursion is done and this makes sense because query strings have length limits so if recursion was done on complex objects and collections, you would soon exceed the limit and throw an exception if the collection contained a lot of items (and it would create a really ugly query string).So in answer to
How can this be done?
You can't unless you manually generate theRouteValueDictionary
(or write your own helper to generate the query string)Have upvoted the above 2 answers - they're both perfectly good answers. On this side of things (redirecting to an action or generaring a URL to redirect to) MVC doesn't "like" arrays at all.
There are 2 futher approaches I can see:
1) use TempData to persist an retrieve the array (e.g at the bottom of this article)
2) Write and use a custom model binder for AdFilter
I'd go for the latter myself - testable and more deterministic (also, I don't know how TempData works when you get into a server farm scenario)
Another thing you might want to consider is using something like as the return from the Index action.
(This will return the Dofilter view whilst maintaining the "index?tag[0]=1&tag1=2" url in the browser)
Finally - it feels like you're taking the filter criteria only to send them back to the browser so the browser can then ask again for the filter results. Maybe a better option would be to set the action on the form to post to "DoFilter" from the start:
HTH