在ASP.NET MVC中使用自定义编辑器模板和IEnumerable模型的正确,惯用方法

GSe*_*erg 50 c# asp.net-mvc ienumerable view-templates razor-3

这个问题是为什么我的DisplayFor没有遍历我的IEnumerable <DateTime>的后续行动


快速刷新.

什么时候:

  • 该模型具有类型的属性 IEnumerable<T>
  • 您将此属性传递给Html.EditorFor()使用仅接受lambda表达式的重载
  • T在Views/Shared/EditorTemplates下有一个类型的编辑器模板

然后MVC引擎将自动为可枚举序列中的每个项调用编辑器模板,生成结果列表.

例如,当存在Order具有属性的模型类时Lines:

public class Order
{
    public IEnumerable<OrderLine> Lines { get; set; }
}

public class OrderLine
{
    public string Prop1 { get; set; }
    public int Prop2 { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

并且有一个视图Views/Shared/EditorTemplates/OrderLine.cshtml:

@model TestEditorFor.Models.OrderLine

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

然后,当您@Html.EditorFor(m => m.Lines)从顶级视图调用时,您将获得一个页面,其中包含每个订单行的文本框,而不仅仅是一个.


但是,正如您在链接问题中看到的那样,这仅在您使用特定的重载时才有效EditorFor.如果您提供模板名称(为了使用未在OrderLine类之后命名的模板),则不会发生自动序列处理,而是会发生运行时错误.

此时,您必须将自定义模板的模型声明为IEnumebrable<OrderLine>并以某种方式手动迭代其项目以输出所有这些项目,例如

@foreach (var line in Model.Lines) {
    @Html.EditorFor(m => line)
}
Run Code Online (Sandbox Code Playgroud)

这就是问题的开始.

以这种方式生成的HTML控件都具有相同的ID和名称.当你稍后发布它们时,模型绑定器将无法构造一个OrderLines 数组,并且你在控制器中的HttpPost方法中获得的模型对象将是null.
如果你看一下lambda表达式,这是有道理的 - 它并没有真正地将正在构造的对象链接到它所来自的模型中的一个位置.

我已经尝试了各种迭代项目的方法,看起来唯一的方法是重新声明模板的模型,IList<T>并用for以下方法枚举它:

@model IList<OrderLine>

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

然后在顶级视图中:

@model TestEditorFor.Models.Order

@using (Html.BeginForm()) {
    @Html.EditorFor(m => m.Lines, "CustomTemplateName")
}
Run Code Online (Sandbox Code Playgroud)

它提供了正确命名的HTML控件,这些控件在提交时由模型绑定器正确识别.


虽然这有效,但感觉非常错误.

使用自定义编辑器模板的正确,惯用方法是什么EditorFor,同时保留允许引擎生成适合模型绑定器的HTML的所有逻辑链接?

GSe*_*erg 33

与Erik Funkenbusch进行讨论后,我们看到了MVC源代码,看起来有两种更好的(正确的和惯用的)方法.

两者都涉及为帮助程序提供正确的html名称前缀,并生成与默认输出相同的HTML EditorFor.

我现在暂时将它留在这里,将进行更多测试以确保它在深度嵌套的场景中工作.

对于以下示例,假设您已经有两个OrderLine类的模板:OrderLine.cshtmlDifferentOrderLine.cshtml.


方法1 - 使用中间模板 IEnumerable<T>

创建一个帮助器模板,以任何名称保存它(例如"ManyDifferentOrderLines.cshtml"):

@model IEnumerable<OrderLine>

@{
    int i = 0;

    foreach (var line in Model)
    { 
        @Html.EditorFor(m => line, "DifferentOrderLine", "[" + i++ + "]")
    }
}
Run Code Online (Sandbox Code Playgroud)

然后从主Order模板中调用它:

@model Order

@Html.EditorFor(m => m.Lines, "ManyDifferentOrderLines")
Run Code Online (Sandbox Code Playgroud)

方法2 - 没有中间模板 IEnumerable<T>

在主订单模板中:

@model Order

@{
    int i = 0;

    foreach (var line in Model.Lines)
    {
        @Html.EditorFor(m => line, "DifferentOrderLine", "Lines[" + i++ + "]")
    }
}
Run Code Online (Sandbox Code Playgroud)