Agenda 
前言 上一篇有介紹ModelMetadata和參數Model之間的關係.
MVC 提供我們一個IMetadataAware介面,讓我們可以對最終生成ModelMetadata進行自由設定.
在IMetadataAware介面有一個OnMetadataCreated方法
1 2 3 4 public  interface  IMetadataAware {     void  OnMetadataCreated (ModelMetadata metadata ) ; } 
 
在MVC 有預設兩個實現IMetadataAware介面的標籤.
AllowHtmlAttribute:標上的屬性可以攜帶Html資料. 
AdditionalMetadataAttribute:對於當前屬性的modelmetadata資訊的AdditionalValues添加資料(添加資料可透過ViewData.ModelMetadata.AdditionalValues取得資料) 
 
如果你想要對於modelmetadata資訊做修改或新增資料可以製作自己IMetadataAware介面標籤.
我有做一個可以針對於Asp.net MVC Debugger 的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
 
AllowHtmlAttribute標籤 為了防止(Cross-site scripting)XSS攻擊 通過在針對某些輸入框中寫入或注入HTML來攻擊我們Web應用
針對HTML標記驗證通過ModelMetadata的RequestValidationEnabled來控制,如下面程式碼顯示
這是一個布爾類型的可讀寫屬性。
1 2 3 4 public  class  ModelMetadata { 	public  virtual  bool  RequestValidationEnabled { get ; set ; }  } 
 
此屬性在默認情況下為True進行驗證防護
 
ASP.NET MVC 有一個預設標籤AllowHtmlAttribute在進行Model綁定之前會對對應請求資料進行驗證,確保沒有任何HTML標記包含其中。
如果在Input tag輸入有關Html資料就會出現下面錯誤.(這是MVC 貼心幫我們開啟防護XSS攻擊的機制)
具有潛在危險Request.Form 的值已從用戶端 (xxxxxx) 偵測到。
 
如果查看AllowHtmlAttribute原始碼就很簡單只是把metadata.RequestValidationEnabled設成false允許使用者上傳Html資料.
1 2 3 4 5 6 7 8 9 10 11 12 13 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true) ] public  sealed  class  AllowHtmlAttribute  : Attribute , IMetadataAware {     public  void  OnMetadataCreated (ModelMetadata metadata )     {         if  (metadata == null )         {             throw  new  ArgumentNullException("metadata" );         }         metadata.RequestValidationEnabled = false ;     } } 
 
我們就可以把Html資料傳送到AP 端了
只是這個標籤請斟酌使用打開有一定風險.
 
在AssociatedMetadataProvider抽象類別中有個ApplyMetadataAwareAttributes方法.
參數物件上屬性進行反射取得使用到IMetadataAware的標籤,並呼叫他的OnMetadataCreated方法.
1 2 3 4 5 6 7 8 9 10 public  abstract  class  AssociatedMetadataProvider  : ModelMetadataProvider {     private  static  void  ApplyMetadataAwareAttributes (IEnumerable<Attribute> attributes, ModelMetadata result )     {         foreach  (IMetadataAware awareAttribute in  attributes.OfType<IMetadataAware>())         {             awareAttribute.OnMetadataCreated(result);         }     } } 
 
在MVC  Action傳入參數上可以標示許多標籤例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  class  VerifyCodeViewModel { 	[Required ] 	public  string  Provider { get ; set ; } 	[Required ] 	[Display(Name = "代碼" ) ] 	public  string  Code { get ; set ; } 	public  string  ReturnUrl { get ; set ; } 	[Display(Name = "記住此瀏覽器?" ) ] 	public  bool  RememberBrowser { get ; set ; } 	public  bool  RememberMe { get ; set ; } } public  class  ForgotViewModel { 	[Required ] 	[Display(Name = "電子郵件" ) ] 	public  string  Email { get ; set ; } } 
 
RequiredAttribute 
DisplayAttribute 
 
還有其他一大堆,下面會跟大家介紹MVC 是怎麼取得並使用這些標籤,ModelMetadataProviders這個類別會提供使用哪個ModelMetadataProvider
在原始碼建構子預設使用CachedDataAnnotationsModelMetadataProvider
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  class  ModelMetadataProviders {     private  static  ModelMetadataProviders _instance = new  ModelMetadataProviders();     private  ModelMetadataProvider _currentProvider;     private  IResolver<ModelMetadataProvider> _resolver;     internal  ModelMetadataProviders (IResolver<ModelMetadataProvider> resolver = null  )     {         _resolver = resolver ?? new  SingleServiceResolver<ModelMetadataProvider>(                                     () => _currentProvider,                                     new  CachedDataAnnotationsModelMetadataProvider(),                                     "ModelMetadataProviders.Current" );     }      } 
 
