Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
77 views
in Technique[技术] by (71.8m points)

c# - MVC Form not able to post List of objects

so I have an MVC Asp.net app that is having issues. Essentially, I have a View that contains a form, and its contents are bound to a list of objects. Within this loop, it loads PartialView's with the items being looped over. Now everything works up till the submittion of the form. When it gets submitted, the controller is sent a null list of objects. The code below demonstates the problems.

Parent View:

@model IEnumerable<PlanCompareViewModel>
@using (Html.BeginForm("ComparePlans", "Plans", FormMethod.Post, new { id = "compareForm" }))
{
<div>
    @foreach (var planVM in Model)
    {
        @Html.Partial("_partialView", planVM)
    }
</div>
}

_partialView:

@model PlanCompareViewModel
<div>
    @Html.HiddenFor(p => p.PlanID)
    @Html.HiddenFor(p => p.CurrentPlan)
    @Html.CheckBoxFor(p => p.ShouldCompare)
   <input type="submit" value="Compare"/>
</div>

And these are the classes for the above code:

PlanViewModel:

public class PlansCompareViewModel
{

    public int PlanID { get; set; }
    public Plan CurrentPlan { get; set; }
    public bool ShouldCompare { get; set; }
    public PlansCompareViewModel(Plan plan)
    {
        ShouldCompare = false;
        PlanID = plan.PlanId;
        CurrentPlan = plan;
    }

    public PlansCompareViewModel()
    {
        // TODO: Complete member initialization
    }
    public static IEnumerable<PlansCompareViewModel> CreatePlansVM(IEnumerable<Plan> plans)
    {
        return plans.Select(p => new PlansCompareViewModel(p)).AsEnumerable();
    }
}

Controller:

public class PlansController : MyBaseController
{
    [HttpPost]
    public ActionResult ComparePlans(IEnumerable<PlanCompareViewModel> model)
    {
         //the model passed into here is NULL
    }
}

And the problem is in the controller action. As far as I am aware, it should be posting an enumerable list of PlanCompareViewModels, yet it is null. When in inspect the post data being sent, it is sending the correct params. And if I were to change 'IEnumerable' to 'FormCollection', it contains the correct values. Can anyone see why the binder is not creating the correct object? I can get around this using javascript, but that defeats the purpose! Any help would be greatly appreciated!

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Your model is null because the way you're supplying the inputs to your form means the model binder has no way to distinguish between the elements. Right now, this code:

@foreach (var planVM in Model)
{
    @Html.Partial("_partialView", planVM)
}

is not supplying any kind of index to those items. So it would repeatedly generate HTML output like this:

<input type="hidden" name="yourmodelprefix.PlanID" />
<input type="hidden" name="yourmodelprefix.CurrentPlan" />
<input type="checkbox" name="yourmodelprefix.ShouldCompare" />

However, as you're wanting to bind to a collection, you need your form elements to be named with an index, such as:

<input type="hidden" name="yourmodelprefix[0].PlanID" />
<input type="hidden" name="yourmodelprefix[0].CurrentPlan" />
<input type="checkbox" name="yourmodelprefix[0].ShouldCompare" />
<input type="hidden" name="yourmodelprefix[1].PlanID" />
<input type="hidden" name="yourmodelprefix[1].CurrentPlan" />
<input type="checkbox" name="yourmodelprefix[1].ShouldCompare" />

That index is what enables the model binder to associate the separate pieces of data, allowing it to construct the correct model. So here's what I'd suggest you do to fix it. Rather than looping over your collection, using a partial view, leverage the power of templates instead. Here's the steps you'd need to follow:

  1. Create an EditorTemplates folder inside your view's current folder (e.g. if your view is HomeIndex.cshtml, create the folder HomeEditorTemplates).
  2. Create a strongly-typed view in that directory with the name that matches your model. In your case that would be PlanCompareViewModel.cshtml.

Now, everything you have in your partial view wants to go in that template:

@model PlanCompareViewModel
<div>
    @Html.HiddenFor(p => p.PlanID)
    @Html.HiddenFor(p => p.CurrentPlan)
    @Html.CheckBoxFor(p => p.ShouldCompare)
   <input type="submit" value="Compare"/>
</div>

Finally, your parent view is simplified to this:

@model IEnumerable<PlanCompareViewModel>
@using (Html.BeginForm("ComparePlans", "Plans", FormMethod.Post, new { id = "compareForm" }))
{
<div>
    @Html.EditorForModel()
</div>
}

DisplayTemplates and EditorTemplates are smart enough to know when they are handling collections. That means they will automatically generate the correct names, including indices, for your form elements so that you can correctly model bind to a collection.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...