閱讀目錄:程序員
ModelMetadata是ASP.NETMVC中用來表示Model的元數據對象,它包含了一個Model的全部的相關元數據信息,固然這取決Model的使用方向,不一樣的使用方向會有不一樣類型的元數據,咱們這裏的ModelMetadata是針對View顯示相關的元數據;ModelMetadata中絕大部分元數據是用來做爲最終在View生成環節當中須要使用到的,好比:如何肯定一個領域相關的屬性(Address)該如何展示,這裏的Address可能不是一個簡單的String類型表示,而是由一組複雜的類型表示,這樣的狀況下咱們就須要經過自定義元數據來控制最終使用的呈現模板(PartialView);數據庫
在MVC的定義中,Model準確意思是ViewModel(顯示Model,只是用來做爲界面呈現使用的數據實體),它是直接提供給View做爲呈現使用的數據實體,一般狀況下還將做爲DTO類型的數據實體,負責數據的往返傳輸;ASP.NETMVC提供一種自定義Model呈現方式的接口,它容許咱們經過自定義某個ViewModel中的屬性顯示視圖(PartialView部分視圖),從而能夠對ViewModel進行很是細粒度的呈現控制,可是這一擴展機制的背後正是ModelMetadata的功勞;編程
ModelMetadata起到中間橋樑的做用,在橋樑的一端是ViewModel,另外一端是View,然而咱們能夠在ViewModel上經過定義Attribute的方式進行元數據的自定義,能夠經過改變某個ViewModel的ModelMetadata來操縱最終的呈現;數據結構
圖1:Customer ViewModelapp
圖2:Customer ModelMetadata框架
元數據的層次結構與所要表示的ViewModel的結構是一致的,好比上圖中的Customer實體中有一個Shopping屬性,該屬性表示實體中的配送信息,而後Shopping中還包含一個Address屬性表示配送地址,對應的ModelMetadata也是這種包含的層次結構,在每一個ModelMetadata內部都有一個類型爲IEnumerable<ModelMetadata>的Properties屬性來引用它的下級ModelMetadata,這就造成了一個無限嵌套的元數據表示結構,在ModelMetadata經過下面兩行代碼來保存屬性的這種嵌套依賴關係; ide
1 public class ModelMetadata { 2 3 public virtual IEnumerable<ModelMetadata> Properties {} /*類型的子對象元數據*/ 4 5 public string PropertyName {} /*所表示的屬性名稱*/ 6 7 }
當咱們有了一個ViewModel以後就能夠在任何一個View中顯示它,View的呈現是強類型的,也就是說必須具備一個實體的類型做爲數據呈現容器的基礎在View中引入,由於一系列的HtmlHelper擴展方法都是基於這個強類型,咱們經過一個簡單的示例,來大概的瞭解一下ASP.NETMVC使用方式;工具
Customer ViewModel 代碼:開發工具
1 namespace MvcApplication4.Models 2 { 3 public class Customer 4 { 5 public string CustomerId { get; set; } 6 public Shopping Shopping { get; set; } 7 } 8 public class Shopping 9 { 10 public string ShoppingId { get; set; } 11 public Address Address { get; set; } 12 } 13 public class Address 14 { 15 public string AddressId { get; set; } 16 public string CountryCode { get; set; } 17 public string City { get; set; } 18 public string Street { get; set; } 19 } 20 }
這是一個簡單的以Customer爲主的ViewModel,在Customer中定義了一個Shopping類型的屬性,而後在Shopping類型中又定義了一個String類型的Address屬性,這是一個很經常使用的嵌套對象結構;測試
HomePage Controller 代碼:
1 namespace MvcApplication4.Controllers 2 { 3 using Models; 4 5 public class HomePageController : Controller 6 { 7 public ActionResult Index() 8 { 9 Customer customer = new Customer() 10 { 11 CustomerId = "Customer123456", 12 Shopping = new Shopping() 13 { 14 ShoppingId = "Shopping123456", 15 Address = new Address() 16 { 17 AddressId = "Address123456", 18 CountryCode = "CN", 19 City = "Shanghai", 20 Street = "Jiangsu Road" 21 } 22 } 23 }; 24 return View(customer); 25 } 26 27 public ActionResult Edit(Customer customer) 28 { 29 if (customer != null) 30 return new ContentResult() { Content = "Is Ok" }; 31 return new ContentResult() { Content = "Is Error" }; 32 } 33 } 34 }
控制器什麼事情也沒作,直接實例化了一個嵌套層次結構的Customer對象並初始化了一些測試數據,該Action使用ViewResult類型做爲返回結果;
Index View 代碼:
1 @model MvcApplication4.Models.Customer 2 3 <table> 4 <tr> 5 <td> 6 <h2>Model Details Display.</h2> 7 @Html.DisplayForModel() 8 @Html.DisplayFor(model => model.Shopping) 9 @Html.DisplayFor(model => model.Shopping.Address) 10 11 </td> 12 <td></td> 13 <td> 14 <h2>Model details Editor.</h2> 15 @using (Html.BeginForm("Edit", "HomePage", FormMethod.Post)) 16 { 17 @Html.EditorForModel() 18 @Html.EditorFor(model => model.Shopping) 19 @Html.EditorFor(model => model.Shopping.Address) 20 <input type="submit" value="Submit" /> 21 }</td> 22 </tr> 23 </table>
視圖分別對Customer類型的嵌套屬性進行了編輯、顯示定義,這裏須要說明的是EditorForModel()、DisplayForModel()不會作到對嵌套類型的編輯、顯示,由於這不符合平常使用,咱們須要明確的編碼須要編輯、顯示的屬性,經過EditorFor()、DisplayFor()方法進行選擇;
這是一個最基本的MVC使用方式,Customer是須要View進行顯示的ViewModel,在View中經過HtmlHelper擴展方法對Customer實體生成編輯、顯示時的全部HTML,這確實方便了不少,咱們不須要去管到底如何生成這些HTML了;
圖3:
背後爲咱們自動生成了編輯、顯示所須要的HTML;
圖4(如下兩幅):
自動化生成是好事,可是有些時候咱們並不但願它幫咱們生成一些不須要的HTML或者說咱們但願能對生成的過程進行一些控制,好比:這裏的Customer對象,在對象內部的一些屬性(如:CustomerId)咱們根本不但願暴露出來被編輯或被顯示,咱們但願能經過簡單的方式控制這種現實方式;固然MVC爲咱們提供了一整套自動化機制,一樣也爲咱們提供了控制這些自動化機制的接口;
ViewModel在界面上呈現的方式只有兩種,要麼顯示(Display)要麼編輯(Editor),上圖中已經給出MVC默認生成的HTML格式;這是做爲默認的方式輸出,咱們並無參與到輸出過程的任何環節中,要想控制ViewModel的某個屬性的展示方式咱們必須對ModelMetadata進行控制,由於最終生成的這些HTML是根據Model元數據來定的,準確點講HtmlHelper對象和一系列圍繞HtmlHelper的擴展方法都是基於某個ViewModel的ModelMetadata進行最終的生成,全部跟生成相關的選項都是在ModelMetadata中設定的,若是咱們沒有對ViewModel的ModelMetadata進行設置那麼它將有一些默認的數據選項做爲最終生成的基礎;
ASP.NETMVC提供一個叫作 「數據註釋 DataAnnotations」 的方式對某個ViewModel的Model的元數據進行設置,經過在ViewModel中運用一些預約義好的特性來設置本屬性所要展示的方式;好比:上面的Customer實體咱們想控制他的CustomerId只能顯示在界面上,不能對其進行編輯,也就是說咱們只能看不能改;
Customer 代碼:
1 namespace MvcApplication4.Models 2 { 3 public class Customer 4 { 5 [HiddenInput] /*設置CustomerId不出現Input輸入框*/ 6 public string CustomerId { get; set; } 7 public Shopping Shopping { get; set; } 8 } 9 public class Shopping 10 { 11 public string ShoppingId { get; set; } 12 public Address Address { get; set; } 13 } 14 public class Address 15 { 16 public string AddressId { get; set; } 17 public string CountryCode { get; set; } 18 public string City { get; set; } 19 public string Street { get; set; } 20 } 21 }
圖5:
咱們經過使用 HiddenInput特性把CustomerId的輸入框Input隱藏起來了,經過上圖中的CustomerId部分的HTML代碼,咱們能清晰的看見CustomerId的Input的Type被設置成了Hidden,也符合HiddenInput的定義,只將其隱藏起來而不是不輸出HTMLDom;HiddenInput特性中有一個惟一的屬性參數DisplayValue,該屬性參數意思是說隱藏Input元素可是是否要顯示該屬性的值,它是一個Bool類型參數(true:顯示該屬性值,false:不顯示,而且在Display模式下也不顯示);
這裏我就有一個疑問了,在 Display模式下也不顯示,可是通常不少場景下都是須要顯示的,並且這樣的一個特性會致使兩種模式下的顯示衝突;這裏的CustomerId假設我須要在Display下顯示出來,可是在編輯模式下我就是要不顯示出CustomerId屬性值;其實這個時候就須要咱們本身擴展這些設置顯示方式的特性了,前提是咱們得很清楚它是如何控制HTMLDOM輸出的,究竟是如何與HtmlHelper對象協調的,又如何參與到元數據設置當中的;
在ASP.NETMVC中有一組預先定義好的Attribute,這些Attribute是專門用來控制某個ViewModel中的屬性元數據選項;在大多數狀況下,咱們可使用這些預先定義好的Attribute來解決通常的業務場景,可是實踐經驗告訴咱們通常的業務場景很少見,一般都是須要咱們對元數據進行自定義控制,這樣咱們才能作到對當前業務邏輯最大粒度的抽象,從而達到在某個層面上能作到面向特定領域的範圍;
Customer 代碼:
1 namespace MvcApplication4.Models 2 { 3 public class Customer 4 { 5 [Display(Name = "客戶ID")] 6 public string CustomerId { get; set; } 7 public Shopping Shopping { get; set; } 8 } 9 public class Shopping 10 { 11 [Display(Name = "配送ID")] 12 public string ShoppingId { get; set; } 13 public Address Address { get; set; } 14 } 15 public class Address 16 { 17 [Display(Name = "地址")] 18 public string AddressId { get; set; } 19 [Display(Name = "國家編碼")] 20 public string CountryCode { get; set; } 21 [Display(Name = "城市編碼")] 22 public string City { get; set; } 23 [Display(Name = "街道")] 24 public string Street { get; set; } 25 } 26 }
這裏經過Diaplay預約義特性來控制元數據顯示選項,在Display特性中有不少可選屬性用來進一步設置顯示選項,這裏咱們只使用了Name屬性來設置該屬性在界面上顯示的文本信息,用來替換本來顯示代碼屬性名稱的默認選項;
圖6:
能夠作到將界面上本來顯示字段名稱的地方換成使用領域語言顯示,也就是咱們經過Diaplay特性設置的顯示文本;
ViewModel中的屬性有兩種類型的含義,好比:在Address數據實體中CountryCode默認是字符串類型,可是它的領域類型是一個表示國家代碼的編號;雖然不少時候咱們可使用字符串、數字等這些CLR類型來表達任何一種領域概念,這僅僅是代碼層面的表示而已,而一旦咱們將該實體做爲領域對象在界面呈現時就須要還原出領域相關的特性;很常見的狀況就是咱們常常將字符串類型的Email用特定的格式在界面上表示,這就是說明該字段是一個領域相關的特性;代碼是給咱們程序員看的,而領域語言是給相關的領域參與者看的,因此在ViewModel中設置的這些預約義元數據控制特性大致能夠歸來爲這兩類;
在ASP.NETMVC中大部分使用的預約義特性都是位於System.ComponentModel.DataAnnotations命名空間中,惟獨HiddenInput特性是孤身一人在System.Web.Mvc命名空間中,這可能對你形成了一些理解上的困擾;明明是ASP.NETMVC框架使用的對象爲何會跑到System.ComponentModel.DataAnnotations命名空間中去,又爲何恰恰HiddenInput就在System.Web.Mvc命名空間中,按道理說也應該是在System.Web.Mvc開頭的命名空間中才對;其實這要想說清楚就牽扯到一些.NET組件程序設計相關的理論知識,因此會在下一個章節詳細的分析它爲何會在System.ComponentModel.DataAnnotations命名空間中,這些設計究竟是爲了什麼;
在ASP.NETMVC中大部分預先定義好的元數據控制特性都是密封類型的,只有不多一部分是公開類型的,因此若是咱們須要擴展的對象能從這部分對象上繼承那將會很方便,能夠省掉不少工做;有些特性不是一個簡單的數據聲明標識,其中會有一些預約義行爲會被走到,因此若是咱們重寫這部分的行爲就能夠作到簡單的擴展這部分對象來輕鬆的達到擴展目的;
可是很大程度上咱們須要本身能從根本上定製一個元數據控制特性對象,咱們不但願經過繼承原有的預約義的元數據控制特性對象來進行簡單的擴展,咱們須要最大粒度的設計,我想這個要求一點都不過度,誰願意在礙手礙腳的地方Happy呢;
ASP.NETMVC提供IMetadataAware接口讓咱們能夠隨心所欲的控制元數據,控制元數據就能夠控制最終根據元數據生成的邏輯;
CustomDisplayName 代碼:
1 [AttributeUsage(AttributeTargets.Property)] 2 public class CustomDisplayName : Attribute, IMetadataAware 3 { 4 public string Name { get; set; } //默認顯示名稱 5 public void OnMetadataCreated(ModelMetadata metadata) 6 { 7 metadata.DisplayName = string.Format("{0}/{1}", 8 string.IsNullOrEmpty(this.Name) ? metadata.DisplayName : this.Name, metadata.PropertyName); 9 } 10 }
這是一個很簡單的自定義元數據對象,當咱們將CustomDisplayName 特性對象設置在指定的ViewModel中的任何一個屬性上時,將能夠在運行時獲取到系統自動生成的元數據對象模型ModelMetadata,這個時候咱們就能夠對當前的元數據進行隨意的控制,甚至能夠一直追述元數據的全部關聯元數據;
上面的示例代碼將複寫經過預約義特性Display特性設置的元數據信息DisplayName:
1 public class Customer 2 { 3 [CustomDisplayName(Name = "自定義")] 4 [Display(Name = "客戶ID")] 5 public string CustomerId { get; set; } 6 public Shopping Shopping { get; set; } 7 }
在CustomerId屬性上咱們設置了兩個特性,一個是系統預約義的Display特性,該特性將會對元數據對象ModelMetadata的DisplayName屬性進行設置,還有一個正是咱們自定義的CustomDisplayName特性,在咱們自定義特性的內部邏輯中,若是咱們設置了CustomDisplayName對象的Name屬性,那麼咱們將使用該值複寫經過預約義特性Display特性所設置的默認元數據信息,從而達到控制最終元數據的目的;
圖7:
當前這個值是咱們經過Display預約義特性設置的;
圖8:
在CustomDisplayName中的Name屬性是咱們設置的默認要顯示的文本,若是咱們設置了默認值將使用該值複寫預約義特性Display設置的值;
圖9:
使用IMetadataAware接口咱們能夠設計自定義的元數據設置對象,這也是ASP.NETMVC目前公開的惟一一個元數據定義接口;固然若是碰見很是複雜的業務場景時就須要咱們對元數據提供程序進行控制,能夠將元數據的定義方式從聲明式遷移到配置文件中,固然這須要有業務須要才行,純粹的技術實現沒有太多的意義;
在ASP.NETMVC中,大部分的元數據控制特性都是定義在System.ComponentModel.DataAnnotations命名空間中,固然也有一小部分是ASP.NETMVC直接固定的,這些都是跟ASP.NETMVCWEB編程直接相關的(如:HiddenInput元數據庫控制特性,用來隱藏HTML中的Input Dom元素),可是大部分都是位於組件對象模型命名空間中;這就會給咱們帶來一些疑問,爲何跟ASP.NETMVC框架相關的對象模型會被定義在System.ComponentModel.DataAnnotations命名空間中,而該命名空間中的對象模型倒是跟系統組件設計相關的領域,若是你沒有系統組件開發經驗或者沒有Winform程序開發經驗的對你來講可能真的很困惑,由於System.ComponentModel.DataAnnotations命名空間基本上是用來支撐全部.NET平臺上的基礎框架,若是你想擴展VS插件、編寫設計時組件,這些跟.NET平臺相關的領域都會須要該命名空間的支持;
能夠簡單定義System.ComponentModel.DataAnnotations命名空間的做用,該命名空間主要是用來支撐跟.NET平臺組件開發相關的領域,在該命名空間中的對象模型都是用來支持VisualStudio設計時及基礎框架的通用組成部分;
組件模型一般具備三個基本的生命週期,設計時、編譯時、運行時,這裏的組件與咱們一般理解的運行時組件不是一個概念,這裏的組件的參照物是.NET基礎框架,做爲以VS爲開發工具的.NET程序,在設計時咱們都須要可視化編程,將一個簡單的對象以圖形界面的方式呈現出來而且提供設計時支持,這些才這是咱們這裏所說的組件,若是你的組件並無提供設計時、編譯時、運行時這三個基本的生命週期事件,那麼只能說你的組件是不完整的;
設計時:當咱們在使用傳統ASP.NET開發程序的時候最經常使用的就是拖拽一個控件放入界面上,此時會出現一個GUI的設計界面,讓咱們點擊相應的位置設置一些選項,這就是設計時支持,被拖拽的能夠視爲一個能夠重用的組件,這是它在設計時的一個生命週期;
編譯時:當咱們啓動VS進行編譯時,組件有一個自我屬性檢查的過程,一般是用來檢查咱們的預設置項是否正確,好比一些WindowsService,是否填寫了正確的啓動項屬性,這就是組件的編譯時支持;
運行時:這個比較好理解,運行時就是在程序運行過程當中提供的功能,固然你的組件能夠不提供運行時支持,而僅僅提供設計時、編譯時的支持;
組件設計時元數據和ASP.NETMVC Model元數據很類似,爲何說類似,是由於都須要通過一個對元數據獲取的過程;在ASP.NETMVC中Model元數據的設置過程須要經過提取做用於Model上的元數據控制特性而且逐一順序執行後才能完成,而這裏的組件設計時元數據提取過程能夠當作是和ASP.NETMVC Model元數據設置過程當中的提取元數據控制特性過程徹底一致的複用功能;
圖10:
上圖中被圈出的部分是對設計時元數據的控制特性,經過對須要綁定到VS屬性窗口中的模型運用相似ASP.NETMVC中定義Model控制元數據特性的同樣的方式來達到控制被使用的模型,惟一不一樣的是背後的元數據處理程序不一樣而已,可是能夠進行相似的理解;
通過上面兩個小結的講解,咱們知道什麼是系統組件及組件的一個基本的特徵,如:生命週期,更爲重要的是咱們知道了一些跟ASP.NETMVC元數據類似的功能出如今系統組件開發的功能集中,這爲咱們理解爲何ASP.NETMVC元數據註解特性對象會定義在系統組件命名空間中作了不少充足的準備;
System.ComponentModel.DataAnnotationns命名空間是位於System.ComponentModel命名空間下,表示它是一個系統組件開發相關的數據註解組件;幫助咱們在開發系統組件時進行很好的數據註解聲明,最有意義的是能夠很輕鬆的實現元數據驅動設計、契約式設計等相似須要藉助數據註解功能的設計方法;
既然定義在System.ComponentModel下也就意味着能夠供.NET平臺上的全部跟組件設計相關的框架使用,在.NET平臺中有不少須要藉助數據註解特性功能的場景(好比:在WPF中須要藉助數據註解功能來達到MVVM模式的使用);
圖11:
System.ComponentModel.DataAnnotations中的數據註解特性是提供給全部.NET平臺上應用框架使用的,這些框架都或多或少在一些設計上須要數據註解功能,這樣就不須要重複定義這些相似功能了;在ASP.NETMVC中,咱們使用這些數據註解特性來聲明元數據控制選項,在其餘的應用框架中如:WPF中,可能須要用來指定UI上的雙向綁定事件,這些都是須要創建在這些數據註解特性上的;
在System.ComponentModel.DataAnnotations中有一個擴展自System.ComponentModel.TypeDescriptionProvider的類型:
// 摘要: // 經過添加在關聯類中定義的特性和屬性信息,從而擴展某個類的元數據信息。 public class AssociatedMetadataTypeTypeDescriptionProvider : TypeDescriptionProvider { }
該類型擴展了本來很單純的組件類型描述提供程序,添加了關聯類的數據描述獲取功能;意思是說咱們可使用該類來獲取全部預約義的關聯元數據控制特性;
1 [AttributeUsage(AttributeTargets.Property)] 2 public class ValidatorAttribute : Attribute /*自定義的關聯類特性*/ 3 { 4 public string ValidatorFormatString { get; set; } 5 } 6 public class Customer 7 { 8 9 [Validator(ValidatorFormatString = "XXX")]/*設置關聯特性*/ 10 [CustomDisplayName(Name = "自定義")] 11 [Display(Name = "客戶ID")] 12 public string CustomerId { get; set; } 13 public Shopping Shopping { get; set; } 14 }
AssociatedMetadataTypeTypeDescriptionProvider provider = new AssociatedMetadataTypeTypeDescriptionProvider(typeof(ValidatorAttribute)); var result = provider.GetTypeDescriptor(customer).GetProperties()[0].Attributes;
經過使用AssociatedMetadataTypeTypeDescriptionProvider 公共關聯類類型描述提供程序獲取全部關聯類的元數據控制聲明;
圖12:
咱們可使用System.ComponentModel.DataAnnotations命名空間提供的公共組件設計框架中提供的關於數據註解方面的功能來方便的開發有關元數據註解方面的程序特性;