web-dev-qa-db-ja.com

MVCは複雑なオブジェクトのリストを投稿します

質問のリストを含むFeedbackViewModelがあります。

public class FeedbackViewModel
{
    public List<QuestionViewModel> Questions { get; set; }
}

このQuestionViewModelは、5種類の質問に継承できるオブジェクトです

public class QuestionViewModel
{
    public string QuestionText { get; set; }
    public string QuestionType { get; set; }
}

継承する質問タイプの1つの例:

public class SingleQuestionViewModel : QuestionViewModel
{
    public string AnswerText { get; set; }
}

コントローラーのHttpGetアクションのIndexで、データベースから質問を取得し、FeedbackViewModelの質問リストに正しい質問タイプを追加します。次に、このモデルをレンダリングしますビュー内:

@using (Html.BeginForm())
{
    //foreach (var item in Model.Questions)
    for (int i = 0; i < Model.Questions.Count; i++)
    {
        <div class="form-group">
            @Html.DisplayFor(modelItem => Model.Questions[i].QuestionText, new { @class = "control-label col-md-4" })
            <div class="col-md-6">
                @if (Model.Questions[i].QuestionType == "Single")
                {
                    @Html.EditorFor(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText)
                }
                else if (Model.Questions[i].QuestionType == "Multiple")
                {
                    @Html.TextAreaFor(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText)
                }
                else if (Model.Questions[i].QuestionType == "SingleSelection")
                {
                    @Html.RadioButtonForSelectList(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleSelectionQuestionViewModel).SelectedAnswer,
                                                                (Model.Questions[i] as OpenDataPortal.ViewModels.SingleSelectionQuestionViewModel).SelectionAnswers)
                }
                else if (Model.Questions[i].QuestionType == "MultipleSelection")
                {
                    @Html.CustomCheckBoxList((Model.Questions[i] as OpenDataPortal.ViewModels.MultipleSelectionQuestionViewModel).AvailableAnswers)
                }
                else if (Model.Questions[i].QuestionType == "UrlReferrer")
                {
                    @Html.EditorFor(modelItem => (Model.Questions[i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText)
                }
            </div>
        </div>
        <br />
    }

    <br />
    <button type="submit">Submit</button>
}

enter image description here


今、私は単にモデルに質問のリストを投稿することができません。異なるオブジェクトタイプのリストを投稿することさえ可能ですか?


編集:以下は、Fiddlerを使用して発見した投稿内のデータのリストです。

enter image description here

16
Carel

多くの研究の結果、2つの解決策を見つけました。

1つは、ハードコードされたIdと名前を持つHTMLを書くことです。2つは、ICollection/IEnumerableを配列またはリスト(つまり、「インデックス」を持つIList何か)に変換し、ControllerのBindingModelにArrayオブジェクトをPOSTアクション。

Phil Haackの(@haacked)2008ブログ投稿のおかげで http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/ まだ関連性が高い今日のデフォルトのModelBinderがMVCでどのように機能するかについて。 (注:Philの記事のサンプルの目的と拡張方法へのリンクは壊れています)

私にインスピレーションを与えたHTMLスニペット:

<form method="post" action="/Home/Create">
    <input type="hidden" name="products.Index" value="cold" />
    <input type="text" name="products[cold].Name" value="Beer" />
    <input type="text" name="products[cold].Price" value="7.32" />

    <input type="hidden" name="products.Index" value="123" />
    <input type="text" name="products[123].Name" value="Chips" />
    <input type="text" name="products[123].Price" value="2.23" />

    <input type="submit" />
</form>

投稿配列は少し似ています:

products.Index=cold&products[cold].Name=Beer&products[cold].Price=7.32&products.Index=123&products[123].Name=Chips&products[123].Price=2.23

モデル:

public class CreditorViewModel
{
    public CreditorViewModel()
    {
        this.Claims = new HashSet<CreditorClaimViewModel>();
    }
    [Key]
    public int CreditorId { get; set; }
    public string Comments { get; set; }
    public ICollection<CreditorClaimViewModel> Claims { get; set; }
    public CreditorClaimViewModel[] ClaimsArray { 
        get { return Claims.ToArray(); }
    }
}

public class CreditorClaimViewModel
{
    [Key]
    public int CreditorClaimId { get; set; }
    public string CreditorClaimType { get; set; }
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:N2}")]
    public Decimal ClaimedTotalAmount { get; set; }
}

コントローラーGET:

public async Task<ActionResult> Edit(int id)
    {
        var testmodel = new CreditorViewModel
        {
            CreditorId = 1,
            Comments = "test",
            Claims = new HashSet<CreditorClaimViewModel>{
                new CreditorClaimViewModel{ CreditorClaimId=1, CreditorClaimType="1", ClaimedTotalAmount=0.00M},
                new CreditorClaimViewModel{ CreditorClaimId=2, CreditorClaimType="2", ClaimedTotalAmount=0.00M},
            }
        };
        return View(model);
    }

