Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mvc3 razor editortemplate with abstract classes

this is a follow up question from MVC3 Razor httppost return complex objects child collections.

The example I gave was very simple. The child collection is actually a collection of objects that all come from an abstract base class. So the collection has a list of base classes.

I have created a template for each derived class and tried using if child is of type then give the template name as a string. The templates are rendered to the view but not populated on the post back.

I am not sure how I use the editorfor bit with the templates to choose the correct template and get the information to be marshalled back into the child objects within the parent container.

like image 902
Jon Avatar asked May 11 '12 16:05

Jon


1 Answers

You could use a custom model binder. Let's take an example.

Model:

public class MyViewModel
{
    public IList<BaseClass> Children { get; set; }
}

public abstract class BaseClass
{
    public int Id { get; set; }

    [HiddenInput(DisplayValue = false)]
    public string ModelType
    {
        get { return GetType().FullName; }
    }
}

public class Derived1 : BaseClass
{
    public string Derived1Property { get; set; }
}

public class Derived2 : BaseClass
{
    public string Derived2Property { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new MyViewModel
        {
            Children = new BaseClass[]
            {
                new Derived1 { Id = 1, Derived1Property = "prop1" },
                new Derived2 { Id = 2, Derived2Property = "prop2" },
            }
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        // everything will be fine and dandy here
        ...
    }
}

View (~/Views/Home/Index.cshtml):

@model MyViewModel

@using (Html.BeginForm())
{
    for (int i = 0; i < Model.Children.Count; i++)
    {
        @Html.EditorFor(x => x.Children[i].ModelType)
        <div>
            @Html.EditorFor(x => x.Children[i].Id)
            @Html.EditorFor(x => x.Children[i])    
        </div>
    }

    <button type="submit">OK</button>
}

Editor template for the Dervied1 type (~/Views/Home/EditorTemplates/Derived1.cshtml):

@model Derived1
@Html.EditorFor(x => x.Derived1Property)

and the editor template for the Dervied2 type (~/Views/Home/EditorTemplates/Derived2.cshtml):

@model Derived2
@Html.EditorFor(x => x.Derived2Property)

Now all that's left is a custom model binder that will use the hidden field value to instantiate the proper type in the collection:

public class BaseClassModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".ModelType");
        var type = Type.GetType(
            (string)typeValue.ConvertTo(typeof(string)),
            true
        );
        var model = Activator.CreateInstance(type);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
        return model;
    }
}

which will be registered in Application_Start:

ModelBinders.Binders.Add(typeof(BaseClass), new BaseClassModelBinder());
like image 174
Darin Dimitrov Avatar answered Nov 07 '22 16:11

Darin Dimitrov