ITusk

[toc]

  上一节介绍了 Asp.net mvc 中除 ViewResult 外的所有的 ActionResult,这一节介绍 ViewResult

ViewResultBase

  ViewResultBaseAsp.net mvc 中所有的 ViewResultParticalViewResult 的基类,该类继承自 ActionResult。该类的定义如下示:

//---------------------------------属性部分-------------------------------------
//当前 View 所会用的强类型 model
public object Model{get{..}}

//Controller 向 View传值使用的 TempData 集合
public TempDataDictionary TempData{get{..} set{..}}

// 当前ViewResult 所对应的View 
public IView View { get; set; }

//Controller 向 View传值使用的 ViewBag 集合
public dynamic ViewBag
{
     get
       {
           if (_dynamicViewData == null)
            {
               _dynamicViewData = new DynamicViewDataDictionary(()=>ViewData);
            }
                return _dynamicViewData;
       }
}

//Controller 向 View传值使用的 ViewData 集合
public ViewDataDictionary ViewData{get{..}set{..}}

//用于解析视图的视图引擎的集合
public ViewEngineCollection ViewEngineCollection{get{..}set{..}}

//当前ViewResult 对应的 View的名称
public string ViewName{get{..}set{..}}

//---------------------------------方法部分-----------------------------------

//根据 ControllerContext 查找对应的 IView
protected abstract ViewEngineResult FindView(ControllerContext context);

public override void ExecuteResult(ControllerContext context);

  其中的 ViewDataTempDataViewBag 分别对应 ControllerBase 中的同名属性,该类型中还定义了一个 IView 类型的属性 View,该接口的定义如下示:

public interface IView
{
    void Render(ViewCotext viewContext,TextWritter textWritter);
}

  我们知道,对于客户端请求的页面,最终返回客户端的只能是一对 Html 的字符串,显然不论是后台的 Razor Page 还是 WebForm Page 都不能直接响应客户端的请求,这里的 Render 方法的作用就是将服务器端的这种动态的页面渲染为一个能够直接响应客户端请求的静态页面。该方法被调用时,其第二个参数传入的便为 Response.Output

ViewResult

  ViewResult 用于返回一个普通的视图页面。该类型的定义如下示:

public class ViewResult : ViewResultBase
{
//---------------------------属性部分-----------------------------------------

//当前 View 使用的模板页的名称
public string MasterName{get{..}set{..}}

//---------------------------方法部分-----------------------------------------

protected override ViewEngineResult FindView(ControllerContext context)
{
  //获取对应的 View 信息
  ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName);
  if (result.View != null)
   {
       return result;//查找成功
   }

   //查找失败,生成异常信息,其中包含查找过程中搜索的位置信息
   StringBuilder locationsText = new StringBuilder();
   foreach (string location in result.SearchedLocations)
   {
      locationsText.AppendLine();
      locationsText.Append(location);
   }
   throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,                                                   MvcResources.Common_ViewNotFound, ViewName, locationsText));
        }
}

ParticalViewResult

  该类型返回一个分部视图,所谓的分部视图即必须嵌套在其它的 View 中呈现的视图。该类型中仅有包含一个成员,是对基类的 FindView 方法的重写,重写逻辑与 ViewResult 一致,不同之处在于其在获取对应的 View 信息时调用的时 ViewEngineCollectionFindParticalView 方法,除此之外,与 ViewResult 的同名方法处理完全一致。
  

ViewEngine

  这里的 ViewEngin 指所有实现了 IViewEngine 接口的类型。该类型用于根据视图的名称获取对应的 IView 对象实例。在 Asp.net mvc 中内部定义了两种不同的 ViewEngineRazorViewEngineWebFormViewEngine,分别对应两种不同的视图形式:Razor PageWebForm Page.在这里我们只介绍第一种。
  首先,看一下 IViewEngine 接口的定义:

public interface IViewEngine
    {
        ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);
        
        ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);
        
        void ReleaseView(ControllerContext controllerContext, IView view);
    }

  其中前两个方法用于根据视图名称获取对应的 IView,最后一个方法用于释放 IView 对象,我们看到前面的两个方法的返回类型均为 ViewEngineResult,下面看一下该类型的定义:

