好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

ASP.NET MVC的View是如何被呈现出来的?[设计篇]

ASP.NET MVC的View是如何被呈现出来的?[设计篇]

在前面的四篇文章中,我们介绍了各种ActionResult以及相关的请求响应机制,但是与“View的呈现”相关的ActionResult是ViewResult。通过ViewResult的执行实现的对View的呈现比上面我们介绍的各种ActionResult要复杂得多,ASP.NET MVC内部设计了一个扩展的View引擎实现了最终的View呈现工作。[本文已经同步到《 How ASP.NET MVC Works? 》中]

目录 
一、View引擎中的View 
二、ViewEngine 
三、ViewResult的执行

一、View引擎中的View

ASP.NET MVC为我们提供了两种View引擎,它们针对不同的动态View设计方式。一种是传统的Web Form引擎,由于该引擎下View的设计与我们定义.aspx页面一致,又称为ASPX引擎。另外一种则是本书默认采用同时也是推荐使用的Razor引擎。在两种View引擎的工作机制之前,有一个必须要知道的问题:View如何表示?提到View,很多ASP.NET MVC的开发人员可能首先想到的就是定义UI界面的.aspx文件(Web Form引擎)或者.cshtml/.vbhtml文件(Razor引擎)。其实对于View引擎来说,View是一个实现了 IView 接口类型的对象。如下面的代码片断所示,IView的定义非常简单,仅仅具有唯一的Render方法根据指定的View上下文和TextWriter对象实现对View的呈现。

    1:   public   interface  IView
    2:  {    
    3:       void  Render(ViewContext viewContext, TextWriter writer);
    4:  }
    5:   
    6:   public   class  ViewContext : ControllerContext
    7:  {
    8:       //其他成员 
    9:       public   virtual   bool  ClientValidationEnabled { get; set; }
   10:       public   virtual   bool  UnobtrusiveJavaScriptEnabled { get; set; }
   11:   
   12:       public   virtual  TempDataDictionary TempData { get; set; }    
   13:      [Dynamic]
   14:       public   object                      ViewBag { [ return : Dynamic] get; }
   15:       public   virtual  ViewDataDictionary ViewData { get; set; }
   16:       public   virtual  IView              View { get; set; }
   17:       public   virtual  TextWriter         Writer { get; set; }
   18:  }
   19:   
   20:   public   abstract   class  HttpResponseBase
   21:  {
   22:       //其他成员 
   23:       public   virtual  TextWriter Output { get; set; }
   24:  }

IView用于呈现View的Render方法具有两个参数,一个是表示View上下文的 ViewContext 对象。通过上面的代码片断可以看出ViewContext是ControllerContext的子类,用于表示状态数据的ViewData、ViewBag和TempData对应着ControllerBase的同名属性。也就是说当执行从Controller的某个Action方法返回的ViewResult的时候,通过创建的ViewContext保持的状态数据直接来源于Controller对象。

ViewContext具有两个布尔类型属性ClientValidationEnabled和UnobtrusiveJavaScriptEnabled表示是否支持客户端验证和Unobtrusive JavaScript。默认的情况下着两个属性通过同名的AppSettings配置项进行设置。如果应用不具有对应的配置,两个属性默认值为False。

    1:   <  configuration  > 
    2:     <  appSettings  > 
    3:       <  add   key  ="ClientValidationEnabled"   value  ="true"  /> 
    4:       <  add   key  ="UnobtrusiveJavaScriptEnabled"   value  ="true"  /> 
    5:     </  appSettings  > 
    6:   </  configuration  > 

配置的范围是针对整个Web应用而言的,这个全局属性还可以通过HtmlHelper的同名静态属性进行设置。值得一提的是,ASP.NET MVC 允许我们针对某个View开启或者关闭对客户端验证和UnobtrusiveJavaScriptEnabled的支持,而这可以通过当前View的HtmlHelper的实例方法EnableClientValidation/EnableUnobtrusiveJavaScript来实现。

    1:   public   class  HtmlHelper
    2:  {
    3:       //其他成员     
    4:       public   void  EnableClientValidation();
    5:       public   void  EnableClientValidation( bool  enabled);
    6:       public   void  EnableUnobtrusiveJavaScript();
    7:       public   void  EnableUnobtrusiveJavaScript( bool  enabled);
    8:     
    9:       public   static   bool  ClientValidationEnabled { get; set; }    
   10:       public   static   bool  UnobtrusiveJavaScriptEnabled { get; set; }    
   11:  }

