名称用于在编辑器模板中迭代集合时生成错误的名称

时间:2013-09-21 15:31:50

标签: 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)
}

FooBar只是字符串属性。

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

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

@model WrappingViewModel

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

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

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

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

仅当模型本身是集合并且视图是编辑器模板时才会失败。

有几种解决方法,其中没有一种是理想的:

  • 将编辑器模板键入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; }
}

控制器操作:

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)
    <input type="submit" value="Submit" />
}

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

FooBar输入的名称属性将采用Model.[i].Property格式,并且在发布到带有签名ActionResult Index(WrappingViewModel)的操作方法时不会绑定回来。请注意,如上所述,如果您在主视图中迭代Items,如果您摆脱WrappingViewModel,则可以正常工作,将顶级模型设为{ {1}}并直接迭代List<ItemViewModel>。它只适用于这种特定情况。

1 个答案:

答案 0 :(得分:5)

  

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

这是一个错误(link),它将在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")]删除WrappingViewModel属性。
  4. 稍微努力一点:

    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;
          }
      }
      
    3. <强>更新

      如果您不想为 second 选项添加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))
      

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