public class ViewEngineResult
{
        public ViewEngineResult(IEnumerable<string> searchedLocations);

        public ViewEngineResult(IView view, IViewEngine viewEngine);
        
        //定位视图时的查找路径
        public IEnumerable<string> SearchedLocations { get; private set; }
        
        //当前的 View
        public IView View { get; private set; }
        
        //查找时使用的视图引擎
        public IViewEngine ViewEngine { get; private set; }
}

  当调用 IViewEngineFindViewFindParticalView 方法时,如果查找成功,其返回结果中便包含一个 IView 对(View),如果查找失败,查找结果中便包含一个搜索位置的列表(SearchedLocations)。例如当访问一个根本不存在的 NotExist 页面。则会产生下面的异常。

~/Views/default/NotExist.aspx
~/Views/default/NotExist.ascx
~/Views/Shared/NotExist.aspx
~/Views/Shared/NotExist.ascx
~/Views/default/NotExist.cshtml
~/Views/default/NotExist.vbhtml
~/Views/Shared/NotExist.cshtml
~/Views/Shared/NotExist.vbhtml

  上面的异常信息的搜索位置便来自与 ViewEngineResult 类型的 SearchedLocations 属性。

VirtualPathProviderViewEngine 和 BuildManagerViewEngine

  上面所说的两种具体的 ViewEngine 类型均为 BuildManagerViewEngine 类型的子类,而后者有继承自 VirtualPathProviderViewEngine

VirtualPathProviderViewEngine

  该类型是一个抽象类,该类的主要成员定义如下示:

public abstract class VirtualPathProviderViewEngine : IViewEngine{
//----------------------------属性部分-----------------------------------------------

//区域模板页存储位置格式模板, 型如,~/Areas/{2}/Views/{1}/{0}.cshtml等
//其中的占位符最终会被替换为Area名称(如果有),Controller名称、Action名称
public string[] AreaMasterLocationFormats { get; set; }

//区域部分视图存储位置格式模板
public string[] AreaPartialViewLocationFormats { get; set; }

//区域视图存储位置格式模板
public string[] AreaViewLocationFormats { get; set; }

//模板页存储位置格式模板
public string[] MasterLocationFormats { get; set; }

//视图或模板页文件后缀,如cshtml,vbhtml等
public string[] FileExtensions { get; set; }

//部分视图存储位置格式模板
public string[] PartialViewLocationFormats { get; set; }

//视图存储位置格式模板,形如:~/Views/{1}/{0}.cshtml等
public string ViewLocationFormats{get;set;}

//----------------------------方法部分-----------------------------------------------

//根据部分视图路径创建 IView实例
protected abstract IView CreatePartialView(ControllerContext controllerContext, string partialPath);

//根据视图文件创建 IView 实例
protected abstract IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath);

//判断是否存在指定位置的文件
protected virtual bool FileExists(ControllerContext controllerContext, string virtualPath);

//IViewEngine 接口方法
public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache){}

public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache){}

public virtual void ReleaseView(ControllerContext controllerContext, IView view){}

}

  这里说一说它的 FindPartialView 方法的实现,该方法的实现如下示:

public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache){
 if (controllerContext == null){
 //抛出参数异常
 ...
         }
if (String.IsNullOrEmpty(viewName)){
//抛出参数异常
...

string[] viewLocationsSearched;
string[] masterLocationsSearched;

//获取当前的控制器的名称
string controllerName = controllerContext.RouteData.GetRequiredString("controller");

//获取视图文件的存储路径
string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);

//获取模板文件的存储位置
string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched);

//查找失败,将搜索路径写入的返回结果中
if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
{
 return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
}

