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

Ant*_*t P 9 c# asp.net-mvc asp.net-mvc-4

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

@model List<ItemViewModel>

@for(int i = 0; i < Model.Count; i++)
{
    @Html.EditorFor(m => m[i].Foo)
    @Html.EditorFor(m => m[i].Bar)
}
Run Code Online (Sandbox Code Playgroud)

Foo并且Bar只是字符串属性.

这产生以下形式的HTML名称属性[i].Foo并且[i].Bar,其中,当然,是正确的,在一个形式发布时正确地结合.

现在假设,上面的视图是一个编辑器模板,它是这样渲染的(其中Model.Items是a List<ItemViewModel>):

@model WrappingViewModel

@Html.EditorFor(m => m.Items)
Run Code Online (Sandbox Code Playgroud)

突然之间,编辑器模板中生成的名称的形式就是 - 例如 - Items.[i].Foo.默认的模型绑定器不能像预期的那样绑定它Items[i].Foo.

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

@Html.EditorFor(m => m.Items[i].Foo)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

楷模:

public class WrappingViewModel
{
    [UIHint("_ItemView")]
    public List<ItemViewModel> Items { get; set; }

    public WrappingViewModel()
    {
        Items = new List<ItemViewModel>();
    }
}

public class ItemViewModel
{
    public string Foo { get; set; }
    public string Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

控制器动作:

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);
}
Run Code Online (Sandbox Code Playgroud)

Index.cshtml:

@model WrappingViewModel

@using (Html.BeginForm())
{
    @Html.EditorFor(m => m.Items)
    <input type="submit" value="Submit" />
}
Run Code Online (Sandbox Code Playgroud)

_ItemView.cshtml(编辑器模板):

@model List<ItemViewModel>

@for(int i = 0; i < Model.Count; i++)
{
    @Html.EditorFor(m => m[i].Foo)
    @Html.EditorFor(m => m[i].Bar)
}
Run Code Online (Sandbox Code Playgroud)

名称属性FooBar输入将是表单的形式,Model.[i].Property并且在发布到具有签名的操作方法时不会绑定回来ActionResult Index(WrappingViewModel).请注意,如上所述,如果您Items在主视图中进行迭代,或者如果您将其删除WrappingViewModel,请将顶级模型设置为a List<ItemViewModel>Model直接迭代,这样可以正常工作.它仅针对此特定方案失败.

Ale*_*nov 5

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

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

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

简单:

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

    @model ItemViewModel
    @Html.EditorFor(m => m.Foo)
    @Html.EditorFor(m => m.Bar) 
    
    Run Code Online (Sandbox Code Playgroud)
  2. 删除_ItemView.cshtml编辑器模板.

  3. [UIHint("_ItemView")]从中删除属性WrappingViewModel.

有点难:

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

  2. 修改_ItemView.cshtml:

    @model List<ItemViewModel>
    
    @{
        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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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))
Run Code Online (Sandbox Code Playgroud)

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