DefaultModelBinder and collection of inherited objects
Asked Answered
P

1

8

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?

Porter answered 19/12, 2012 at 19:53 Comment(2)
Can you explain in further detail what are you trying to accomplish here? It seems like you're trying to bind the whole form into the viewmodel instead of just the values it carries. I can see the point in generating the forms dynamically based on some JSON data the backend provides but I struggle to understand why you would like to provide the backend again the structure itself instead of the values only when a user fills in the form.Hypertonic
I am not generating the form dynamically. I am accepting the json which represent the structure of the form that will be saved later in the system.Porter
P
2

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;
        }
Porter answered 20/12, 2012 at 9:47 Comment(1)
Thank you very much for the GetModelProperties hint! You save me a lot of time!Chios

© 2022 - 2024 — McMap. All rights reserved.