上節:ASP.NET MVC Controller異步機制-MVC原理系列9,講述了ASP.NET MVC Controller 異步機制的相關知識。
本節你講解:ViewTemplate和ModelMetadata這兩方面的內容,讓你更深入的理解ASP.NET MVC的原理,從而更有助於自已實現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中摘錄的:
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數據類型的模板。
<%@ 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將在以後更多的涉及。這裏也可以這樣寫:
<%@ 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。
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); ModelMetadataProviders.Current = new ConventionsMetadataProvider(); }
ModelMetadataProvider本身是個抽象類,可以從下面任意的類中繼承,推薦從DataAnnotationModelMetadataProvider繼承,這樣我們自定義的ModelMetadataProvider就能保留原有的支持了。
再來大概看看MVC中ModelMetadata有哪些屬性和方法,它們中的部分將被TemplateHelpers考察並影響到Html元素的呈現。其中的FromLambdaExpression()方法用於從Lambda表達式中得到ModelMetadata,這也是內建的TemplateHelpers要調用的方法。
當我們需要用Attribute描述Model類的時候,也許會碰到這樣的情況,Model本身是諸如ORM等工具生成的,不能直接修改這樣的類。於是MVC提供了[MetadataType]屬性來解決這種情形。通常自動生成的Model類是部分類:
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特性標識,在其中定義一個內部類,標註上需要的特性描述即可。
[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 } }
http://www.th7.cn/Program/net/201307/143648.shtml