在ASP.NET MVC中使用IEnumerable模型的自定义编辑器模板的正确,惯用的方法
这个问题是为什么我的DisplayFor循环通过我的IEnumerable <DateTime>的后续?
快速刷新。
什么时候:
- 该模型具有
IEnumerable<T>
types的属性 - 您使用只接受lambdaexpression式的重载将此属性传递给
Html.EditorFor()
- 在Views / Shared / EditorTemplates下有一个types为
T
的编辑器模板
那么MVC引擎将自动为可枚举序列中的每个项目调用编辑器模板,生成结果列表。
例如,当有一个具有属性Lines
的模型类Order
:
public class Order { public IEnumerable<OrderLine> Lines { get; set; } } public class OrderLine { public string Prop1 { get; set; } public int Prop2 { get; set; } }
并有一个视图/共享/ EditorTemplates / OrderLine.cshtml:
@model TestEditorFor.Models.OrderLine @Html.EditorFor(m => m.Prop1) @Html.EditorFor(m => m.Prop2)
然后,当您从顶层视图调用@Html.EditorFor(m => m.Lines)
,您将得到一个包含每个订单行的文本框的页面,而不仅仅是一个。
但是,正如您在链接问题中所看到的那样,这只有在使用EditorFor
特定重载EditorFor
。 如果您提供了一个模板名称(为了使用一个没有以OrderLine
类命名的模板),那么自动序列处理将不会发生,而是会发生运行时错误。
在这一点上,您将不得不将自定义模板的模型声明为IEnumebrable<OrderLine>
并手动迭代它的项目以输出所有项目,例如
@foreach (var line in Model.Lines) { @Html.EditorFor(m => line) }
这就是问题出现的地方。
以这种方式生成的HTML控件都具有相同的ID和名称。 当您稍后POST它们时,模型联编程序将无法构buildOrderLine
的数组,并且您在控制器中的HttpPost方法中获得的模型对象将为null
。
如果您查看lambdaexpression式,这是有道理的 – 它并不真正将正在构build的对象链接到模型中的某个位置。
我已经尝试了遍历这些项目的各种方法,似乎唯一的方法是将模板的模型重新声明为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) }
然后在顶层视图中:
@model TestEditorFor.Models.Order @using (Html.BeginForm()) { @Html.EditorFor(m => m.Lines, "CustomTemplateName") }
它提供了正确命名的HTML控件,可以在提交时被模型联编程序正确识别。
虽然这有效,但感觉非常错误。
在EditorFor
使用自定义编辑器模板的正确惯用方法是什么,同时保留所有允许引擎生成适合模型绑定器的HTML的逻辑链接?
在和Erik Funkenbusch讨论后,他们开始研究MVC源代码 ,看起来有两种更好的(正确和习惯的)方法来做到这一点。
两者都涉及提供正确的html名称前缀给助手,并生成与默认EditorFor
的输出相同的HTML。
我现在就把它留在这里,将做更多的testing,以确保它在深度嵌套的情况下工作。
对于以下示例,假设您已经有OrderLine
类的两个模板: OrderLine.cshtml
和DifferentOrderLine.cshtml
。
方法1 – 为IEnumerable<T>
使用中间模板
创build一个辅助模板,以任何名称保存(例如“ManyDifferentOrderLines.cshtml”):
@model IEnumerable<OrderLine> @{ int i = 0; foreach (var line in Model) { @Html.EditorFor(m => line, "DifferentOrderLine", "[" + i++ + "]") } }
然后从主Order模板中调用它:
@model Order @Html.EditorFor(m => m.Lines, "ManyDifferentOrderLines")
方法2 – 没有IEnumerable<T>
的中间模板
在主Order模板中:
@model Order @{ int i = 0; foreach (var line in Model.Lines) { @Html.EditorFor(m => line, "DifferentOrderLine", "Lines[" + i++ + "]") } }
有许多方法可以解决这个问题。 在编辑器模板中指定模板名称时,无法获取默认的IEnumerable支持。 首先,我build议如果你在同一个控制器中有相同types的多个模板,你的控制器可能有太多的责任,你应该考虑重构它。
话虽如此, 最简单的解决scheme是一个自定义的数据types 。 除了UIHints和typenames之外,MVC还使用DataTypes。 看到:
自定义EditorTemplate不在MVC4中用于DataType.Date
所以,你只需要说:
[DataType("MyCustomType")] public IEnumerable<MyOtherType> {get;set;}
然后,您可以在编辑器模板中使用MyCustomType.cshtml。 不像UIHint,这不会受到缺乏IEnuerable支持 。 如果您的使用情况支持默认types(例如,电话或电子邮件,则优先使用现有的types枚举)。 或者,您可以派生自己的DataType属性并使用DataType.Custom作为基础。
您也可以简单地将您的types换成另一种types以创build不同的模板。 例如:
public class MyType {...} public class MyType2 : MyType {}
然后,您可以很容易地创build一个MyType.cshtml和MyType2.cshtml,并且您可以始终将MyType2作为MyType用于大多数目的。
如果对您来说这太“黑客”了,您可以始终根据通过编辑器模板的“additionalViewData”parameter passing的参数来构build模板以进行不同的呈现。
另一个select是使用传递模板名称来进行types设置的版本,如创build表格标签或其他types的格式,然后使用更通用的types版本来渲染来自命名模板的更一般的forms。
这使您可以拥有一个CreateMyType模板和一个EditMyType模板,这些模板除了各个订单项(您可以与之前的build议结合使用)之外都是不同的。
另外一个select是,如果你不使用DisplayTemplates这种types,你可以使用DisplayTempates替代模板(当创build一个自定义模板时,这只是一个惯例..当使用内置的模板,那么它只会创build显示版本)。 当然,这是违反直觉的,但是如果只有两个你需要使用的相同types的模板,并且没有相应的显示模板,它确实可以解决这个问题。
当然,您总是可以将IEnumerable转换为模板中的数组,而不需要重新声明模型types。
@model IEnumerable<MyType> @{ var model = Model.ToArray();} @for(int i = 0; i < model.Length; i++) { <p>@Html.TextBoxFor(x => model[i].MyProperty)</p> }
我大概可以想出其他十几种方法来解决这个问题,但实际上,无论我有没有这样做,我发现如果我考虑这个问题,我可以简单地重新devise我的模型或视图。方式不再需要解决。
换句话说,我认为这个问题是一种“代码味道”,并且表明我可能做错了什么,重新思考这个过程通常会产生一个没有问题的更好的devise。
所以要回答你的问题。 正确的,惯用的方法是重新devise你的控制器和视图,以便这个问题不存在。 除此之外, select最less的攻击性“黑客”来实现你想要的 。
似乎没有比@GSerg的回答更容易实现这一点的方法 。 奇怪的是,MVC团队还没有拿出一个混乱的方式来做到这一点。 我已经使这个扩展方法至less在一定程度上封装它:
public static MvcHtmlString EditorForEnumerable<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string templateName) { var fieldName = html.NameFor(expression).ToString(); var items = expression.Compile()(html.ViewData.Model); return new MvcHtmlString(string.Concat(items.Select((item, i) => html.EditorFor(m => item, templateName, fieldName + '[' + i + ']')))); }
您可以使用UIHint
属性来指导您想要为编辑器加载的视图。 所以你的Order
对象看起来像这样使用UIHint
public class Order { [UIHint("Non-Standard-Named-View")] public IEnumerable<OrderLine> Lines { get; set; } }
- 使用String.split()提取单词对
- 升级到Google Play服务:9.0.0错误无法解决:com.google.android.gms:play-services-measurement:9.0.0
- 通过POST(ajax)发送JSON数据并从Controller(MVC)接收json响应
- 哪里和如何链接_ViewStart.cshtml布局文件?
- 无法findtypes或名称空间名称“DbContext”
- 如何重新创buildentity framework的数据库?
- AllowAnonymous不能使用Custom AuthorizationAttribute
- ASP.NET MVC全局variables
- MVC4的DataType.Date EditorFor不会显示在Chrome中的date值,在Internet Explorer罚款
- 将“自定义操作筛选器”中的ASP.NET MVC Pass对象传递
- 检索ASP.NET MVC中的当前视图名称?