上一篇有介紹ModelMetadata
和參數Model
之間的關係.
MVC提供我們一個IMetadataAware
介面,讓我們可以對最終生成ModelMetadata
進行自由設定.
我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
在IMetadataAware
介面有一個OnMetadataCreated
方法
public interface IMetadataAware
{
void OnMetadataCreated(ModelMetadata metadata);
}
在MVC有預設兩個實現IMetadataAware
介面的標籤.
AllowHtmlAttribute
:標上的屬性可以攜帶Html
資料.AdditionalMetadataAttribute
:對於當前屬性的modelmetadata
資訊的AdditionalValues
添加資料(添加資料可透過ViewData.ModelMetadata.AdditionalValues
取得資料)如果你想要對於modelmetadata
資訊做修改或新增資料可以製作自己IMetadataAware
介面標籤.
為了防止(Cross-site scripting)XSS攻擊通過在針對某些輸入框中寫入或注入HTML
來攻擊我們Web
應用
針對HTML
標記驗證通過ModelMetadata
的RequestValidationEnabled
來控制,如下面程式碼顯示
這是一個布爾類型的可讀寫屬性。
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
資料.
[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
方法.
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
傳入參數上可以標示許多標籤例如
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
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
物件,這個物件存放參數上屬性欄位使用標籤資訊.
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使用標籤
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
才會被反射取得.
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
的取得和呼叫過程.