ASP.NET MVC Model元數據及其定制:一個重要的接口IMetadataAware


在介紹用於自定義Model元數據屬性的AdditionalMetadataAttribute特性時我們提到了它實現的接口IMedataAware,我們說這是一個非常重要並且有用的接口,通過自定義實現該接口的特性我們可以對最終生成的Model元數據進行自由地定制。如下面的代碼片斷所示,IMedataAware接口具有唯一的方法成員OnMetadataCreated。當Model元數據被創建出來后,會先獲取上述的這一系列標注特性對其進行初始化,然后獲取應用在目標元素上所有實現了IMedataAware接口的特性,並將初始化的ModelMetadata對象作為參數調用OnMetadataCreated方法。所以我們通過創建實現該接口的特性不僅僅可以添加一些額外的元數據屬性,也可以修改已經通過相應的標注特性初始化的相關屬性。[本文已經同步到《How ASP.NET MVC Works?》中]

   1: public interface IMetadataAware
   2: {    
   3:     void OnMetadataCreated(ModelMetadata metadata);
   4: }

ASP.NET MVC定義了兩個實現了IMedataAware接口的特性,一個就是我們已經介紹過的AdditionalMetadataAttribute,另一個則是AllowHtmlAttribute

一、AllowHtmlAttribute

為了防止最終用於通過在針對某個數據的輸入中注入一些HTML來攻擊我們的Web應用,ASP.NET MVC在進行Model綁定之前會對對應的請求數據進行驗證,確保沒有任何HTML標記包含其。這個針對HTML標記的驗證通過ModelMetadata的RequestValidationEnabled來控制,如下面的代碼片斷所示,這是一個布爾類型的可讀寫屬性。該屬性在默認情況下為True,意味着默認開啟針對HTML標記的請求驗證。

   1: public class ModelMetadata
   2: {
   3:     //其他成員
   4:     public virtual bool RequestValidationEnabled { get; set; } 
   5: }

