🚫 Ad Blocker Detected

Please disable your AD blocker to continue using this site. Ads help us keep the content free! please press keyboard F5 to refresh page after disabled AD blocker

請關閉廣告攔截器以繼續使用本網站。廣告有助於我們保證內容免費。謝謝! 關閉後請按 F5 刷新頁面

0%

探討Model上客製化標籤如何被解析使用 (第20天)

Agenda

前言

上一篇有介紹ModelMetadata和參數Model之間的關係.

UML_Model

MVC提供我們一個IMetadataAware介面,讓我們可以對最終生成ModelMetadata進行自由設定.

IMetadataAware介面

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標記驗證通過ModelMetadataRequestValidationEnabled來控制,如下面程式碼顯示

這是一個布爾類型的可讀寫屬性。

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) 偵測到。

UML_Model

如果查看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端了

只是這個標籤請斟酌使用打開有一定風險.

為何可以透過實現IMetadataAware介面來擴充對於metadata操作

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);
}
}
}

CachedDataAnnotationsModelMetadataProvider

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

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

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 許可協議。轉載請註明出處!

如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^