return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
}

  从上可以看出,该方法有一个 bool 类型的参数 useCache,该参数表示在根据根据视图名称定位视图文件的存储位置时是否使用缓存,在根据定位视图文件存储位置时,需要根据当前的请求解析出Area、Controller、Action等信息,然后将这些信息代入到上面定义的一堆视图模板存储模板中,得到搜索的位置列表,然后对路径的列表进行遍历,根据路径信息判断是否存在指定位置的文件。为了提高整个定位过程的效率,Asp.net mvc 会对匹配成功的视图文件路径信息进行缓存,首先根据当前的视图的名称,视图所属Controller的名称,所在Area的名称,以及表示视图类型的前缀(如View、PartialView、Master)等生成缓存的 Key,然后对匹配成功的路径信息进行存储(内部存储在 HttpContext.Cache 中) 。这里的这个参数便表示是否其中这个缓存进行搜索。
  
  FindPartialView 方法的实现,逻辑实现过程基本同 FindView,不同的地方就在于生成获取缓存项的 Key 时的前缀参数为 PartialView,而前者为 View
  
  void ReleaseView(ControllerContext controllerContext, IView view) 方法的实现比较简单,就是使用 as 运算符尝试将 view 转换为 IDisposable 类型,如果转换成功,则调用其 Dispose 方法。

BuildManagerViewEngine

  该类型同样的是一个抽象类,该类的主要成员的定义如下示:

public abstract class BuildManagerViewEngine : VirtualPathProviderViewEngine{

//--------------------------------构造函数------------------------------------
protected BuildManagerViewEngine();

protected BuildManagerViewEngine(IViewPageActivator viewPageActivator);

internal BuildManagerViewEngine(
IViewPageActivator viewPageActivator, 
IResolver<IViewPageActivator> activatorResolver,
IDependencyResolver dependencyResolver, /*IOC 容器*/
VirtualPathProvider pathProvider/*封装一组可以让Web应用从虚拟文件系统中检索资源的方法*/
);

//-----------------------------属性部分---------------------------------------

//提供一组方法用于帮助 Asp.net 的编译工作管理,例如获取当前应用引用的程序集信息、根据虚拟路径获取
//对应文件编译产生的类型等,详情可以查看 BuildManager 类
internal IBuildManager BuildManager{get{..}set{..}}

protected IViewPageActivator ViewPageActivator{get{..}}

//---------------------------方法部分----------------------------------------

//判断是否存在指定路径的文件,这里用于查找定位视图文件编译后产生的 .cs 文件
protected override bool FileExists(ControllerContext controllerContext, string virtualPath);
}

  上面还有一个 IViewPageActivator 类型的属性,在 Asp.net mvc 中,定义的视图文件必须经过编译为特定的类型,才能够被执行,关于视图的编译问题,后面会详细说明,该接口便是用于创建视图文件编译产生类型对应的实例。该接口的定义如下示:

 public interface IViewPageActivator
 {
        object Create(ControllerContext controllerContext, Type type);
  }

  在 BuildManagerViewEngine 类中提供了该接口唯一的默认实现 DefaultViewPageActivator,其创建实例的过程分两种情况,第一种就是判断 IOC 容器(默认为 DependencyResolver.Currrent 属性指定的默认值 DefaultDependencyResolver 类型的实例)是否注册有参数 type 指定的类型,如果有注册,则调用其 GetService(type) 方法创建类型实例,否则,则使用反射的方式创建类型的实例,然后返回该实例。
  BuildManagerViewEngine 类型具有两个具体的子类,RazorViewEngineWebFormViewEngine,这里只介绍前者。
  RazorViewEngine 类型的定义如下示:

public class RazorViewEngine
{
//定义启动页文件的名称
internal static readonly string ViewStartFileName = "_ViewStart";
public RazorViewEngine(IViewPageActivator viewPageActivator)
            : base(viewPageActivator)
{
    //区域中的视图的位置信息模板
    AreaViewLocationFormats = new[]
    {
          "~/Areas/{2}/Views/{1}/{0}.cshtml",
          "~/Areas/{2}/Views/{1}/{0}.vbhtml",
          "~/Areas/{2}/Views/Shared/{0}.cshtml",
          "~/Areas/{2}/Views/Shared/{0}.vbhtml"
     };

    //区域中视图模板的位置信息模板
    AreaMasterLocationFormats = new[]
    {
         "~/Areas/{2}/Views/{1}/{0}.cshtml",
         "~/Areas/{2}/Views/{1}/{0}.vbhtml",
         "~/Areas/{2}/Views/Shared/{0}.cshtml",
         "~/Areas/{2}/Views/Shared/{0}.vbhtml"
    };

   //区域中 分部视图的位置信息模板
    AreaPartialViewLocationFormats = new[]
    {
          "~/Areas/{2}/Views/{1}/{0}.cshtml",
          "~/Areas/{2}/Views/{1}/{0}.vbhtml",
          "~/Areas/{2}/Views/Shared/{0}.cshtml",
          "~/Areas/{2}/Views/Shared/{0}.vbhtml"
     };

     //视图的位置信息模板
     ViewLocationFormats = new[]
    {
           "~/Views/{1}/{0}.cshtml",
           "~/Views/{1}/{0}.vbhtml",
           "~/Views/Shared/{0}.cshtml",
           "~/Views/Shared/{0}.vbhtml"
      };

    //视图模板页的位置信息模板
    MasterLocationFormats = new[]
    {
        "~/Views/{1}/{0}.cshtml",
        "~/Views/{1}/{0}.vbhtml",
        "~/Views/Shared/{0}.cshtml",
        "~/Views/Shared/{0}.vbhtml"
    };

    //分部视图页存储位置信息模板
    PartialViewLocationFormats = new[]
   {
        "~/Views/{1}/{0}.cshtml",
        "~/Views/{1}/{0}.vbhtml",
        "~/Views/Shared/{0}.cshtml",
        "~/Views/Shared/{0}.vbhtml"
    };

   //视图文件扩展名集合
   FileExtensions = new[]
   {
        "cshtml",
        "vbhtml",
    };
  }

//重写基类的方法,返回 RazorView 类型的实例
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath);

//重写基类的方法,返回 RazorView 类型实例
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath);
}

  从上图可以看出,在该类的构造函数中,对其继承的视图存储位置模板变量进行了初始化,这些变量用于视图文件的搜索。

视图类型

  这里的视图是指实现 IView 接口的类型,该接口的定义如下示:

public interface IView
{   
    //渲染视图
    void Render(ViewContext viewContext, TextWriter writer);
}

  第一个参数继承自 ControllerContext,可以看作是对ControllerContext、ViewData/TempData 等对象的封装,第二个参数表示用于将解析结果输出的流,调用时传入值为 HttpContext.Response.Output。
  该接口有一个直接的实现子类: BuildManagerView,该类型是一个抽象类,具有两个子类,分别为 RazorViewWebFormView,同样的,这里只介绍前者。

BuildManagerView

  该类是一个抽象类,其定义如下示:
  

public abstract class BuildManagerCompiledView : IView
{
//-----------------------------------构造函数---------------------------------
protected BuildManagerCompiledView(ControllerContext controllerContext, string viewPath)
    : this(controllerContext, viewPath, null);

protected BuildManagerCompiledView(ControllerContext controllerContext, string viewPath, IViewPageActivator viewPageActivator)
            : this(controllerContext, viewPath, viewPageActivator, null)
}

internal BuildManagerCompiledView(ControllerContext controllerContext, string viewPath, IViewPageActivator viewPageActivator, IDependencyResolver dependencyResolver);

//------------------------------- 属性部分 -----------------------------------

internal IBuildManager BuildManager{get{..}set{..}}

//View文件的虚拟路径
public string ViewPath { get; protected set; }

//------------------------------- 方法部分 ----------------------------------

//接口方法
public virtual void Render(ViewContext viewContext, TextWriter writer);

//视图渲染
protected abstract void RenderView(ViewContext viewContext, TextWriter writer, object instance);

  下面看一下其 Render 方法的实现。在该方法中,首先调用 BuildManager.GetCompiledType(ViewPath) 方法获取与当前View对应的编译产生的类的类型,然后创建该类型的实例(通过),如果该实例不为空,然后将该实例作为参数调用 RenderView 方法。