AllowHtmlAttribute特性,顧名思義,就是運行作為目標元素的內容包含HTML標記。如下面的代碼片斷所示,AllowHtmlAttribute是實現了IMetadataAware 接口,在OnMetadataCreated方法中它直接將作為參數的ModelMetadata對象的RequestValidationEnabled屬性設置為False,從而使針對目標對象的請求驗證被忽略掉。

   1: [AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
   2: public sealed class AllowHtmlAttribute : Attribute, IMetadataAware
   3: {
   4:     public void OnMetadataCreated(ModelMetadata metadata)
   5:     {
   6:         //其他操作
   7:         metadata.RequestValidationEnabled = false;
   8:     }
   9: }

為了驗證ASP.NET MVC針對HTML標記的請求驗證和AllowHtmlAttribute的作用,我們來做一個簡單的實例演示。在通過Visual Studio提供的ASP.NET MVC項目模板創建的空Web應用中,我們定義了如下一個數據類型Foo,其中屬性Baz上應用了AllowHtmlAttribute特性。

   1: public class Foo
   2: {
   3:     public string Bar { get; set; }
   4:  
   5:     [AllowHtml]
   6:     public string Baz { get; set; }
   7: }

然后我們創建如下一個默認的HomeController,默認的Index操作方法中具有一個類型為Foo的參數,該參數直接作為Model呈現在默認的View中。

   1: public class HomeController : Controller
   2: {
   3:     public ActionResult Index(Foo foo)
   4:     {
   5:         return View(foo);
   6:     }
   7: }

如下所示的Index操作對應的View定義,這是一個以Foo為Model的強類型View。在該View中,我們直接調用HtmlHelper<Model>的EditorForModel方法將Foo對象以編輯模式呈現出來。

   1: @model Foo
   2: @{
   3:     ViewBag.Title = "Index";
   4: }
   5: @Html.EditorForModel()

現在我們直接運行該Web應用。根據Model綁定的規則我們知道,如果我們通過瀏覽器訪問HomeController的Index操作,可以通過查詢字符串的方式對該操作方法的參數進行初始化。具體來說,我們可以分別指定名稱為Bar和Baz的查詢字符串對作為參數的Foo對象的兩個屬性進行初始化。為了驗證對包含HTML標記的輸入的驗證,我們將最終綁定到Model上的查詢字符串設置為<script/>。

如下圖所示,由於Foo的屬性Baz上應用了AllowHtmlAttribute特性是之支持包含HTML標記的數據,所以我們以查詢字符串方式指定的包含HTML標記的內容(<script/>)直接顯示在相應的文本框中。但是Bar屬性在默認情況下是不運行綁定的數據具有任何HTML標記的,所以會將輸入的數據視為惡意注入的HTML,直接拋出異常。

image

二、實例演示:創建實現IMetadataAware接口的特性定制Model元數據

通過上面對Model元數據定義的介紹我們知道顯示名稱可以通過在數據類型或者屬性成員上應用DisplayAttribute特性來定義。在使用該特性的時候,我們需要顯式制定表示顯示名稱的Name屬性,如果需要進行本地化處理,需要將顯示內容定義在某個資源文件中,並通過ResourceType屬性指定該資源文件生成的類型。[源代碼從這里下載]

為了簡化,我們通過實現IMetadataAware接口的方式定義了如下一個DisplayTextAttribute特性。該特性的屬性DisplayName/ResourceType與DisplayAttribute的Name/ResourceType具有相同的作用,唯一不同的是DisplayTextAttribute的這兩個屬性均是可以缺省的。如果DisplayName沒有顯式指定,則默認使用屬性名稱或者類型名稱;如果ResourceType沒有顯式指定,則采用通過靜態字段staticResourceType表示的默認資源類型,該類型通過靜態方法SetResourceType進行注冊。

   1: [AttributeUsage(AttributeTargets.Class| AttributeTargets.Property)]
   2: public class DisplayTextAttribute: Attribute, IMetadataAware
   3: {
   4:     private static Type staticResourceType;
   5:     public string DisplayName { get; set; }
   6:     public Type ResourceType { get; set; }
   7:  
   8:     public DisplayTextAttribute()
   9:     {
  10:         this.ResourceType = staticResourceType;
  11:     }
  12:  
  13:     public void OnMetadataCreated(ModelMetadata metadata)
  14:     {
  15:         this.DisplayName = this.DisplayName ?? (metadata.PropertyName ?? metadata.ModelType.Name);
  16:         if (null == this.ResourceType)
  17:         {
  18:             metadata.DisplayName = this.DisplayName;
  19:             return;
  20:         }
  21:         PropertyInfo property = this.ResourceType.GetProperty(this.DisplayName, BindingFlags.NonPublic|BindingFlags.Public| BindingFlags.Static);
  22:         metadata.DisplayName = property.GetValue(null, null).ToString();
  23:     }
  24:  
  25:     public static void SetResourceType(Type resourceType)
  26:     {
  27:         staticResourceType = resourceType;
  28:     }
  29: }

DisplayTextAttribute對Model元數據的定制實現在OnMetadataCreated方法中。具體來說,我們根據設置的DisplayName和ResourceType屬性解析出最終作為目標元素顯示名稱的文本作為ModelMetadata的DisplayName屬性值。

接下來我們來演示如何使用這個DisplayTextAttribute特性來替換DisplayAttribute特性進行顯示名稱的設置,為此我們在通過Visual Studio的ASP.NET MVC 項目模板創建的空Web應用中創建如下一個表示員工的Employee類型。Employee所有的屬性上均應用了DisplayTextAttribute特性,而DisplayName和ReourceType屬性沒有顯式指定。

   1: public class Employee
   2: {
   3:     [DisplayText]
   4:     public string Name { get; set; }
   5:  
   6:     [DisplayText]
   7:     public string Gender { get; set; }
   8:  
   9:     [DisplayText]
  10:     [DataType(DataType.Date)]
  11:     public DateTime BirthDate { get; set; }
  12:  
  13:     [DisplayText]
  14:     public string Department { get; set; }
  15: }

接下來我們打開項目的屬性對話框並選擇“資源(Rources)”Tab頁,按照如下圖所示為Employee中的四個屬性定義相應的資源字符串作為顯示的名稱,資源字符串條目的名稱為屬性名。

image

該資源文件會自動生成一個類型為Resources的內部類型。由於應用在Employee屬性上的DisplayTextAttribute特性並沒有顯式指定資源類型,所以我們需要在Global.asax文件中通過如下的方式將Resources類型注冊為默認的資源類型。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {    
   3:     //其他成員
   4:     protected void Application_Start()
   5:     {
   6:         //其他操作
   7:         DisplayTextAttribute.SetResourceType(typeof(Resources));
   8:     }
   9: }

現在我們通過調用HtmlHelper<TModel>的EditorForModel方法將一個具體的Employee對象以編輯模式顯示在某個Model類型為Employee的強類型View上,會呈現出如下圖所示的效果,我們可以看到作為標簽顯示的文字正式我們定義在資源文件中的內容。

image

ASP.NET MVC Model元數據及其定制: 初識Model元數據
ASP.NET MVC Model元數據及其定制: Model元數據的定制
ASP.NET MVC Model元數據及其定制:一個重要的接口IMetadataAware


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM