I´m doing a JQuery based component for dynamic rows using Asp.Net MVC.
What that means is that I need that code to be translated to a model object with two items featuring name and description in my action. Something in the lines of:
You can imagine my surprise when I found out that it does not work. Coming from MonoRail I might’ve been spoiled, but I really expected it to work seamlessly. That’s when I learned that if you want advanced binding in ASP.Net MVC you have to do it yourself.
public class AdvancedModelBinder : IModelBinder
{ public ModelBinderResult BindModel(ModelBindingContext bindingContext)
{ var result = Activator.CreateInstance(bindingContext.ModelType);
BindPropertiesToResult(bindingContext.ModelType.Name, result, bindingContext);
return new ModelBinderResult(result);
}
private void BindPropertiesToResult(string path, object result, ModelBindingContext bindingContext)
{ if (DepthCountFor(path) > 10) return;
var properties = result.GetType().GetProperties();
foreach (var property in properties)
{ var propertyValue = ReflectionUtils.IsList(property) ? GetListValuesFor(path, property, bindingContext) : GetValueFor(path, property, bindingContext);
if (propertyValue != null) property.SetValue(result, propertyValue, null);
}
}
private object GetListValuesFor(string path, PropertyInfo property, ModelBindingContext context)
{ var newList = ReflectionUtils.CreateListFor(property.PropertyType);
var propertyPath = path + "." + property.Name + "[";
if (!PathExists(propertyPath, context)) return null;
var maxIndex = -1;
foreach (string key in context.HttpContext.Request.Form.Keys)
{ if (!key.ToLowerInvariant().StartsWith(propertyPath.ToLowerInvariant())) continue;
int index;
var numericProperty = key.ToLowerInvariant().Replace(propertyPath.ToLowerInvariant(), string.Empty);
var indexOf = numericProperty.IndexOf("]"); if (indexOf == -1) continue;
numericProperty = numericProperty.Substring(0, indexOf);
var isNumeric = Int32.TryParse(numericProperty.Replace("]", string.Empty), out index); if (isNumeric && index > maxIndex)
{ maxIndex = index;
}
}
AddItemsToList(path + "." + property.Name, ReflectionUtils.GetListType(property.PropertyType), maxIndex, newList, context);
return newList;
}
private object GetValueFor(string path, PropertyInfo property, ModelBindingContext context)
{ var propertyPath = path + "." + property.Name;
if (!PathExists(propertyPath, context)) return null;
if (!property.PropertyType.IsClass || property.PropertyType.Equals(typeof(string)))
{ var value = context.ValueProvider.GetValue(propertyPath);
return value!=null ? value.AttemptedValue : null;
}
var newObject = Activator.CreateInstance(property.PropertyType);
BindPropertiesToResult(propertyPath, newObject, context);
return newObject;
}
private void AddItemsToList(string path, Type itemType, int index, object newList, ModelBindingContext context)
{ for (var i = 0; i <= index; i++)
{ var obj = Activator.CreateInstance(itemType);
BindPropertiesToResult(string.Format("{0}[{1}]", path, i), obj, context); ReflectionUtils.InvokeMethodOn(newList, "Add", obj);
}
}
private bool PathExists(string path, ModelBindingContext context)
{ foreach (string key in context.HttpContext.Request.Form.Keys)
{ if (key.ToLowerInvariant().StartsWith(path.ToLowerInvariant()))
return true;
}
return false;
}
private int DepthCountFor(string path)
{ return path.Count(c => c == '.');
}
}
public static class ReflectionUtils
{ public static bool IsList(PropertyInfo property)
{ var propertyType = property.PropertyType;
return propertyType.IsArray ||
propertyType.FullName.StartsWith(typeof(IList<>).FullName) ||
propertyType.IsSubclassOf(typeof(List<>)) ||
propertyType.GetInterfaces().ToList().Contains(typeof(IList<>));
}
public static object CreateListFor(Type type)
{ var listType = typeof(List<>);
var genericType = listType.MakeGenericType(GetListType(type));
return Activator.CreateInstance(genericType);
}
public static Type GetListType(Type listType)
{ return listType.GetGenericArguments()[0];
}
public static object InvokeMethodOn(object obj, string methodName, params object[] parameters)
{ var methodInfo = obj.GetType().GetMethod(methodName);
return methodInfo.Invoke(obj, parameters);
}
public static object GetPropertyValue(object obj, string propertyName)
{ var property = obj.GetType().GetProperty(propertyName);
return property.GetValue(obj, null);
}
}
This will enable the scenario that I need, I´m just sharing with whomever might need it.