Skip to content

Instantly share code, notes, and snippets.

@jagregory
Created January 4, 2011 13:29
Show Gist options
  • Save jagregory/764758 to your computer and use it in GitHub Desktop.
Save jagregory/764758 to your computer and use it in GitHub Desktop.
Custom MVC1 model binder that handles array properties
public class ArrayModelBinder : IFilteredPropertyBinder
{
private static readonly Regex IdRegex = new Regex("(?<=_)[0-9]+(?=_)");
public bool IsMatch(Type modelType)
{
return modelType.IsArray;
}
public void BindProperty(IModelBinder binder, ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
var elementType = propertyDescriptor.PropertyType.GetElementType();
var namePrefix = char.ToLower(propertyDescriptor.Name[0]) + propertyDescriptor.Name.Substring(1);
var list = new ArrayList();
var relatedFormValues = GetRelatedFormPairs(namePrefix, bindingContext.ValueProvider);
var groupedFormValues = GroupByIndex(relatedFormValues);
foreach (var elementValues in groupedFormValues.OrderBy(x => x.Key))
{
var modelBindingContext = new ModelBindingContext
{
ValueProvider = new PrefixedValueProvider(namePrefix + "_" + elementValues.Key + "_", elementValues),
ModelType = elementType
};
var model = binder.BindModel(controllerContext, modelBindingContext);
list.Add(model);
}
var array = list.ToArray(elementType);
propertyDescriptor.SetValue(bindingContext.Model, array);
}
static IEnumerable<KeyValuePair<string, ValueProviderResult>> GetRelatedFormPairs(string namePrefix, IEnumerable<KeyValuePair<string, ValueProviderResult>> valueProvider)
{
return valueProvider
.Where(kvp => kvp.Key.StartsWith(namePrefix + "_"));
}
static IEnumerable<IGrouping<int, KeyValuePair<string, ValueProviderResult>>> GroupByIndex(IEnumerable<KeyValuePair<string, ValueProviderResult>> formValues)
{
return formValues.GroupBy(x => int.Parse(IdRegex.Match(x.Key).Value));
}
}
public interface IFilteredPropertyBinder : IPropertyBinder
{
bool IsMatch(Type modelType);
}
public interface IPropertyBinder
{
void BindProperty(IModelBinder binder, ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor);
}
public class SmartBinder : DefaultModelBinder
{
readonly List<IFilteredModelBinder> modelBinders = new List<IFilteredModelBinder>();
readonly List<IFilteredPropertyBinder> propertyBinders = new List<IFilteredPropertyBinder>();
public SmartBinder Add(IFilteredModelBinder binder)
{
modelBinders.Add(binder);
return this;
}
public SmartBinder Add(IFilteredPropertyBinder binder)
{
propertyBinders.Add(binder);
return this;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
foreach (var binder in modelBinders)
{
if (!binder.IsMatch(bindingContext.ModelType)) continue;
return binder.BindModel(controllerContext, bindingContext);
}
return base.BindModel(controllerContext, bindingContext);
}
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
foreach (var binder in propertyBinders)
{
if (!binder.IsMatch(propertyDescriptor.PropertyType)) continue;
binder.BindProperty(this, controllerContext, bindingContext, propertyDescriptor);
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
ModelBinders.Binders.DefaultBinder = new SmartBinder()
.Add(new ArrayModelBinder());
@jagregory
Copy link
Author

Very rudimentary implementation of a model binder which will try to populate an array from the posted form values. For this to work your form fields must have the following naming convention: camelCaseArrayPropertyName_arrayIndex_PascalCaseModelPropertyName. For example: people_1_Name.

SmartModelBinder (inspired by Jimmy Bogard's A better Model Binder) allows applying "property" model binders that match a specific criteria.

This was written for an MVC1 system, there may be better alternatives for MVC2+

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment