僅此一文讓你明白ASP.NET MVC 之Model的呈現

本文目的

咱們來看一個小例子,在一個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

http://www.cnblogs.com/DotCpp/

生成的是一個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的兄弟--ModelMetadata

      在介紹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的建立者ModelMetadataProvider

咱們還要來認識一位重量級的人物: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旅程:

http://www.cnblogs.com/DotCpp/

     【注意,這個圖中代碼是通過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綁定與驗證,敬請期待!

     但願新手看的明白,老手多提意見。若是你以爲對你有幫助,請讓更多人知道它:)

相關文章
相關標籤/搜索