Question

I have an action method like this below.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Form newForm)
{
     ...
}

I have a model with the following classes, which I'd like to load the data from the ajax JSON data.

public class Form
{
    public string title { get; set; }

    public List<FormElement> Controls { get; set; }

}

public class FormElement
{
    public string ControlType { get; set; }

    public string FieldSize { get; set; }
}

public class TextBox : FormElement
{
    public string DefaultValue { get; set; }
}

public class Combo : FormElement
{
    public string SelectedValue { get; set; }
}

Here is the JSON data.

{ "title": "FORM1", 
"Controls": 
[
{ "ControlType": "TextBox", "FieldSize": "Small" ,"DefaultValue":"test"}, 
{ "ControlType": "Combo", "FieldSize": "Large" , "SelectedValue":"Option1" }
] 
}


 $.ajax({
                url: '@Url.Action("Create", "Form")',
                type: 'POST',
                dataType: 'json',
                data: newForm,
                contentType: 'application/json; charset=utf-8',
                success: function (data) {
                    var msg = data.Message;
                }
            });

DefaultModelBinder is handling the nested object structure but it can't resolve the different sub classes.

What would be the best way to load List with the respective sub-classes?

Was it helpful?

Solution

I have looked in to the code of the mvc DefaultModelBinder implementation. When binding a model DefaultModelBinder look up the properties of the model using GetModelProperties(). The following is how DefaultModelBinder look up the properties :

 protected virtual ICustomTypeDescriptor GetTypeDescriptor(ControllerContext controllerContext, ModelBindingContext bindingContext) {
            return TypeDescriptorHelper.Get(bindingContext.ModelType);
        }

TypeDescriptorHelper.Get is using ModelType which is the partent type (in my case FormElement), so the properties of the child class (TextBox, Combo) are not retrieved.

You can override the method and change the behavior to retrieve the specific child type as below.

protected override System.ComponentModel.PropertyDescriptorCollection GetModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    Type realType = bindingContext.Model.GetType();
    return new AssociatedMetadataTypeTypeDescriptionProvider(realType).GetTypeDescriptor(realType).GetProperties();
}


protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            ValueProviderResult result;
            result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".ControlType");

            if (result == null)
                return null;

            if (result.AttemptedValue.Equals("TextBox"))
                return base.CreateModel(controllerContext,
                        bindingContext,
                        typeof(TextBox));
            else if (result.AttemptedValue.Equals("Combo"))
                return base.CreateModel(controllerContext,
                        bindingContext,
                        typeof(Combo));
            return null;
        }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top