我正在使用Steve Sanderson的BeginCollectionItem方法来添加动态内容。当我在第一级做它时,一切正常。但是,当尝试在另一个BeginCollectionItem中实现嵌套集合意味着BeginCollectionItem时,它似乎不起作用。
我的模型如下:
public class Order
{
[Key]
[HiddenInput]
public int id { get; set; }
[Display(Name = "Order number")]
public string number { get; set; }
...
[Display(Name = "Payment method")]
public List<PaymentMethod> payment_methods { get; set; }
...
}
public class PaymentMethod
{
public MethodOfPayment method { get; set; }
public CC cc { get; set; }
public CASH cash { get; set; }
public TT tt { get; set; }
}
public class TT
{
[Key]
public int id { get; set; }
[Required(ErrorMessage = "{0} is required.")]
[Display(Name = "Total amount")]
public double? total_value { get; set; }
...
[Display(Name = "Transfers")]
public List<Transfer> transfers { get; set; }
}
public class Transfer
{
[Key]
public int id { get; set; }
[Display(Name = "Payment")]
public int payment_id { get; set; }
[Required(ErrorMessage = "{0} is required.")]
[Display(Name = "SWIFT")]
public string swift { get; set; }
[Required(ErrorMessage = "{0} is required.")]
[Display(Name = "Amount transferred")]
public double? transfer_amount { get; set; }
[Required(ErrorMessage = "{0} is required.")]
[Display(Name = "Date transferred")]
public DateTime transfer_date { get; set; }
...
}
现在我所拥有的订单可能有多种付款方式,如果其中一种付款方式是TT(电传转帐),则可能涉及多次转帐。 在一个订单中实现多个支付方法作为集合可以工作,但是当我尝试在TT中实现多个传输时,这些传输中的任何一个都不会传递给控制器。
以下是我的观点:
@model prj.Models.Model.Order
@using (Html.BeginForm("Create")){
@Html.ValidationSummary(true, "Creation was unsuccessful. Please correct the errors and try again.")
...
@Html.TextBoxFor(m => m.number, new { id = "txtnumber" })
...
<div id="editorPaymentRows">
@foreach (var payment in Model.payment_methods)
{
@Html.Partial("_NewPayment", payment)
}
</div>
}
在_NewPayment部分:
@using prj.Helpers
@model prj.Models.Model.PaymentMethod
<div class="editPaymentRow">
@using (Html.BeginCollectionItem("payment_methods"))
{
...
<div class="editor-label">
@Html.LabelFor(m => m.tt.total_value)<req>*</req>
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.tt.total_value)
</div>
...
<div id="editorTransferRows">
@if (Model.tt != null)
{
foreach (var transfer in Model.tt.transfers)
{
@Html.Partial("_NewTransfer", transfer)
}
}
...
</div>
}
</div>
最后在_NewTransfer partial:
@using prj.Helpers
@model prj.Models.Model.Transfer
...
<div class="editTransferRow">
//using (Html.BeginCollectionItem("transfers"))
@using (Html.BeginCollectionItem("tt.transfers"))
{
...
<div class="editor-label">
@Html.LabelFor(m => m.swift)<req>*</req>
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.swift, new { @class = "t_swift" })
</div>
...
<div class="editor-label">
@Html.LabelFor(m => m.transfer_amount)<req>*</req>
</div>
<div class="editor-field">
@Html.TextBoxFor(m => m.transfer_amount, new { @class = "t_transfer_amount" })
</div>
...
}
</div>
所以一切正常,除了在控制器中,List传输在PaymentMethods的TT属性中始终为null。它没有正确传递给控制器。有什么我想念的吗?
嵌套的BeginCollectionItem不起作用吗?我必须做一个额外的步骤吗? 请说清楚。 感谢
我用以下链接中显示的Joe Stevens的方法想出来了:
http://www.joe-stevens.com/2011/06/06/editing-and-binding-nested-lists-with-asp-net-mvc-2/
干杯
答案 0 :(得分:15)
要获得Html.BeginCollectionItem
的前缀,您可以访问ViewData.TemplateInfo.HtmlFieldPrefix
(我正在使用nuget包)。您使用tt.transfers
走在正确的轨道上,但您需要使用特定的前缀。
而不仅仅是
Html.BeginCollectionItem("tt.transfers")
您还需要当前payment_method的前缀。
@{
var paymentMethodPrefix = ViewData.TemplateInfo.HtmlFieldPrefix;
}
@using (Html.BeginCollectionItem(paymentMethodPrefix + ".tt.transfers"))
快速测试看起来你也可以:
@using (Html.BeginCollectionItem(ViewData.TemplateInfo.HtmlFieldPrefix + ".tt.transfers"))
答案 1 :(得分:2)
我无法正确地将Job Stevens的方法与MVC 5相适应。我仅使用Job Stevens的扩展类下面的名称为BeginCollectionItem2的扩展
public static class HtmlPrefixScopeExtensions
{
private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem2(this HtmlHelper html, string collectionName)
{
if (html.ViewData["ContainerPrefix"] != null)
{
collectionName = string.Concat(html.ViewData["ContainerPrefix"], ".", collectionName);
}
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
var htmlFieldPrefix = string.Format("{0}[{1}]", collectionName, itemIndex);
html.ViewData["ContainerPrefix"] = htmlFieldPrefix;
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, htmlFieldPrefix);
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
string key = idsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
脚本如下:
function addRow() {
$.ajax({
type: "POST",
data: {processTypeId:@Model.Id},
url: '@Url.Action("GetFlowItemRow", "Flow")',
success: function (partialView) {
$('#divItemList').append(partialView);
}
});
}
function addParameterRow(rw, prx) {
$.ajax({
type: "POST",
url: '@Url.Action("GetFlowItemParameterRow", "Flow")' + '?pId=' + '@Model.Id' + '&prefix=' + prx ,
success: function (partialView) {
rw.closest('table').find("tbody").append(partialView);
}
});
}
html按钮可添加部分视图,例如:
<a title="Add Operation" href="javascript:;" onclick="addRow()">
<i class="la la-plus-circle"></i>
</a>
<a title="Add Operation Parameter" href="javascript:;" onclick="addParameterRow($(this),'@ViewData["ContainerPrefix"]')">
<i class="la la-plus-circle"></i>
</a>
控制器上的局部视图方法:
public PartialViewResult GetFlowItemRow(int? processTypeId)
{
FlowItemModel _item = new FlowItemModel() { ProcessTypeId = processTypeId ?? 0 };
return PartialView("~/Views/Flow/Partial/_FlowItem.cshtml", _item);
}
public PartialViewResult GetFlowItemParameterRow(int? pId, string prefix)
{
ViewData["ContainerPrefix"] = prefix;
FlowItemParameterModel _item = new FlowItemParameterModel() { };
return PartialView("~/Views/Flow/Partial/_FlowItemParameter.cshtml", _item);
}
流项部分:
<tr>
@using (Html.BeginCollectionItem2("OperationList"))
{
@Html.HiddenFor(model => model.ItemId)
<td style="vertical-align:middle">
@Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
</td>
<td>
<table style="width:100%">
<thead>
<tr>
<th class="kt-font-success">Name</th>
<th class="kt-font-success">Unit</th>
<th>
<a title="Add Parameter" href="javascript:;" onclick="addParameterRow($(this),'@ViewData["ContainerPrefix"]')">Add
</a>
</th>
</tr>
</thead>
<tbody id="divParameterList">
</tbody>
</table>
</td>
}
</tr>
项目参数部分
<tr>
@using (Html.BeginCollectionItem2("ParameterList"))
{
<td>@Html.TextBoxFor(m => m.ParameterName, new { @class = "form-control" })</td>
<td>
@Html.TextBoxFor(m => m.Unit, new { @class = "form-control" })
</td>
}</tr>
答案 2 :(得分:1)
您可以使用JonK答案中的代码,但不适用于ajax调用中动态添加的部分,因为“ ViewData.TemplateInfo.HtmlFieldPrefix”为空。
解决方案是使用前缀形式将属性添加到模型中,并在ajax函数调用的操作中填充该属性。
要使用jQuery获取表单前缀,您只需执行以下操作:
// "this" a button that will trigger the ajax call and is within the div with the class "partial-enclosing-class"
var parentRow = $(this).parents('.partial-enclosing-class');
var hiddenInput = parentRow.find('input[name$="].Id"]');
var formPrefix = hiddenInput.prop('name').replace(".Id", "");