在CachedDataAnnotationsModelMetadataProvider類別有一個CreateMetadataPrototype方法返回一個CachedDataAnnotationsModelMetadata物件,這個物件存放參數上屬性欄位使用標籤資訊.
1 2 3 4 5 6 7 8 9 10 11 12 public  class  CachedDataAnnotationsModelMetadataProvider  : CachedAssociatedMetadataProvider <CachedDataAnnotationsModelMetadata >{ 	protected  override  CachedDataAnnotationsModelMetadata CreateMetadataPrototype (IEnumerable<Attribute> attributes, Type containerType, Type modelType, string  propertyName ) 	{ 		return  new  CachedDataAnnotationsModelMetadata(this , containerType, modelType, propertyName, attributes); 	} 	protected  override  CachedDataAnnotationsModelMetadata CreateMetadataFromPrototype (CachedDataAnnotationsModelMetadata prototype, Func<object > modelAccessor ) 	{ 		return  new  CachedDataAnnotationsModelMetadata(prototype, modelAccessor); 	} } 
 
CachedDataAnnotationsModelMetadata類別上有許多屬性,主要是方便日後來判斷使用MVC 使用標籤
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 27 28 29 30 31 32 33 34 35 36 37 public  class  CachedDataAnnotationsModelMetadata  : CachedModelMetadata <CachedDataAnnotationsMetadataAttributes >{     private  bool  _isEditFormatStringFromCache;     public  CachedDataAnnotationsModelMetadata (CachedDataAnnotationsModelMetadata prototype, Func<object > modelAccessor )         : base (prototype, modelAccessor )     {     }     public  CachedDataAnnotationsModelMetadata (CachedDataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string  propertyName, IEnumerable<Attribute> attributes )         : base (provider, containerType, modelType, propertyName, new  CachedDataAnnotationsMetadataAttributes(attributes.ToArray( )))     {     }     protected  override  bool  ComputeConvertEmptyStringToNull ()     {         return  PrototypeCache.DisplayFormat != null                      ? PrototypeCache.DisplayFormat.ConvertEmptyStringToNull                     : base .ComputeConvertEmptyStringToNull();     }     protected  override  string  ComputeDataTypeName ()     {         if  (PrototypeCache.DataType != null )         {             return  PrototypeCache.DataType.ToDataTypeName();         }         if  (PrototypeCache.DisplayFormat != null  && !PrototypeCache.DisplayFormat.HtmlEncode)         {             return  DataTypeUtil.HtmlTypeName;         }         return  base .ComputeDataTypeName();     }      } 
 
有一個蠻特別事情是CachedDataAnnotationsModelMetadata : CachedModelMetadata<CachedDataAnnotationsMetadataAttributes>他繼承一個泛型類別CachedDataAnnotationsMetadataAttributes存放取得物件標籤的資訊.
CachedDataAnnotationsMetadataAttributes類別主要把屬性上的某些標籤給值到類別的屬性上,方便CachedDataAnnotationsModelMetadata來操作使用.
這也是為什麼只有某些標籤掛在屬性上可以被使用.預設只有CachedDataAnnotationsMetadataAttributes才會被反射取得.
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 27 28 29 30 31 32 public  class  CachedDataAnnotationsMetadataAttributes { 	public  CachedDataAnnotationsMetadataAttributes (Attribute[] attributes ) 	{ 		DataType = attributes.OfType<DataTypeAttribute>().FirstOrDefault(); 		Display = attributes.OfType<DisplayAttribute>().FirstOrDefault(); 		DisplayColumn = attributes.OfType<DisplayColumnAttribute>().FirstOrDefault(); 		DisplayFormat = attributes.OfType<DisplayFormatAttribute>().FirstOrDefault(); 		DisplayName = attributes.OfType<DisplayNameAttribute>().FirstOrDefault(); 		Editable = attributes.OfType<EditableAttribute>().FirstOrDefault(); 		HiddenInput = attributes.OfType<HiddenInputAttribute>().FirstOrDefault(); 		ReadOnly = attributes.OfType<ReadOnlyAttribute>().FirstOrDefault(); 		Required = attributes.OfType<RequiredAttribute>().FirstOrDefault(); 		ScaffoldColumn = attributes.OfType<ScaffoldColumnAttribute>().FirstOrDefault();          	} 	public  DataTypeAttribute DataType { get ; protected  set ; } 	public  DisplayAttribute Display { get ; protected  set ; } 	public  DisplayColumnAttribute DisplayColumn { get ; protected  set ; } 	public  DisplayFormatAttribute DisplayFormat { get ; protected  set ; } 	public  DisplayNameAttribute DisplayName { get ; protected  set ; } 	public  EditableAttribute Editable { get ; protected  set ; } 	public  HiddenInputAttribute HiddenInput { get ; protected  set ; }      } 
 
小結: ModelMetaData是一個Model Binding 很重要物件,裡面存放許多調用參數的資訊.
MVC 提供一個IMetadataAware介面可以改變ModelMetaData中資訊,提高更高的彈性.
這篇也介紹了IMetadataAware介面是在哪邊做攔截.
另外也分享常掛在屬性上標籤取得的類別跟機制
DisplayNameAttribute 
RequiredAttribute 
DisplayAttribute 
 
透過CachedDataAnnotationsModelMetadataProvider這個類別來取得以上標籤,並在日後做判斷.
下篇會和大家分享另一種屬性標籤ValidationAttribute的取得和呼叫過程.
__此文作者__:Daniel Shih(石頭) __此文地址__: https://isdaniel.github.io/Ithelp-day20/   __版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW  許可協議。轉載請註明出處!