在编辑器模板中迭代集合时,NameFor会生成错误的名称

假设我有一个类型为集合的视图,例如List

 @model List @for(int i = 0; i  m[i].Foo) @Html.EditorFor(m => m[i].Bar) } 

FooBar只是字符串属性。

这将生成[i].Foo[i].Barforms的HTML名称属性,当然,这些属性是正确的,并且在表单中发布时可以正确绑定。

现在假设上面的视图是一个编辑器模板,它是这样呈现的(其中Model.ItemsList ):

 @model WrappingViewModel @Html.EditorFor(m => m.Items) 

突然之间,编辑器模板中生成的名称的forms – 例如 – Items.[i].Foo 。 默认模型绑定器不能绑定它,因为它需要表单Items[i].Foo

这在第一个场景中运行良好 – 视图不是编辑器模板 – 并且在集合是属性的情况下也可以正常工作,而不是整个模型:

 @Html.EditorFor(m => m.Items[i].Foo) 

它仅在模型本身是集合视图是编辑器模板时失败。

有几种方法可以解决这个问题,其中没有一个是理想的:

  • 将编辑器模板键入到ItemViewModel的单个实例中 – 这不好,因为相关的实际模板包含用于添加到集合中/从集合中删除的附加标记。 我需要能够使用模板中的整个集合。
  • List包装在另一个属性中(例如通过实现ItemListViewModel )并将其传递给模板 – 这也不是理想的,因为这是一个企业应用程序,我宁愿不会因多余的包装视图模型而混乱。
  • 手动生成内部编辑器模板的标记以生成正确的名称 – 这是我目前正在做的但我宁愿避免它,因为我失去了HtmlHelpers的灵活性。

所以,问题是:为什么NameFor (以及因此EditorFor )在这种特殊情况下表现出这种行为,当它适用于轻微变化时(即它是有意的,如果是,为什么)? 是否有一种简单的方法来解决这种行为而没有上述任何缺点?

根据要求,完整代码重现:

楷模:

 public class WrappingViewModel { [UIHint("_ItemView")] public List Items { get; set; } public WrappingViewModel() { Items = new List(); } } public class ItemViewModel { public string Foo { get; set; } public string Bar { get; set; } } 

控制器动作:

 public ActionResult Index() { var model = new WrappingViewModel(); model.Items.Add(new ItemViewModel { Foo = "Foo1", Bar = "Bar1" }); model.Items.Add(new ItemViewModel { Foo = "Foo2", Bar = "Bar2" }); return View(model); } 

Index.cshtml:

 @model WrappingViewModel @using (Html.BeginForm()) { @Html.EditorFor(m => m.Items)  } 

_ItemView.cshtml(编辑器模板):

 @model List @for(int i = 0; i  m[i].Foo) @Html.EditorFor(m => m[i].Bar) } 

FooBar输入的名称属性将采用Model.[i].Property的forms,并且在发布到具有签名ActionResult Index(WrappingViewModel)的操作方法时不会绑定回来。 请注意,如上所述,如果您在主视图中迭代Items或者如果您摆脱了WrappingViewModel ,则使顶层模型成为List并直接迭代Model可以正常工作。 它只适用于此特定方案。

为什么NameFor (以及因此EditorFor )在这种特殊情况下表现出这种行为,当它适用于轻微变化时(即它是有意的,如果是,为什么)?

这是一个错误( 链接 ),它将通过ASP.NET MVC 5的发布来修复。

是否有一种简单的方法来解决这种行为而没有上述任何缺点?

简单:

  1. 使用以下代码添加ItemViewModel.cshtml编辑器模板:

     @model ItemViewModel @Html.EditorFor(m => m.Foo) @Html.EditorFor(m => m.Bar) 
  2. 删除_ItemView.cshtml编辑器模板。

  3. [UIHint("_ItemView")]删除[UIHint("_ItemView")]属性。

有点难:

  1. 添加ItemViewModel.cshtml编辑器模板(与上面相同)。

  2. 修改_ItemView.cshtml

     @model List @{ string oldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix; try { ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty; for (int i = 0; i < Model.Count; i++) { var item = Model[i]; string itemPrefix = string.Format("{0}[{1}]", oldPrefix, i.ToString(CultureInfo.InvariantCulture)); @Html.EditorFor(m => item, null, itemPrefix) } } finally { ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix; } } 

UPDATE

如果您不想为第二个选项添加ItemViewModel.cshtml编辑器模板,而不是@Html.EditorFor(m => item, null, itemPrefix)您必须编写类似的内容:

 @Html.EditorFor(m => item.Foo, null, Html.NameFor(m => item.Foo).ToString().Replace("item", itemPrefix)) @Html.EditorFor(m => item.Bar, null, Html.NameFor(m => item.Bar).ToString().Replace("item", itemPrefix)) 

注意 :最好将该段代码包装为扩展方法