咱們來看一個小例子,在一個ASP.NET MVC項目中建立一個控制器Home,只有一個Index:html
public class HomeController : Controller { public ActionResult Index() { var model = new DemoModel {Email = "test@test.com"}; return View(model); } } public class DemoModel { [DataType(DataType.EmailAddress)] public string Email { get; set; } }
建立對應的強類型視圖json
@model TestMvc.Controllers.DemoModel <fieldset> <legend>DemoModel</legend> <div > @Html.DisplayFor(model => model.Email) </div> </fieldset>
運行一下,若是你的RP不是很是很差的狀況下,會出現下面的結果:ide
生成的是一個Email的連接。看一下email部分對應的html源文件:函數
<div > <a href="mailto:test@test.com">test@test.com</a> </div>
對於不求甚解的人來講,這很正常啊,正常的就像1+1=2同樣。但對於勤學好問的同窗來講,問題來了。post
爲何生成的是一個email連接,不是一個純文本?ui
由於我用DataType指明瞭它是email!this
那DataType是如何指導Model生成html的?url
若是你對這些問題感興趣,請你看下去。spa
在介紹Model如何能在View中正常顯示以前,不得不提一下他的兄弟ModelMetadata這個類。ModelMetadata實現對Model的一些特徵進行描述(如Model的類型、Model的值,Model要顯示的模板 ),而且實現了對Model的全部屬性值進行描述(遞歸結構)。有了這個兄弟,Model才能知道如何在View中正常顯示【注意:這裏特指的是用HtmlHelper的系列擴展方法(如Display/DiaplayFor/EditorFor等)方法在View中顯示。若是你是在View中直接取Model的值用html顯示,你能夠暫不用關心ModelMetadata。但!!不要以爲這樣你就不用瞭解ModelMetadata了,由於MVC中還有一個核心與它息息相關---Model綁定與驗證,因此我建議你仍是先認識一下這位好兄弟】,讓咱們來看看這個兄弟長什麼樣:prototype
public class ModelMetadata { private readonly Type _modelType; //Model的類型 private readonly string _propertyName; //屬性名稱 private object _model; //Model的值 private Func<object> _modelAccessor; //爲了用Lambd給Model傳值(這是潮流) private IEnumerable<ModelMetadata> _properties; //Model屬性值的ModelMetadata集合 //經過子類的ComputeXX系列函數將Model的註解特性賦值到下面幾個屬性 public virtual string DataTypeName { get; set; } public virtual string Description { get; set; } public virtual string DisplayFormatString { get; set; } public virtual string DisplayName { get; set; } (其它略,屬性太多了,影響閱讀...)
public object Model { get { if (_modelAccessor != null) { _model = _modelAccessor(); _modelAccessor = null; } return _model; } } public virtual IEnumerable<ModelMetadata> Properties { get { if (_properties == null) { IEnumerable<ModelMetadata> originalProperties = Provider.GetMetadataForProperties(Model, RealModelType); _propertiesInternal = SortProperties(originalProperties.AsArray()); _properties = new ReadOnlyCollection<ModelMetadata>(_propertiesInternal); } return _properties; } } protected ModelMetadataProvider Provider { get; set; } //ModelMetadata的建立者(後面會有介紹) public virtual string TemplateHint { get; set; } //Model顯示用的模板名稱 }
然而在MVC中默認使用的卻不是這個類,而是它的子類CachedDataAnnotationsModelMetadata(其實中間還隔着一個CachedModelMetadata類,由於該類只是作了簡單的封裝,爲了方便讀者理解,在此省略)。CachedDataAnnotationsModelMetadata作的最主要的一件事情就是經過一系列的Compute函數獲得Model上註解特性的值,並把這些值賦值到對應的屬性。以下:
public class CachedDataAnnotationsModelMetadata : CachedModelMetadata<CachedDataAnnotationsMetadataAttributes> { public CachedDataAnnotationsModelMetadata(CachedDataAnnotationsModelMetadataProvider provider, Type containerType, Type modelType, string propertyName, IEnumerable<Attribute> attributes) : base(provider, containerType, modelType, propertyName, new CachedDataAnnotationsMetadataAttributes(attributes.ToArray())) {
PrototypeCache = new CachedDataAnnotationMetadataAttributes(attributes.ToArray()); //這個賦值應該在它的父類CachedModelMetadata中執行的,這裏簡化一下,方便閱讀 } protected override string ComputeDataTypeName() { if (PrototypeCache.DataType != null) { return PrototypeCache.DataType.ToDataTypeName(); }return base.ComputeDataTypeName(); }
public override string DataTypeName{ get{ return ComputeDataTypeName();}} }
注意代碼中兩個紅色的部分,把Model的註解特性存在PrototypeCache裏面,這樣就能夠經過反射獲得註解特性的值了。好比文章開始處的DemoModel的屬性Email對應的CachedDataAnnotationsModelMetadata對象中 DataTypeName最終被賦值爲「EmailAddress」,等介紹完下一位重量級人物後,會詳解具體調用過程。
咱們還要來認識一位重量級的人物:ModelMetadata的建立者:
public abstract class ModelMetadataProvider { public abstract IEnumerable<ModelMetadata> GetMetadataForProperties(object container, Type containerType); public abstract ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName); public abstract ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType); }
它是一個純抽象類。該類的第一級子類很是重要,看一下代碼:
public abstract class AssociatedMetadataProvider : ModelMetadataProvider { private static void ApplyMetadataAwareAttributes(IEnumerable<Attribute> attributes, ModelMetadata result) { foreach (IMetadataAware awareAttribute in attributes.OfType<IMetadataAware>()) { awareAttribute.OnMetadataCreated(result); } } protected abstract ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName); public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType) {
//獲得Model上上面的Attribute AttributeList attributes = new AttributeList(GetTypeDescriptor(modelType).GetAttributes());
//建立ModelMetadata ModelMetadata result = CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */); //建立ModelMetadata後,用全部實現IMetadataAware接口的Attribute對ModelMetadata進行改造 ApplyMetadataAwareAttributes(attributes, result); return result; } protected virtual ICustomTypeDescriptor GetTypeDescriptor(Type type) { return TypeDescriptorHelper.Get(type); } }
注意紅色的部分,正是由於有這樣一個處理,用戶就能夠經過自已自定義擴展實現IMetadataAware接口,對ModelMetadata進行再加工處理。
根據上面ModelMetadata講解,咱們知道,MVC中默認使用的是CachedDataAnnotationsModelMetadata這個類,因而對應ModelMetadataProvider的最終子類CachedDataAnnotationsModelMetadataProvider。
當外部想經過GetMetadataForType來獲得ModelMetadata時,內部會調用CreateMetadata,再根據原型模式調用CreateMetadataPrototype或CraeteMetadataFromPrototype實現最終建立過程:
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); } }
同MVC的路由表RouteTable同樣,ModelMetadataProvider也有一個靜態的ModelMetadataProviders變量Current來提供默認的ModelMetadataProvider。簡化代碼以下:
public class ModelMetadataProviders { private static ModelMetadataProviders _instance = new ModelMetadataProviders(); private ModelMetadataProvider _currentProvider; internal ModelMetadataProviders() { _currentProvider=new CachedDataAnnotationsModelMetadataProvider(); } public static ModelMetadataProvider Current { get { return _instance._currentProvider; } set { _instance._currentProvider = value; } } }
知道了這兩位重量級的大員後,如今咱們回到文章開始的代碼:
Html.DisplayFor(model => model.Email)
當View中執行這句時,執行HtmlHelper的擴展函數,正式進入漫長的生成MvcString旅程:
【注意,這個圖中代碼是通過N重簡化而來,實際調用流程複雜無比(全是在TemplateHelpers中跳轉),函數參數多如牛毛,實在不忍讓你們看的痛苦】
紅色線是程序執行的主流程,後來的執行主場景都是在TemplateHelpers裏面完成。
從黃色線能夠看出ModelMetadata是經過調用ModelMetadata自己的靜態函數FromLambdExpression--->GetMetadataFromProvider,最終由ModelMetadataProviders完成建立。
再來看看桔黃色部分。MVC中有一個DefaultDisplayTemplates的類,存儲了諸如Boolean/String/Html/Email等等數據類型的模板,MVC最終根據ViewData中的ModelMetadata.DataType的值找到對應的模板,最終完成HTML字符串的輸出,做爲WebViewPage.ExecutePageHierarchy的一部分(WebViewPage請參看:僅此一文讓你明白ASP.NET MVC 之View的顯示(僅此一文系列二))。
本文只是用了ModelMetadata的一個DataType屬性作爲例子,讓你瞭解在Model中添加註解特性是如何被ModelMetadata使用的。還有其它不少Model註解特性(如ReadOnlyAttribute/RequiredAttribute/UIHintAttribute等等)都和該原理相似。
其實ModelMetadata在view顯示中的做用是有限的,由於不少開發人員都不喜歡用這種前臺顯示方式,尤爲隨着json的大量使用,與JQUERY等前臺的完美結合。但它有屬於它的舞臺----Model綁定與驗證,敬請期待!
但願新手看的明白,老手多提意見。若是你以爲對你有幫助,請讓更多人知道它:)