MVC 发布复杂对象列表 [英] MVC post a list of complex objects

查看:22
本文介绍了MVC 发布复杂对象列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个包含问题列表的反馈视图模型:

公共类FeedbackViewModel{公共列表<问题视图模型>问题{得到;放;}}

这个QuestionViewModel是一个可以被5种不同类型问题继承的对象

公共类 QuestionViewModel{公共字符串 QuestionText { get;放;}公共字符串 QuestionType { get;放;}}

继承问题类型之一的示例:

公共类 SingleQuestionViewModel : QuestionViewModel{公共字符串 AnswerText { 获取;放;}}

在控制器中 Index 操作的 HttpGet 中,我从数据库中获取问题并在 FeedbackViewModel 的问题列表中添加正确的问题类型 然后我在视图中渲染这个模型:

@using (Html.BeginForm()){//foreach(Model.Questions中的var item)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] 作为 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)}

<br/>}<br/><button type="submit">提交</button>}

<小时>

现在,我根本无法在模型中发布问题列表.甚至可以发布不同对象类型的列表吗?

<小时>

以下是我使用 Fiddler 发现的帖子中的数据列表:

解决方案

经过大量研究,我找到了两个解决方案:

  1. 一种是编写具有硬编码 ID 和名称的 HTML
  2. 二是将您的 ICollection/IEnumerable 转换为数组或列表(即 IList 带有索引"的内容),并在您的控制器 POST 操作中的 BindingModel 中有一个数组对象.

感谢 Phil Haack (@haacked) 2008 年的博客文章 http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/这仍然与今天默认的 ModelBinder 为 MVC 工作的方式有关.(注意:Phil 文章中示例项目和扩展方法的链接已损坏)

激发我灵感的 HTML 片段:

<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"/><输入类型=提交"/></表单>

帖子数组看起来有点像:

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

型号:

公共类 CreditorViewModel{公共 CreditorViewModel(){this.Claims = new HashSet();}[钥匙]公共 int CreditorId { 获取;放;}公共字符串注释 { 获取;放;}公共 ICollection索赔{得到;放;}公共 CreditorClaimViewModel[] ClaimsArray {得到 { 返回 Claims.ToArray();}}}公共类 CreditorClaimViewModel{[钥匙]公共 int CreditorClaimId { 获取;放;}公共字符串 CreditorClaimType { 获取;放;}[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:N2}")]public Decimal ClaimedTotalAmount { get;放;}}

控制器获取:

public async Task编辑(int id){var testmodel = 新 CreditorViewModel{债权人Id = 1,评论 = "测试",声明 = 新的 HashSet{新 CreditorClaimViewModel{ CreditorClaimId=1, CreditorClaimType="1", ClaimedTotalAmount=0.00M},新 CreditorClaimViewModel{ CreditorClaimId=2, CreditorClaimType="2", ClaimedTotalAmount=0.00M},}};返回视图(模型);}

编辑.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><!--选项一-->@foreach(Model.Claims 中的 var 项目){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"),新的{@class = "文本框单行",data_val = "真",data_val_number = "该字段 ClaimedTotalAmount 必须是一个数字.",data_val_required = " ClaimedTotalAmount 字段是必需的."})@Html.Hidden(name: "Claims.index", value: item.CreditorClaimId, htmlAttributes: null)@Html.Hidden(name: fieldPrefix + "CreditorClaimId", value: item.CreditorClaimId, htmlAttributes: null)</td></tr>}<!--选项二-->@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>}

表单在控制器中处理:

发布模型:

公共类 CreditorPostViewModel{公共 int CreditorId { 获取;放;}公共字符串注释 { 获取;放;}公共 ICollection索赔{得到;放;}公共 CreditorClaimPostViewModel[] ClaimsArray { get;放;}}公共类 CreditorClaimPostViewModel{公共 int CreditorClaimId { 获取;放;}public Decimal ClaimedTotalAmount { get;放;}}

控制器:

[HttpPost]公共操作结果编辑(int id,CreditorPostViewModel creditorVm){//...

I have a FeedbackViewModel that contains a list of questions:

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

This QuestionViewModel is an object that can be inherited by 5 different types of questions

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

An example of one of the inheriting question types:

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

In the HttpGet of the Index action in the controller I get the questions from the database and add the correct question type in list of question in the FeedbackViewModel Then I render this model in the view:

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


Now, I simply can't get it to post the list of questions in the model. Is it even possible to post a list of different object types?


Edit: Following is the list of data within the post that I discovered using Fiddler:

解决方案

After much research I've found two solutions:

  1. One is to write HTML that has hardcoded Id's and Names
  2. Two is to convert your ICollection/IEnumerable to an Array or List (i.e IList something with an 'index'), and have an Array object in your BindingModel in your Controller POST Action.

Thanks to Phil Haack's (@haacked) 2008 blog post http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/ Which is still relevant to how the default ModelBinder works today for MVC. (NB: the links in Phil's article to sample porject and extension methods are broken)

HTML snippet that inspired me:

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

Post array looks a bit like:

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

Model:

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

Controller 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>
}

Form is processed in the Controller:

Post Model:

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

Controller:

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

这篇关于MVC 发布复杂对象列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