RazorView

  该类型表示一个使用 Razor 语法的 IView ,该类型的定义如下示:

 public class RazorView : BuildManagerCompiledView
 {
 //---------------------------------构造函数---------------------------------
 public RazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions);

public RazorView(ControllerContext controllerContext, string viewPath, string layoutPath, bool runViewStartPages, IEnumerable<string> viewStartFileExtensions, IViewPageActivator viewPageActivator);

//--------------------------------- 属性部分 ---------------------------------
//布局文件的存放路径
public string LayoutPath { get; private set; }

//是否使用启动页(_ViewStart.cshtml/_ViewStart.vbhtml)
public bool RunViewStartPages { get; private set; }

//获取 StartPage 的委托
internal StartPageLookupDelegate StartPageLookup { get; set; }

//该类型用于根据一个虚拟路径创建一个类型实例
internal IVirtualPathFactory VirtualPathFactory { get; set; }

//通过该类型可以实现针对不同的设备展示同一视图的不同的展示效果,详情请看:
//http://www.c-sharpcorner.com/UploadFile/b696c4/display-mode-provider-in-mvc-5-application/
internal DisplayModeProvider DisplayModeProvider { get; set; }

//启动页的文件名扩展
public IEnumerable<string> ViewStartFileExtensions { get; private set; }

 }

  该类型中方法只有一个,该方法是对基类中的抽象方法 void RenderView(ViewContext viewContext, TextWriter writer, object instance) 的实现。用于对视图进行渲染。该方法的定义如下示:

protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
{
     if (writer == null)
     {
          throw new ArgumentNullException("writer");
     }

     WebViewPage webViewPage = instance as WebViewPage;

     //获取视图实例失败
     if (webViewPage == null)
     {
           throw new InvalidOperationException(
                String.Format(
                CultureInfo.CurrentCulture,
                MvcResources.CshtmlView_WrongViewBase,
                ViewPath));
     }

// An overriden master layout might have been specified when the    ViewActionResult got returned.
// We need to hold on to it so that we can set it on the inner page once it has executed.
   //这里针对调用 Controller的 View 方法时指定了 layout 参数的情况
   webViewPage.OverridenLayoutPath = LayoutPath;
   webViewPage.VirtualPath = ViewPath;
   webViewPage.ViewContext = viewContext;
   webViewPage.ViewData = viewContext.ViewData;
   
   //初始化 HtmlHelper、UrlHelper等对象
   webViewPage.InitHelpers();

   if (VirtualPathFactory != null)
   {   //改属性默认为一个 VirtualPathFactoryManager 实例
       webViewPage.VirtualPathFactory = VirtualPathFactory;
    }

   if (DisplayModeProvider != null)
   {
         webViewPage.DisplayModeProvider = DisplayModeProvider;
   }

   WebPageRenderingBase startPage = null;

  //是否使用启动页
  if (RunViewStartPages)
   {
        startPage = StartPageLookup(webViewPage, RazorViewEngine.ViewStartFileName, ViewStartFileExtensions);
  }
  
  //执行视图的渲染工作,包括视图主体层级的渲染、Section的渲染,子视图的渲染
  //此方法可以看作是视图渲染的入口方法,内部进行上述的类型的视图渲染
  webViewPage.ExecutePageHierarchy(new WebPageContext(context: viewContext.HttpContext, page: null, model: null), writer, startPage);
        }
    }

  该方法的 writter 参数的值为 HttpContext.Response.Output, 第三个参数 instance 为根据当前的视图文件编译产生的类的实例。

