How to deal with more than one value per key in ASP.NET MVC 3?
Asked Answered
E

3

14

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?

Esparza answered 18/5, 2011 at 16:50 Comment(0)
F
5

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:

  1. 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)
    {
        ...
    }
    
  2. Manually generate this anchor:

    <a href="@string.Format("{0}?{1}&page=5", Url.Action("action", "controller"), Request.QueryString)">abc</a>
    
Frances answered 18/5, 2011 at 17:25 Comment(2)
It was a good answer and I almost thought it would work at first try ... but we're talking about programming, and it never does. The problem I had is the generated link now looks like /5?period=9&amp;events=1&amp;events=3&amp;recordsPerPage=10&amp;page=2Esparza
@Bruno, I would probably go with a custom model binder. As far as the second part of my answer is concerned I've fixed it by removing the Html.AttributeEncode call which is not necessary in this case.Frances
D
0

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.

Draftee answered 18/5, 2011 at 17:17 Comment(0)
U
0

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.

Unaccountable answered 18/5, 2011 at 17:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.