How can I bind query string parameter that is comma separated value
http://localhost/Action?ids=4783,5063,5305
to a controller action expecting a list?
public ActionResult Action(List<long> ids) { return View(); }
Note! ids
in the controller action must a list (or something IEnumerable based), so string ids
is not accepted as an answer because these parameters are passed to many actions and parsing string to an array would add unwanted noise.
Default model binder expects simple type lists to be in the format
name=value&name=value2&name=value3
To use builtin binding, you should change your query string to
Action?ids=4783&ids=5063&ids=5305
Or create custom model binder. You may take a look at following article (code from there)
public class CommaSeparatedValuesModelBinder : DefaultModelBinder { private static readonly MethodInfo ToArrayMethod = typeof(Enumerable).GetMethod("ToArray"); protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { if (propertyDescriptor.PropertyType.GetInterface(typeof(IEnumerable).Name) != null) { var actualValue = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name); if (actualValue != null && !String.IsNullOrWhiteSpace(actualValue.AttemptedValue) && actualValue.AttemptedValue.Contains(",")) { var valueType = propertyDescriptor.PropertyType.GetElementType() ?? propertyDescriptor.PropertyType.GetGenericArguments().FirstOrDefault(); if (valueType != null && valueType.GetInterface(typeof(IConvertible).Name) != null) { var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(valueType)); foreach (var splitValue in actualValue.AttemptedValue.Split(new[] { ',' })) { list.Add(Convert.ChangeType(splitValue, valueType)); } if (propertyDescriptor.PropertyType.IsArray) { return ToArrayMethod.MakeGenericMethod(valueType).Invoke(this, new[] { list }); } else { return list; } } } } return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); } }
Here's my improved version of Nathan Taylor’s solution used in archil's answer.
To wire this up, you can either attach this to an individual Controller argument:
[ModelBinder(typeof(CommaSeparatedModelBinder))]
…or set it as the global default binder in Application_Start in global.asax.cs:
ModelBinders.Binders.DefaultBinder = new CommaSeparatedModelBinder();
In the second case it will try and handle all IEnumerables and fall back to ASP.NET MVC standard implementation for everything else.
Behold:
public class CommaSeparatedModelBinder : DefaultModelBinder { private static readonly MethodInfo ToArrayMethod = typeof(Enumerable).GetMethod("ToArray"); public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { return BindCsv(bindingContext.ModelType, bindingContext.ModelName, bindingContext) ?? base.BindModel(controllerContext, bindingContext); } protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { return BindCsv(propertyDescriptor.PropertyType, propertyDescriptor.Name, bindingContext) ?? base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); } private object BindCsv(Type type, string name, ModelBindingContext bindingContext) { if (type.GetInterface(typeof(IEnumerable).Name) != null) { var actualValue = bindingContext.ValueProvider.GetValue(name); if (actualValue != null) { var valueType = type.GetElementType() ?? type.GetGenericArguments().FirstOrDefault(); if (valueType != null && valueType.GetInterface(typeof(IConvertible).Name) != null) { var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(valueType)); foreach (var splitValue in actualValue.AttemptedValue.Split(new[] { ',' })) { if(!String.IsNullOrWhiteSpace(splitValue)) list.Add(Convert.ChangeType(splitValue, valueType)); } if (type.IsArray) return ToArrayMethod.MakeGenericMethod(valueType).Invoke(this, new[] { list }); else return list; } } } return null; } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With