Edit.cshtml:

@Html.DisplayNameFor(m => m.Comments)
@Html.EditorFor(m => m.Comments)

<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(m => Model.Claims.FirstOrDefault().CreditorClaimType)
        </th>
        <th>
            @Html.DisplayNameFor(m => Model.Claims.FirstOrDefault().ClaimedTotalAmount)
        </th>
    </tr>        
<!--Option One-->
@foreach (var item in Model.Claims)
{
    var fieldPrefix = string.Format("{0}[{1}].", "Claims", item.CreditorClaimId);
    <tr>
        <td>
            @Html.DisplayFor(m => item.CreditorClaimType)
        </td>
        <td>
        @Html.TextBox(fieldPrefix + "ClaimedTotalAmount", item.ClaimedTotalAmount.ToString("F"),
        new
        {
            @class = "text-box single-line",
            data_val = "true",
            data_val_number = "The field ClaimedTotalAmount must be a number.",
            data_val_required = "The ClaimedTotalAmount field is required."
        })
        @Html.Hidden(name: "Claims.index", value: item.CreditorClaimId, htmlAttributes: null)
        @Html.Hidden(name: fieldPrefix + "CreditorClaimId", value: item.CreditorClaimId, htmlAttributes: null)
        </td>
    </tr>
    }
</table>    
<!--Option Two-->
@for (var itemCnt = 0; itemCnt < Model.ClaimsArray.Count(); itemCnt++)
{
    <tr>
        <td></td>
        <td>
            @Html.TextBoxFor(m => Model.ClaimsArray[itemCnt].ClaimedTotalAmount)
            @Html.HiddenFor(m => Model.ClaimsArray[itemCnt].CreditorClaimId)
    </td></tr>
}

フォームはコントローラーで処理されます:

投稿モデル:

public class CreditorPostViewModel
{
    public int CreditorId { get; set; }
    public string Comments { get; set; }
    public ICollection<CreditorClaimPostViewModel> Claims { get; set; }
    public CreditorClaimPostViewModel[] ClaimsArray  { get; set; }
}

public class CreditorClaimPostViewModel
{
    public int CreditorClaimId { get; set; }
    public Decimal ClaimedTotalAmount { get; set; }
}

コントローラ:

[HttpPost]
    public ActionResult Edit(int id, CreditorPostViewModel creditorVm)
    {
        //...
30
OzBob

この投稿で私を正しい方向に向けてくれてありがとう。非シーケンシャルIDictionary<string, bool>オブジェクトをバインドするための構文を正しく取得するのに苦労しました。これが100%正しいかどうかはわかりませんが、このRazorコードは役に立ちました。

<input type="hidden" name="MyDictionary.Index" value="ABC" />
<input type="hidden" name="MyDictionary[ABC].Key" value="ABC" />
@Html.CheckBox(name: "MyDictionary[ABC].Value", isChecked: Model.MyDictionary["ABC"], htmlAttributes: null)

チェックボックスが必要な場合は、標準のHTMLチェックボックスの代わりに必ずHtml.CheckBoxを使用してください。値が提供されない場合、モデルは爆発し、Html.CheckBoxは非表示フィールドを生成して、チェックボックスがチェックされていないときに値が存在することを確認します。

4
Nick Hotalling

Model.Questions[i]は順番にレンダリングします。

例えば、 Model.Questions[0], Model.Questions[1], Model.Questions[2]。順序が正しくない場合、mvcモデルバインダーは最初の要素のみをバインドすることに気付きました。

3
salli

私はこのコードを使用するかもしれません

<input type="hidden" name="OffersCampaignDale[@(item.ID)].ID" value="@(item.ID)" />

@Html.Raw(Html.EditorFor(modelItem => item.NameDale, new { htmlAttributes = new { @class = "form-control" } })
.ToString().Replace("item.NameDale", "OffersCampaignDale[" + item.ID+ "].NameDale").Replace("item_NameDale", "NameDale-" + item.ID))
@Html.ValidationMessageFor(modelItem => item.NameDale, "", new { @class = "text-danger" })
0

Razorを使用すると、オブジェクトを変更せずに、次のように辞書を使用してforループを実装できます。

@foreach (var x in Model.Questions.Select((value,i)=>new { i, value }))
{
     if (Model.Questions[x.i].QuestionType == "Single")
     {
          @Html.EditorFor(modelItem => (modelItem.Questions[x.i] as OpenDataPortal.ViewModels.SingleQuestionViewModel).AnswerText)
     }
   ...
}

これを機能させるには、コレクションがリストまたは配列である必要があります。

0
user942620