94

I have an ASP.net MVC controller called Designs that has an action with the following signature:

public ActionResult Multiple(int[] ids)

However, when I try to navigate to this action using the url:

http://localhost:54119/Designs/Multiple?ids=24041,24117

The ids parameter is always null. Is there any way to get MVC to convert the ?ids= URL query parameter into an array for the action? I've seen talk of using an action filter but as far as I can tell that will only work for POSTs where the array is passed in the request data rather than in the URL itself.

Red
  • 3,030
  • 3
  • 22
  • 39
Grokys
  • 16,228
  • 14
  • 69
  • 101

5 Answers5

162

The default model binder expects this url:

http://localhost:54119/Designs/Multiple?ids=24041&ids=24117

in order to successfully bind to:

public ActionResult Multiple(int[] ids)
{
    ...
}

And if you want this to work with comma separated values you could write a custom model binder:

public class IntArrayModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (value == null || string.IsNullOrEmpty(value.AttemptedValue))
        {
            return null;
        }

        return value
            .AttemptedValue
            .Split(',')
            .Select(int.Parse)
            .ToArray();
    }
}

and then you could apply this model binder to a particular action argument:

public ActionResult Multiple([ModelBinder(typeof(IntArrayModelBinder))] int[] ids)
{
    ...
}

or apply it globally to all integer array parameters in your Application_Start in Global.asax:

ModelBinders.Binders.Add(typeof(int[]), new IntArrayModelBinder());

and now your controller action might look like this:

public ActionResult Multiple(int[] ids)
{
    ...
}
Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 3
    I was missing `[FromUri]`. `public ActionResult Multiple([FromUri]int[] ids) {}` **(GET)** – C0d1ngJammer May 22 '17 at 13:22
  • 1
    @Darin is there a way to apply the custom binding globally but just ignore for specific action? I couldn't find a way to do that :https://stackoverflow.com/questions/45379040/how-to-override-global-model-binder-for-speacific-post-action-mvc5 – akd Jul 28 '17 at 23:59
18

To extend on Darin Dimitrov's answer, something you can get away with is accepting a simple string in your URL parameter and converting it to an array yourself:

public ActionResult Multiple(string ids){
  int[] idsArray = ids.Split(',').Select(int.Parse).ToArray();
  /* ...process results... */
}

If you get a parse error while doing this (because someone passed you a malformed array), you can cause your exception handler to return a 400 Bad Request error instead of the default, more unfriendly 404 Not Found error that MVC returns when an endpoint is not found.

Community
  • 1
  • 1
TheHans255
  • 2,059
  • 1
  • 19
  • 36
12

You can also use this URL format, and ASP.NET MVC will do everything for you. But, remember to apply URL encoding.

?param1[0]=3344&param1[1]=2222
Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188
Cioxideru
  • 1,472
  • 1
  • 11
  • 13
7

I don't know where Groky's URL string was coming from, but I had the same problem with some javascript calling my controller/action. It would build up a URL of null, 1, or many "IDs" from a multiple-select list (which is unique to the solution I'm going to share).

I copy/pasted Darin's custom model binder and decorated my action/parameter, but it didn't work. I still got null valued int[] ids. Even in the "safe" case where I actually did have many IDs.

I ended up changing the javascript to produce an ASP.NET MVC friendly parameter array like

?ids=1&ids=2

I had to do some silly stuff, though

ids || []                 #=> if null, get an empty array
[ids || []]               #=> if a single item, wrap it in an array
[].concat.apply([], ...)  #=> in case I wrapped an array, flatten it

So, the full block was

ids = [].concat.apply([], [ids || []])
id_parameter = 'ids=' + ids.join('&ids=')

It's messy, but it's the first time I had to hack like this in javascript.

Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188
  • 2
    Just curious if there's a missing 'not' before "first time" in that last sentence. Otherwise, lucky you! – DCShannon Mar 24 '15 at 00:40
  • 1
    @DCShannon: haha, I see your point! But, this was my first time. I'm not sure if this kind of thing is normal or if I'm going overboard. – Anthony Mastrean Mar 25 '15 at 13:31
7

.Net Core Answer

For those coming here in recent times, you can do this in .Net Core with:

http://localhost:54119/Designs/Multiple?ids=24041&ids=24117

and:

public ActionResult Multiple([FromQuery] int[] ids)
{
    ...
}
Red
  • 3,030
  • 3
  • 22
  • 39