开发者

How to update a model that contains a list of IMyInterface in MVC3

I have a model like so:

        return new MyViewModel()
        {
            Name = "My View Model",

            Modules = new IRequireConfig[]
            {
                开发者_开发百科new FundraisingModule()
                {
                    Name = "Fundraising Module",
                    GeneralMessage = "Thanks for fundraising"
                },

                new DonationModule()
                {
                    Name = "Donation Module",
                    MinDonationAmount = 50
                }
            }
        };

The IRequireConfig interface exposes a DataEditor string property that the view uses to pass to @Html.EditorFor like so:

    @foreach (var module in Model.Modules)
    {
        <div>
            @Html.EditorFor(i => module, @module.DataEditor, @module.DataEditor)  //the second @module.DataEditor is used to prefix the editor fields
        </div>
    }

When I post this back to my controller TryUpdateModel leaves the Modules property null. Which is pretty much expected since I wouldnt expect it to know which concrete class to deserialize to.

Since I have the original model still available when the post comes in I can loop over the Modules and get their Type using .GetType(). It seems like at this point I have enough information to have TryUpdateModel try to deserialize the model, but the problem is that it uses a generic type inference to drive the deserializer so it does not actually update any of the properties except the ones defined in the interface.

How can I get update my Modules array with their new values?

If any particular point isnt clear please let me know and I will try to clarify


You could use a custom model binder. Assuming you have the following models:

public interface IRequireConfig
{
    string Name { get; set; }
}

public class FundraisingModule : IRequireConfig
{
    public string Name { get; set; }
    public string GeneralMessage { get; set; }
}

public class DonationModule : IRequireConfig
{
    public string Name { get; set; }
    public decimal MinDonationAmount { get; set; }
}

public class MyViewModel
{
    public string Name { get; set; }
    public IRequireConfig[] Modules { get; set; }
}

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new MyViewModel
        {
            Name = "My View Model",
            Modules = new IRequireConfig[]
            {
                new FundraisingModule()
                {
                    Name = "Fundraising Module",
                    GeneralMessage = "Thanks for fundraising"
                },
                new DonationModule()
                {
                    Name = "Donation Module",
                    MinDonationAmount = 50
                }
            }
        };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

View:

@model MyViewModel
@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.Name)
    for (int i = 0; i < Model.Modules.Length; i++)
    {
        @Html.Hidden("Modules[" + i + "].Type", Model.Modules[i].GetType())
        @Html.EditorFor(x => x.Modules[i])
    }
    <input type="submit" value="OK" />
}

and finally the custom model binder:

public class RequireConfigModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeParam = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Type");
        if (typeParam == null)
        {
            throw new Exception("Concrete type not specified");
        }
        var concreteType = Type.GetType(typeParam.AttemptedValue, true);
        var concreteInstance = Activator.CreateInstance(concreteType);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => concreteInstance, concreteType);
        return concreteInstance;
    }
}

which you would register in Application_Start:

ModelBinders.Binders.Add(typeof(IRequireConfig), new RequireConfigModelBinder());

Now when the form is submitted the Type will be sent and the model binder will be able to instantiate the proper implementation.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