Introduction
In my scenario, model had both simple fields directly bindable to input controls, and an array of complex objects that was constructed using fancy JavaScript. I had to submit the whole model using one form postback (no AJAX).
This is the model (the challenge here was to bind ComplexData
):
public class ComplexModel
{
public string FirstName {get;set;}
public string LastName {get;set;}
[JsonBindable]
public List<ComplexObject> ComplexData {get;set;}
}
Controller is trivial:
public class ComplexController : Controller
{
public ViewResult Save(ComplexModel model)
{
}
}
View binds FirstName
and LastName
directly to inputs and ComplexData
property is prepared just before form submit by stringifying the JSON object. Formatted JSON string
is placed in a hidden field.
<script>
$(document).ready(function () {
$('form').submit(function () {
$('input[name=ComplexData]').val(JSON.stringify(getMyComplexDataObject()));
});
}
</script>
<form>
<input name='FirstName'/>
<input name='LastName'/>
<input type='hidden' name='ComplexData'/>
<input type='submit' value='Save!' />
</form>
So the problem was to bind JSON string to the ComplexObject
. The solution to the problem was to create custom attribute:
public class JsonBindableAttribute : Attribute
{
}
… and a custom model binder:
public class JsonPropBinder : DefaultModelBinder
{
protected override object GetPropertyValue(ControllerContext controllerContext,
ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
if (propertyDescriptor.Attributes.OfType<Attribute>().
Any(x => (x is JsonBindableAttribute)))
{
var value = bindingContext.ValueProvider.
GetValue(bindingContext.ModelName).AttemptedValue;
return JsonConvert.DeserializeObject
(value, propertyDescriptor.PropertyType);
}
return base.GetPropertyValue(controllerContext, bindingContext,
propertyDescriptor, propertyBinder);
}
}
… and finally register the binder:
protected void Application_Start()
{
ModelBinders.Binders.Add(typeof(ComplexModel), new JsonPropBinder());
}