asp.net-mvc MVC 发布复杂对象列表

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26155607/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-07 23:15:39  来源:igfitidea点击:

MVC post a list of complex objects

asp.net-mvcformsinheritancepostcomplex-data-types

提问by Carel

I have a FeedbackViewModel that contains a list of questions:

我有一个包含问题列表的 FeedbackViewModel:

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

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

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

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 HttpGetof the Indexaction in the controller I get the questions from the database and add the correct question type in list of question in the FeedbackViewModelThen I render this model in the view:

在控制器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

在此处输入图片说明



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:

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

enter image description here

在此处输入图片说明

回答by OzBob

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

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)

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

HTML snippet that inspired me:

激发我灵感的 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>

Post array looks a bit like:

Post 数组看起来有点像:

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:

编辑.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)
    {
        //...

回答by Nick Hotalling

Thanks for pointing me in the right direction with this post. I was struggling to get the syntax right for binding a non-sequential IDictionary<string, bool>object. Not sure this is 100% correct, but this Razor code worked for me:

感谢您通过这篇文章为我指明正确的方向。我正在努力使绑定非顺序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)

If you need a checkbox, be sure to use Html.CheckBox instead of a standard HTML checkbox. The model will blow up if a value is not provided, and Html.CheckBox generates a hidden field to ensure a value is present when the checkbox is not checked.

如果您需要复选框,请务必使用 Html.CheckBox 而不是标准的 HTML 复选框。如果未提供值,模型将爆炸,并且 Html.CheckBox 会生成一个隐藏字段以确保未选中复选框时存在值。

回答by salli

Make sure you are rendering your view in order so that Model.Questions[i]renders in order.

确保按顺序渲染视图,以便按顺序Model.Questions[i]渲染。

For example, Model.Questions[0], Model.Questions[1], Model.Questions[2]. I noticed that if the order is not correct mvc model binder will only bind the first element.

例如,Model.Questions[0], Model.Questions[1], Model.Questions[2]。我注意到如果顺序不正确 mvc 模型绑定器只会绑定第一个元素。

回答by user942620

Using Razor you can implement the for loop using a dictionary as follows without making changes to your object:

使用 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)
     }
   ...
}

The collection needs to be either a List or Array for this to work.

集合需要是列表或数组才能工作。

回答by Yitzhak Weinberg

I use this code maybe its can help

我使用此代码也许它可以帮助

<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" })