I have the following problem: one of the system I'm working in most important features is a search page. In this page I have some options, like records per page, starting date, ending date, and the problematic one: type. One must have the possibility to choose more than one type (most of the time, all of them will be selected). To make that work, i created the following:
<div>
<label>Eventos:</label>
<div>
@Html.ListBox("events", Model.Events, new { style = "width: 100%" })
</div>
</div>
It creates a listbox where I can choose more than one option, and when the form is submited, my query string will look like this:
/5?period=9&events=1&events=3&recordsPerPage=10
There it is possible to see that two events (which is the type I was talking before) are created. The action method to this page takes a List<long>
as one of its arguments, which represents that two events
values. The problem begins when I want to use that with MVC Contrib. Their pager works just fine, but as I was requested, I created another pager, which displays links to five pages after and before the one the user is at. To do this, in a part of my code I have to do the following (which is very similar to the MVC Contrib pager, that works):
public RouteValueDictionary GetRoute(int page)
{
var routeValues = new RouteValueDictionary();
foreach (var key in Context.Request.QueryString.AllKeys.Where(key => key != null))
{
routeValues[key] = Context.Request.QueryString[key];
}
routeValues["page"] = page;
return routeValues;
}
And then:
@Html.ActionLink(page.ToString(), action, controller, GetRoute(page), null)
The problem is that it is a Dictionary, which makes the second time I set the value for routeValues["events"]
erase the previous.
Do you guys have any idea on how to work with it?
Very good question. Unfortunately it is not easy to generate an url which has multiple query string parameters with the same name using the Html.ActionLink
helper. So I can see two possible solutions:
Write a custom model binder for long[]
that is capable of parsing a comma separated values. This way you can keep your GetRoute
method which will generate the following url: period=9&events=1%2C3&recordsPerPage=10&page=5
.
public class CommaSeparatedLongArrayModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (values != null && !string.IsNullOrEmpty(values.AttemptedValue))
{
// TODO: A minimum of error handling would be nice here
return values.AttemptedValue.Split(',').Select(x => long.Parse(x)).ToArray();
}
return base.BindModel(controllerContext, bindingContext);
}
}
which you will register in Application_Start
:
ModelBinders.Binders.Add(typeof(long[]), new CommaSeparatedLongArrayModelBinder());
and then the following controller action will be able to understand the previous URL:
public ActionResult Foo(long[] events, int page, int period, int recordsPerPage)
{
...
}
Manually generate this anchor:
<a href="@string.Format("{0}?{1}&page=5", Url.Action("action", "controller"), Request.QueryString)">abc</a>
Try looking at WinTellect's PowerCollections, allows you to create a MultiDictionary, still can't have duplicate keys, but you can have multiple values per key.
You should write either extension methods that target the routeValue collection or a custom model binder that transforms your Event parameter into a list always. If you view Event always being a list, just a commonly length 1 list will alleviate most of the problems you face.
At this point you will just interact with a list interface. You could then write a custom binder to allow you to directly place that into the route correctly or you could unpack the list back into the query string. There's a software project based on this called Unbinder for unpacking objects into property/value pairs that you can easily use in query strings or other purposes.