接口IView的Render方法的第二个参数是一个TextWriter对象。对于该方法来说,只要我们将内容写入该TextWriter即完成了针对相关内容在View上的呈现,因为在调用Render方法的时候,作为该参数的是当前 HttpResponse的Output属性表示的TextWriter 。

二、ViewEngine

View引擎的核心是一个ViewEngine对象,它实现了 IViewEngine 接口。如下面的代码片断所示,IViewEngine定义了两个FindView和FindPartialView方法根据指定的Controller上下文、View名称和布局文件名称去获取对应的View和Partial View,两个方法中具有一个布尔类型的参数useCache表示是否启用缓存。另一个方法ReleaseView用于释放View对象。

    1:   public   interface  IViewEngine
    2:  {    
    3:      ViewEngineResult FindPartialView(ControllerContext controllerContext,  string  partialViewName,  bool  useCache);
    4:      ViewEngineResult FindView(ControllerContext controllerContext,  string  viewName,  string  masterName,  bool  useCache);
    5:       void  ReleaseView(ControllerContext controllerContext, IView view);
    6:  }

FindView和FindPartialView方法返回的并不是实现了IView接口的类型的对象,而是一个类型为System.Web.Mvc.ViewEngineResult对象。如下面的代码片断所示,ViewEngineResult的只读属性View和ViewEngine属性表示找到的View对象和表示自身的ViewEngine对象。在成功获取到对应View的情况下这两个属性会通过构造函数进行初始化。如果没有找到相应的View,则将一个搜寻位置列表传入另一个构造函数创建一个ViewEngineResult,而只读属性SearchedLocations表示的就是这么一个搜寻位置列表。

    1:   public   class  ViewEngineResult
    2:  {    
    3:       public  ViewEngineResult(IEnumerable< string > searchedLocations);
    4:       public  ViewEngineResult(IView view, IViewEngine viewEngine);
    5:     
    6:       public  IEnumerable< string > SearchedLocations { get; }
    7:       public  IView               View { get; }
    8:       public  IViewEngine         ViewEngine { get; }
    9:  }

如果返回的ViewEngineResult包含一个具体的View,那么这个View将会最终被呈现出来。反之,如果ViewEngineResult仅仅包含一个通过SearchedLocations属性表示的在获取目标View过程中使用的搜索位置列表,那么最终呈现出来的就是如下图所示的包含该列表的错误页面。

我们可以通过一个简单的实例来验证这一点。在通过Viual Studio的ASP.NET MVC项目模板创建的空Web应用中,我们定义了如下一个HomeController。在默认的Action方法Index中,我们通过 ViewEngines 的静态只读属性Engines得到一个全局ViewEngine列表,并调用其FindView方法试图去寻找一个根本不存在View(“NonExistentView”)。最后我们将得到的ViewEngineResult对象的SearchedLocations属性表示的搜寻位置列表呈现出来。

    1:   public   class  HomeController : Controller
    2:  {
    3:       public   void  Index()
    4:      {
    5:          ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext,  "NonExistentView" ,  null );
    6:           foreach  ( string  location  in  result.SearchedLocations)
    7:          {
    8:              Response.Write(location +  "<br/>" );
    9:          }
   10:      }
   11:  }

运行我们的程序后表示在获取目标View中采用的搜寻位置列表会如下图所示的方式呈现出来,而这个列表与上图是完全一致的。

在上面实例演示中涉及到了一个重要的静态类型ViewEngines,它通过如下定义的只读属性Engines维护一个全局ViewEngine列表。从给出的定义可以看出,两个原生的ViewEngine在初始化的时候就被添加到了该列表中,它们的类型就是分别代表Web Form和Razor引擎的 WebFormViewEngine 和 RazorViewEngine 如果我们创建了一个自定义View引擎,相应的ViewEngine也可以通过ViewEngines进行注册。

    1:   public   static   class  ViewEngines
    2:  {
    3:       private   static   readonly  ViewEngineCollection _engines =  new  ViewEngineCollection {  new  WebFormViewEngine(),  new  RazorViewEngine() };
    4:     
    5:       public   static  ViewEngineCollection Engines
    6:      {
    7:          get {  return  _engines;}
    8:      }
    9:  }
   10:   
   11:   public   class  ViewEngineCollection : Collection<IViewEngine>
   12:  {
   13:       //其他成员 
   14:       public   virtual  ViewEngineResult FindPartialView(ControllerContext controllerContext,  string  partialViewName);
   15:       public   virtual  ViewEngineResult FindView(ControllerContext controllerContext,  string  viewName,  string  masterName);
   16:  }

ViewEngines的静态只读属性Engines的类型是 ViewEngineCollection ,它是一个元素类型为IViewEngine的集合。ViewEngineCollection同样定义了FindView/FindPartialView这两个方法用于获取指定名称的View和分部View,在方法内部它会遍历集合中 的ViewEngine对象并调用它们的同名方法直到找的一个具体的View或者Partial View。由于WebFormViewEngine排在RazorViewEngine之前,所以前者会被优先使用,这可以从上面两张截图所示的搜寻位置列表看出来(先搜索.aspx和.ascx,再搜索.cshtml和.vbhtml)。

对于ViewEngineCollection的FindView/FindPartialView方法来说,不知道读者是否注意到了它们没有一个表示是否采用缓存的useCache参数。实际上当这两个方法被调用的时候,会先采用缓存的方式调用相应的ViewEngine,如果返回为Null,则以不采用缓存的方式再次调用它们。

三、ViewResult的执行

View引擎对View的获取以及对View的呈现最初是通过ViewResult触发的,那么两者是如何衔接的呢?这是本小节着重讨论的问题,在这之前我们不妨先来看看ViewResult的定义。如下面的代码片断所示,表示ViewResult的类型 ViewResult 是抽象类 ViewResultBase 的子类。

    1:   public   class  ViewResult : ViewResultBase
    2:  {    
    3:       protected   override  ViewEngineResult FindView(ControllerContext context);
    4:       public   string  MasterName { get; set; }
    5:  }
    6:   
    7:   public   abstract   class  ViewResultBase : ActionResult
    8:  {   
    9:       public   override   void  ExecuteResult(ControllerContext context);
   10:       protected   abstract  ViewEngineResult FindView(ControllerContext context);
   11:    
   12:       public   object                      Model { get; }
   13:       public  TempDataDictionary         TempData { get; set; }    
   14:      [Dynamic]
   15:       public   object                      ViewBag { [ return : Dynamic] get; }
   16:       public  ViewDataDictionary         ViewData { get; set; }   
   17:       public   string                      ViewName { get; set; }
   18:       public  ViewEngineCollection       ViewEngineCollection { get; set; }
   19:       public  IView                      View { get; set; }
   20:  }

ViewResultBase的只读属性Model表示作为View的Model对象,三个表示数据状态的属性(ViewData、ViewBag和TempData)来源于Controller的同名属性。View和ViewName属性则是代表具体的View对象和View的名称。ViewEngineCollection属性值默认来源于ViewEngines的静态属性Engines代表的全局ViewEngine列表。

ViewResultBase用于获取具体View的FindView方法在ViewResult类中被实现,后者提供了额外的属性MasterName表示布局文件名称。在FindView方法的内部会直接调用ViewEngineCollection属性的FindView方法,如果返回的ViewEngineResult包含一个具体的View(View属性不为空),则直接返回该ViewEngineResult,否则抛出一个InvalidOperation异常,并将通过ViewEngineResult的SearchedLocations属性表示的搜寻位置列表格式化成一个字符串作为该异常的消息,所以图8-5所示的搜寻位置列表实际上是抛出的InvalidOperation异常的消息。

ASP.NET MVC的View引擎涉及到的相关的类型/接口以及它们之间的关系可以通过如图下所示的UML来表示。ViewResult通过静态类型ViewEngines利用View引擎激活对应的View对象并最终将View的内容呈现出来。

与除EmptyResult以外的所有ActionResult类型一样,抽象类Conrtoller中提供了相应的方法辅助创建ViewResult。如下面的代码片断所示,Controller具有如下一系列View方法帮助我们根据指定的View名称、View对象、布局文件名称和Model对象创建相应的ViewResult。

    1:   public   abstract   class  Controller : ControllerBase, ...
    2:  {
    3:       //其他成员    
    4:       protected  ViewResult View();
    5:       protected  ViewResult View( object  model);
    6:       protected  ViewResult View( string  viewName);
    7:       protected  ViewResult View(IView view);
    8:       protected  ViewResult View( string  viewName,  object  model);
    9:       protected  ViewResult View( string  viewName,  string  masterName);
   10:       protected   virtual  ViewResult View(IView view,  object  model);
   11:       protected   virtual  ViewResult View( string  viewName,  string  masterName,  object  model);
   12:  }

ViewResult与View引擎的交互体现在用于执行执行ActionView的ExecuteResult上。如下面的代码片断所示,如果View属性为Null,会调用FindView方法得到一个用于封装指定名称(如果没有执行则采用当前的Action名称作为View名称)的View的ViewEngineResult对象,并将其View属性作为自身的View。然后创建View上下文,并将该上下文和当前HttpResponse的Output属性代表的TextWriter对象作为参数调用View对象的Render方法实现对View的最终呈现。View呈现完成之后,通过ViewEngineResult得到对应的ViewEngine,并调用其Release对象对View进行回收操作。

    1:   public   abstract   class  ViewResultBase : ActionResult
    2:  {
    3:       //其他成员 
    4:       public   override   void  ExecuteResult(ControllerContext context)
    5:      {   
    6:           //其他操作      
    7:           if  ( string .IsNullOrEmpty( this .ViewName))
    8:          {
    9:               this .ViewName = context.RouteData.GetRequiredString( "action" );
   10:          }
   11:          ViewEngineResult result =  null ;
   12:           if  ( this .View ==  null )
   13:          {
   14:              result =  this .FindView(context);
   15:               this .View = result.View;
   16:          }
   17:          TextWriter output = context.HttpContext.Response.Output;
   18:          ViewContext viewContext =  new  ViewContext(context,  this .View,  this .ViewData,  this .TempData, output);
   19:           this .View.Render(viewContext, output);
   20:           if  (result !=  null )
   21:          {
   22:              result.ViewEngine.ReleaseView(context,  this .View);
   23:          }
   24:      }
   25:  }

ViewResult为们提供了一种与View引擎交互的手段,其实在进行View的获取和呈现的时候完全可以抛开ViewResult,直接利用View引擎来完成,如下两种Action方法的定义是完全等效的。

    1:   //Action方法直接返回ViewResult 
    2:   public   class  HomeController : Controller
    3:  {
    4:       public  ActionResult Index()
    5:      {
    6:           return  View();
    7:      }
    8:  }
    9:   
   10:   //Action方法直接调用View引擎 
   11:   public   class  HomeController : Controller
   12:  {
   13:       public   void  Index()
   14:      {
   15:           string  viewName = ControllerContext.RouteData.GetRequiredString( "action" );
   16:          ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, viewName,  null );
   17:           if  ( null  == result.View)
   18:          { 
   19:               throw   new  InvalidOperationException(FormatErrorMessage(viewName,result.SearchedLocations));
   20:          }
   21:           try 
   22:          {
   23:              ViewContext viewContext =  new  ViewContext(ControllerContext, result.View,  this .ViewData,  this .TempData, Response.Output);
   24:              result.View.Render(viewContext, viewContext.Writer);
   25:          }
   26:           finally 
   27:          {
   28:              result.ViewEngine.ReleaseView(ControllerContext, result.View);
   29:          }
   30:      }
   31:   
   32:       private   string  FormatErrorMessage( string  viewName, IEnumerable< string > searchedLocations)
   33:      {
   34:           string  format =  "The view '{0}' or its master was not found or no view engine supports the searched locations. The following locations were searched:{1}" ;
   35:          StringBuilder builder =  new  StringBuilder();
   36:           foreach  ( string  str  in  searchedLocations)
   37:          {
   38:              builder.AppendLine();
   39:              builder.Append(str);
   40:          }
   41:           return   string .Format(CultureInfo.CurrentCulture, format, viewName, builder);
   42:      }
   43:  }

上面我们仅仅介绍了ViewResult利用View引擎进行View的获取和呈现,其实当我们调用HtmlHelper的扩展方法Partial将指定的Partial View的HTML呈现出来时,内部调用View引擎的方式与之类

ASP.NET MVC的View是如何被呈现出来的?[设计篇]  
ASP.NET MVC的View是如何被呈现出来的?[实例篇]

作者: Artech
出处: http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

分类:  [01] 技术剖析

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于ASP.NET MVC的View是如何被呈现出来的?[设计篇]的详细内容...

  阅读:55次