web-dev-qa-db-ja.com

MVCフォームがオブジェクトのリストを投稿できません

だから私は問題を抱えているMVC Asp.netアプリを持っています。基本的に、フォームを含むビューがあり、そのコンテンツはオブジェクトのリストにバインドされています。このループ内で、ループされているアイテムとともにPartialViewをロードします。これで、フォームが送信されるまですべてが機能します。送信されると、コントローラーにはオブジェクトのヌルリストが送信されます。以下のコードは問題を実証しています。

親ビュー:

@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>

そして、これらは上記のコードのクラスです:

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();
    }
}

コントローラ:

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

そして問題はコントローラーのアクションにあります。私の知る限り、列挙可能なPlanCompareViewModelsのリストを投稿する必要がありますが、それはnullです。送信されている投稿データを検査するとき、正しいパラメータを送信しています。そして、「IEnumerable」を「FormCollection」に変更すると、正しい値が含まれます。バインダーが正しいオブジェクトを作成していない理由を誰でも見ることができますか?私はjavascriptを使用してこれを回避できますが、それは目的に反します!どんな助けも大歓迎です!

44
Sonoilmedico

あなたのモデルはnullです。フォームに入力を提供する方法は、モデルバインダーが要素を区別する方法を持たないためです。今、このコード:

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

それらのアイテムにいかなる種類のインデックスも提供していません。したがって、次のようなHTML出力を繰り返し生成します。

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

ただし、コレクションにバインドする場合は、フォーム要素に次のようなインデックスを付けて名前を付ける必要があります。

<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" />

そのインデックスは、モデルバインダーが個別のデータを関連付け、正しいモデルを構築できるようにするものです。そのため、ここで修正することをお勧めします。部分的なビューを使用してコレクションをループするのではなく、代わりにテンプレートの力を活用します。従う必要がある手順は次のとおりです。

  1. ビューの現在のフォルダー内にEditorTemplatesフォルダーを作成します(たとえば、ビューがHome\Index.cshtmlの場合、フォルダーHome\EditorTemplatesを作成します)。
  2. モデルに一致する名前で、そのディレクトリに厳密に型指定されたビューを作成します。あなたの場合、それはPlanCompareViewModel.cshtmlになります。

これで、部分ビューにあるすべてのものがそのテンプレートに入れられます:

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

最後に、親ビューは次のように簡素化されます。

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

DisplayTemplatesおよびEditorTemplatesは、コレクションをいつ処理するかを知るのに十分スマートです。つまり、コレクションへのバインドを正しくモデル化できるように、フォーム要素のインデックスを含む正しい名前が自動的に生成されます。

93
John H

これをお読みください: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
_planCompareViewModel[0].PlanIdplanCompareViewModel[1].PlanIdなどのHTML要素の「名前」属性にインデックスを設定して、バインダーがIEnumerableに解析できるようにする必要があります。
@foreach (var planVM in Model)の代わりにforループを使用し、インデックスで名前をレンダリングします。

30
LINQ2Vodka