系列目錄
前言
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中摘錄的:
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就能保留原有的支援了。
再來大概看看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 } }
總結
以上僅僅從架構的角度闡述了關於Template和ModelMatedata的實現。更多細節還需要在實踐中多多留意。另外這部分內容還涉及到下一篇要談到的“模型繫結”,多做些相應的擴充比較有利於理解這部分內容。
勞動果實,轉載請註明出處:http://www.cnblogs.com/P_Chou/archive/2011/01/23/details-asp-net-mvc-10.html