前言
Model是MVC强大的机制之一,它是MVC框架中客户端和服务端数据交互的核心机制。深入的理解Model有助于我们自己在MVC的基础上扩展,也有助于我们创造出更具复用意义的软件模块。主要包含以下议题:
- Templated view helpers:根据Model生成Html控件元素
- Model Binding:自动映射和解析用户提交的数据
- Integrating validation:集成客户端认证
我们知道ASP.NET web应用程序的数据交互其实就是客户端表单数据和.NET对象(Model)之间的转化。下图说明了这个问题:
在MVC中,众多HTML Helper负责将Model转化成Html标记,Model binding将用户提交的数据转化成Model。
Templated View Helpers
MVC2新增的Templated View Helpers指的是类似Html.TextBoxFor()之类的扩展方法。用这样的方法来构造链接表单之类的的Html元素的话,是十分方便和智能的。这些方法会根据Model或Model属性的类型自动决定转换成什么样的Html元素,并自动使得Model Binding得以支持。
比如如果你有个属性叫Approved,是个bool类型,那么Html.EditorFor(x => x.Approved)将会转化成一个check box。比如当调用Html.EditorFor()时,MVC需要选择一个合适的模板呈现,因此模板可以理解成对某种数据结构的预定义的Html的呈现方式。先来看看MVC内建有哪些模板,下面的代码是从TemplateHelpers中摘录的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | static readonly Dictionary< string , Func<HtmlHelper, string >> defaultDisplayActions = new Dictionary< string , Func<HtmlHelper, string >>(StringComparer.OrdinalIgnoreCase) { { "EmailAddress" , DefaultDisplayTemplates.EmailAddressTemplate }, { "HiddenInput" , DefaultDisplayTemplates.HiddenInputTemplate }, { "Html" , DefaultDisplayTemplates.HtmlTemplate }, { "Text" , DefaultDisplayTemplates.StringTemplate }, { "Url" , DefaultDisplayTemplates.UrlTemplate }, { "Collection" , DefaultDisplayTemplates.CollectionTemplate }, { typeof ( bool ).Name, DefaultDisplayTemplates.BooleanTemplate }, { typeof ( decimal ).Name, DefaultDisplayTemplates.DecimalTemplate }, { typeof ( string ).Name, DefaultDisplayTemplates.StringTemplate }, { typeof ( object ).Name, DefaultDisplayTemplates.ObjectTemplate }, }; static readonly Dictionary< string , Func<HtmlHelper, string >> defaultEditorActions = new Dictionary< string , Func<HtmlHelper, string >>(StringComparer.OrdinalIgnoreCase) { { "HiddenInput" , DefaultEditorTemplates.HiddenInputTemplate }, { "MultilineText" , DefaultEditorTemplates.MultilineTextTemplate }, { "Password" , DefaultEditorTemplates.PasswordTemplate }, { "Text" , DefaultEditorTemplates.StringTemplate }, { "Collection" , DefaultEditorTemplates.CollectionTemplate }, { typeof ( bool ).Name, DefaultEditorTemplates.BooleanTemplate }, { typeof ( decimal ).Name, DefaultEditorTemplates.DecimalTemplate }, { typeof ( string ).Name, DefaultEditorTemplates.StringTemplate }, { typeof ( object ).Name, DefaultEditorTemplates.ObjectTemplate }, }; |
从中可以看到内建的模板有哪些。总的来说,Html元素可以分为两类,显示类和编辑类。分别地,主要有两大类的Helper,DisplayXXX、LabelXXX和EditorXXX。MVC框架包含有DefaultDisplayTemplates和DefaultEditorTemplates分别负责完成render的工作。而TemplateHelpers类负责调配选择使用那种模板。MVC内建的模板主要是简单类型,对于复杂类型MVC支持用户自己定义模板,自定义一个模板实际上就是创建ascx,然后放在合适的位置,由View引擎自行查找。框架会审查/Views/Shared/DisplayTemplates/和/Views/Shared/EditorTemplates/目录下的ascx文件,并将其视为自定义模板加载,所以我们可以设计复杂的ascx模板,将他视为用户控件,然后简单的使用TemplateHelper的各种方法来加载,这也不失为一种代替PartialView或ChildAction的方式。这里的ascx文件名要尽量对应类型名,因此,我们可以创建一个DateTime.ascx,并像下面这样编辑代码来“重载”MVC内建对DateTime数据类型的模板。
1 2 3 4 5 | <%@ Control Language="C#" Inherits="ViewUserControl< DateTime ?>" %> <%: Html.TextBox("", /* Name suffix */ ViewData.TemplateInfo.FormattedModelValue, /* Initial value */ new { @class = "date-picker" } /* HTML attributes */ ) %> |
注意到,这里用到ViewData.TemplateInfo.FormattedModelValue,而不是Model.ToString(),这样可以充分利用ModelMetadata的特性,关于ModelMetaData将在以后更多的涉及。这里也可以这样写:
1 2 3 4 5 | <%@ Control Language="C#" Inherits="ViewTemplateUserControl< DateTime ?>" %> <%: Html.TextBox("", /* Name suffix */ FormattedModelValue, /* Initial value */ new { @class = "date-picker" } /* HTML attributes */ ) %> |
注意到这里继承的是ViewTemplateUserControl而不是上面的ViewUserControl。
既然MVC以模板的方式呈现UI,那么是什么影响MVC的选择呢?以下因素按优先级影响到MVC框架对模板的选择:
- 在EditorFor方法中显示指定的模板名称,Html.EditorFor(x => x.SomeProperty , “My Template”)。
- 对应Model的元数据描述,比如在属性上添加特性[UIHint(“My Template”)]
- Model的元数据描述的数据类型,比如[DataType(DataType.EmailAddress)]
- 对应属性的真实.NET 类型
- 对于可以被转化成string的简单类型,使用String模板
- Model的父类属性也会被转化
- 如果属性实现了IEnumable,将选择Collection模板
- 最后使用Object模板
ModelMetadata
TemplateHelpers的确是完成render工作的最主要类,但事实上TemplateHelpers也仅仅负责render,它需要一个叫ModelMetadata的东西来为它提供数据,而ModelMetadata本身就像它的类名,意思是“模型元数据”,相当于一个描述数据的对象,这个对象需要ModelMetadataProvider来提供真正提供数据,下图可以帮助理解:
可以看到MVC内建了DataAnnotationModelMetadataProvider来充当ModelMetadataProvider,它内建支持.NET中的Data Annotation特性,比如DisplayColum、DisplayFormat、Required等,不仅如此DataAnnotationModelMetadataProvider还支持MVC特有的特性描述如:UIHint等。
可以像下面这样指定一个ModelMetadataProvider:ConventionsMetadataProvider。
1 2 3 4 5 6 | protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); ModelMetadataProviders.Current = new ConventionsMetadataProvider(); } |
ModelMetadataProvider本身是个抽象类,可以从下面任意的类中继承,推荐从DataAnnotationModelMetadataProvider继承,这样我们自定义的ModelMetadataProvider就能保留原有的支持了。
再来大概看看ModelMetadata有哪些属性和方法,它们中的部分将被TemplateHelpers考察并影响到Html元素的呈现。其中的FromLambdaExpression()方法用于从Lambda表达式中得到ModelMetadata,这也是内建的TemplateHelpers要调用的方法。
当我们需要用Attribute描述Model类的时候,也许会碰到这样的情况,Model本身是诸如ORM等工具生成的,不能直接修改这样的类。于是MVC提供了[MetadataType]属性来解决这种情形。通常自动生成的Model类是部分类:
1 2 3 4 5 6 7 8 9 | public partial class Person { public int PersonId { get ; set ; } public string FirstName { get ; set ; } public string LastName { get ; set ; } public DateTime BirthDate { get ; set ; } public Address HomeAddress { get ; set ; } public bool IsApproved { get ; set ; } } |
定义另一个对应的类,并用MetadataType特性标识,在其中定义一个内部类,标注上需要的特性描述即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 | [MetadataType( typeof (PersonMetadata))] public partial class Person { // This class is only used as a source of metadata private class PersonMetadata { [HiddenInput(DisplayValue = false )] public int PersonId { get ; set ; } [DisplayName( "First name" )] public string FirstName { get ; set ; } [DisplayName( "Last name" )] public string LastName { get ; set ; } // Also add any other properties for which you want to supply metadata } } |
总结
以上仅仅从框架的角度阐述了关于Template和ModelMatedata的实现。更多细节还需要在实践中多多留意。另外这部分内容还涉及到下一篇要谈到的“模型绑定”,多做些相应的扩展比较有利于理解这部分内容。
劳动果实,转载请注明出处:http://www.cnblogs.com/P_Chou/archive/2011/01/23/details-asp-net-mvc-10.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义