视图的编译

  前面说过, .cshtml.vbhtml 的视图文件必须经过编译为特定的类型才能够执行,Asp.net mvc 中默认采用动态编译的方式对视图文件进行编译,所谓动态编译就是,当针对一个视图文件被第一次访问时才会被编译,当应用发布后,针对视图文件的修改也会引起视图文件的重新编译,默认情况下,视图文件的编译是针对目录级别来进行编译的,即同一目录下的视图文件会被编译进同一个程序集中。
  视图文件编译后生成的类型全部继承自 WebViewPageWebViewPage,后者针对使用强类型的视图,其中的 TModel 表示该视图页面的 Model。例如,现有 ~/Views/home/index.cshtml 这样的一个视图文件,那么其编译后便会产生一个继承自 WebViewPage 名称为 **_Page_Views_home_Index_cshtml** 的类。如果当前的 home 文件夹下有多个的视图文件,那么这多个视图文件会被编译进同一个程序集中去。
    当将 Asp.net mvc 应用部署在 IIS 上时,编译生成的 .cs 文件会被放在 Asp.Net 的临时目录下,形如:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\projectname/ 下的某个子目录下,需要注意的时编译生成的文件的名称与编译生成的类的名称并不一致,因此,查找起来不是很方便,可以文件生成的时间来查找。对于运行于 IIS Express 下的应用,生成的文件并不会放在上面的目录下。此时,可以将项目属性中的服务器设置为本地的 IIS 服务器的方式来查看。
    另有一种查看编译生成类的方式,在 Visual Studio 中安装一个 Razor Generator 的扩展插件,然后在某个视图文件的属性选项卡中将其自定义工具设置为 RazorGenerator,然后右键运行自定义工具便会生成当前视图文件编译产生的类文件。

WebViewPage 和 WebViewPage &ltTModel>

  在说这两个类型之前,先看一下下面的类型的关系,然后一层一层的往下说
Alt text|center
图片来自 Asp.net mvc 5 框架揭秘

WebPageExecutingBase

  该类型在上面的图中没有标识出来,其是所有的 CHTML 类型的基类,该类是一个抽象类,上面图中的第一层级的 WebPageRenderingBase 便是该类的直接子类。下面我们看一看该类中定义的主要的成员。

public abstract class WebPageExecutingBase
{
//-------------------------------属性部分 ------------------------------------
//跨请求和会话进行信息共享
public virtual HttpApplicationStateBase AppState{get{..}}

//当前请求的上下文信息
public virtual HttpContextBase Context { get; set; }

//提供页面的渲染过程中的监测服务
internal InstrumentationService InstrumentationService{get{..}set{..}}

//当前视图文件的虚拟路径
public virtual string VirtualPath { get; set; }

//根据虚拟路径创建一个指定文件的类型
public virtual IVirtualPathFactory VirtualPathFactory{get{..}set{..}}

//------------------------- 方法部分 ------------------------------------------
////根据当前视图的虚拟路径及参数信息生成 url
public virtual string Href(string path, params object[] pathParts);

//核心的方法,所有的视图的渲染工作都是在该方法中进行的或者说是以此方法为开端展开的
public abstract void Execute();

//将 result 写入到响应的输出流中,其中的 HelperResult 实现了 IHtmlString接口
public abstract void Write(HelperResult result);

//将 value 写入到响应的输出流中
public abstract void Write(object value);

//将 value 写入到响应的输出流中,这里侧重输出的内容为字面值
public abstract void WriteLiteral(object value);

//下面的三个方法用于 html 页面代码的生成,在 上面的Write/WriteLiteral等方法的子类实现中内部调用
//下面的三个方法
public static void WriteTo(TextWriter writer, HelperResult content)
     
public static void WriteTo(TextWriter writer, object content)
        
public static void WriteLiteralTo(TextWriter writer, object content)

//获取输出流
protected internal virtual TextWriter GetOutputWriter()
}

  在该类中定义了多个 BeginContextEndContext 方法的重载,这两个方法分别在视图渲染之前和视图渲染之后执行,记录一些视图渲染过程中的信息,如渲染开始的位置、写入数据的长度,数据是否为字面值以及所使用的 TextWritter 类型的实例。在两个方法的内部均调用了上面的 InstrumentationService 的同名方法。该类型为检测视图渲染过程提供了可能,使用于一些与视图渲染性能相关的场合,更多详细信息请看 这里

WebPageRenderingBase

  该类是 WebPageExecutingBase 的直接子类,该类的主要成员如下示:

public abstract class WebPageRenderingBase : WebPageExecutingBase, ITemplateFile
{
//-------------------------------- 属性部分 ---------------------------------

//缓存对象,其值来自 HttpContext 的同名属性
public virtual Cache Cache{get{..}}

//布局文件的虚拟路径
public abstract string Layout { get; set; }

//存储与当前页面相关信息的字典,如页面的标题 title
public abstract IDictionary<object, dynamic> PageData { get; }

//针对当前请求的 HttpRequest,其值来自 HttpContext的同名属性
public virtual HttpRequestBase Request{get{..}}

//针对当前请求的响应的 HttpResponse,其值来自 HttpContext 的同名属性
public virtual HttpResponseBase Response{get{..}}

//辅助工具类,封装一些请求处理常用的方法,如路径转换、Url编/解码等,其值来自 HttpContext 的同名属性
public virtual HttpServerUtilityBase Server{get{..}}

//会话,其值来自 HttpContext 的同名属性
public virtual HttpSessionStateBase Session

//当前上下文的用户,来自HttpContext 的同名属性
public virtual IPrincipal User{get{..}internal set{..}}

//当前请求方法是否为 POST
public virtual bool IsPost{get{..}}

//当前请求是否为 Ajax 请求(判断Http头部的 X-Requested-With 字段是否为 XmlHttpRequest)
public virtual bool IsAjax{get{..}}

//-------------------------------- 方法部分 ----------------------------------

//主体视图渲染,其中会调用 Execute() 方法
public abstract void ExecutePageHierarchy();

//渲染页面,path 表示要渲染的(子)视图的虚拟路径,data 表示渲染过程中使用的参数
public abstract HelperResult RenderPage(string path, params object[] data);
}

  该类主要封装了一些请求处理过程中需要使用到的一些组件,并定义了视图渲染的入口方法。

WebPageBase

 该类是 WebPageRenderingBase 的直接子类,同样的是一个抽象类,该类的主要成员定义如下示:

public abstract class WebPageBase : WebPageRenderingBase
{
 //---------------------------- 属性部分 -------------------------------------
 //记录当前已渲染的 Section的信息,存储值为 Section 的 name, 这里的已渲染是指已调用   RenderSection 方法
 private readonly HashSet<string> _renderedSections;

//记录当前 body 是否已渲染,即是否已调用 RenderBody 方法
private bool _renderedBody = false;

//一个临时的 TextWritter,用于进行当前页面主体部分的渲染,最终会被合并进 _currentWritter
private StringWriter _tempWriter;

//当前渲染当前整体的 TextWritter,其被赋值为 HttpContext.Response.Output
//private StringWritter _currentWritter;

//当前所使用的 TextWritter,其值为 OutputStack 的栈顶元素
public TextWriter Output;

//存储有 所有 TextWritter 的栈
 public Stack<TextWriter> OutputStack

//-------------------------- 方法部分 --------------------------------------

//根据虚拟路径创建类型实例,用于创建启动页或布局页的实例(通过IVirtualPathFactory)
public static WebPageBase CreateInstanceFromVirtualPath(string virtualPath){..}

//定义 Section,第一个参数为 Section 的名称,第二个参数为一个无参返回类型为void 的委托,用于将Section 内容写入到一个 TextWritter,在该类内部定义有一个 Dictionary<string,SectionWritter> 的字典,该方法内部就是将其存入到该字典中
public void DefineSection(string name, SectionWriter action){..}

//判断是否已定义指定名称的 section
 public bool IsSectionDefined(string name){..}

//进行页面主体部分的渲染,在布局页中必须包括该方法
public HelperResult RenderBody()

//进行布局页或子视图的渲染,内部调用下面的 RenderPageCore 方法
public override HelperResult RenderPage(string path, params object[] data)

private HelperResult RenderPageCore(string path, bool isLayoutPage, object[] data)

//进行 Section 的渲染,内部调用其重载 RenderSection(name,required:true),第二个参数表示该 Section是否为必须的
public HelperResult RenderSection(string name)
public HelperResult RenderSection(string name, bool required)
}

  在进行视图的渲染的时候,首先会判断是否使用了启动页,如果有则进行启动页的渲染,然后进行主体页面的渲染,如 bodysection 等,然后判断是否使用了布局页,如果有则进布局页的渲染,将前面的内容填入到模板页面的 坑里面。最后将之前几步得到的全部内容写入的响应的输出流中。
  那么针对每个在视图文件中定义的 Sectionbody 是如何与模板文件组成一个完整的页面的呢?
  在每个视图中使用的 @section sectionname 都会被编译为对 DefineSection(string name,SectionWriter action) 方法的调用,其中 @section 块中的内容会被编译为第二个参数的委托,该委托内部调用void Write(object value)/void WriteLiteral(object value) 方法将其写入到一个 TextWritter 实例中,前面说过,该类中定义有一个 Dictionary 类型的属性,其中存储的便是所定义的 Section的名称及编译后的委托,在布局页中调用 HelperResult RenderSection(string name,bool isRequired) 方法,返回类型 HelperResult 实现了 IHtmlString 接口,该接口中仅定义了一个 string ToHtmlString() 方法,该类型的接口方法的实现,便是调用的 上面写入的 TextWriter 实例的 ToString() 方法获取的。HelperResult 的构造函数接受一个 Action 的参数,这表示一个接受一个 TextWritter 类型参数,返回类型 为 void 的委托,对于每个 HelperResult 类型的实例,该参数传入的总为一个新的 StringWritter 类型的实例,而前面的 DefinedSection 中最后写入的那个 TextWriter 便是这里所创建的 StringBuilder 类型的实例,最后这些 TextWritter 都会被合并到 **_currentWriter** 属性,即 HttpContext.Response.Output 中去,最终被输出到客户端。

WebViewPage

  该类型为所有的非强类型视图编译后的类型,由于在其基类中已经处理了大部分的工作,因此,该类中主要定义了一些在视图中使用的一些属性信息。该类型的主要成员如下示:

public abstract class WebViewPage : WebPageBase, IViewDataContainer, IViewStartPageChild
{
    public override HttpContextBase Context;
    
    public HtmlHelper<object> Html{..}

    public AjaxHelper<object> Ajax{..}

    internal string OverridenLayoutPath{..}

    public TempDataDictionary TempData{..}

    public object Model{get;set}

    public UrlHelper Url { get; set; }
   
    public ViewContext ViewContext { get; set; }

    public ViewDataDictionary ViewData{..}

//----------------------- 方法部分-------------------------------------------
...
...
}

   这里说一下 OverridenLayoutPath 这个属性,这个属性表示对当前的视图使用新的模板页,覆盖原有的布局页,在 Controller 下定义有一个 View(string viewName, string masterName),该方法的第二个参数最终便是对该属性进行了赋值。

WebViewPage <ltTModel>

  所有的强类型视图编译产生的模型均继承自该类,该类又继承自 WebViewPage,该类中主要对基类中的一些属性进行了重写,如前面定义的 HtmlHelper<object> Html,在这里被重写定义为了 HtmlHelper<TModel> ,Model 属性的类型有原来的 Object 便为了 TModel

总结

  最后,总结一些视图呈现的整体流程。

  对于 Action 方法返回的 ViewResult 对象,调用其 ExecuteResult(ControllerContext context) 方法,在该方法中,首先根据当前请求的上下文信息及视图的名称等信息定位对应的视图文件,如果搜索失败,则抛出异常,异常中包括视图搜索的路径信息,如果搜索成功则创建一个 RazorView 类型的实例,然后调用该实例的 Render(ViewContext context,TextWriter writer) 方法,在该方法中,首先对当前请求的视图文件进行编译,编译生成的类型为 WebViewPageWebViewPage<TModel> 类型的子类,然后调用类型实例的 ExecutePageHierarchy 方法进行视图的渲染,首先进行启动页的渲染,然后进行页面主体的渲染,最后进行布局页面的渲染,将前面渲染产生的文本信息填入到布局文件中所定义的各个坑中,最后将整体渲染产生的内容写入到 HttpContext.Response.Output 中去,完成对请求的响应。

相关文章: