对于Web应用来说,它的界面是由浏览器根据HTML代码及其引用的相关资源进行渲染后展示给用户的结果,换句话说Web应用的界面呈现工作是由浏览器完成的,Web应用的原理是通过Http协议从服务器上获取到对应的Html代码以及相关资源,使得浏览器能够完成正确的呈现工作

  ASP.NET MVC作为一个Web应用构建框架View承担了UI显示的功能,在开发过程中View以Action的名称命名,当用户的请求被路由到某一Action方法时,ASP.NET MVC将会根据Action的名称来获取到对应的View文件,将该View文件动态处理后生成最终的Html内容,将内容返回到浏览器进行显示。所以ASP.NET的渲染实际上指的是动态的生成Html代码的过程
  而ASP.NET MVC中action的代码可以简单如下:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染 

  仅需要调用一个View方法就可以将Index这个View显示到用户的浏览器上,那么View方法到底做了什么处理?Razor是什么?Action方法的返回值ActionResult又是什么?
  本文将从以下几个方面来介绍ASP.NET MVC Html代码的生成过程:
  ● ActionResult及ViewResult
  ● View的查找与Razor
    ○ ViewEngineCollection&ViewEngine
    ○ ViewEngineResult
  ● View的编译与激活
  ● View的渲染
  ● 使用示例代码演示View的渲染过程
  ● View的Html Helper与ModelMetadata
  ● 常用的ActionResult
  ● 小结

ActionResult及ViewResult

  在之前的文章《ASP.NET没有魔法——ASP.NET MVC 过滤器(Filter)》中提到过,Action方法是由ActionInvoker完成执行的,Action返回的结果是一个ActionResult类型,Action执行后ActionInvoker又调用了ActionResult的ExecuteResult方法完成特定的操作,相关代码如下所示:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  ActionResult的定义如下,它包含了一个名为ExecuteResult的方法,该方法用来完成对action方法执行结果进行处理:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  回到最初提到的View()方法,该方法定义在Controller中,它的返回值是一个ViewResult类型:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  可以这么说,当Action执行完成后,ASP.NET MVC的View渲染工作是由ViewResult在ExecuteResult方法中完成的,ViewResult的ExecuteResult实现代码如下(注:该代码在ViewResult的基类ViewResultBase中实现):

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  从代码中可以容易的看出,ASP.NET MVC中View的渲染工作主要有四步:
  1. 如果没有指定View的名称,那么默认以Action的名称为View名称。
  2. 根据控制器的上下文查找并获得真实的View对象。
  3. 调用View对象的Render方法将View的内容写到HttpContext的响应信息中,后续将其返回至浏览器。
  4. 释放View对象。

  根据上面的分析View渲染的两个重要步骤就是View对象的查找渲染,其整个过程可参考下图,详细内容将在后续介绍:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

View的查找与Razor

  在ASP.NET MVC中View文件一般放置在项目根目录的Views目录下,以Controller名称为子目录,每一个子目录下保存了以action方法名称命名的View文件:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  ViewResult类型中查找View的代码如下:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  从代码可以看出它是通过一个ViewEngineCollection对象,根据ViewName(默认是actionName)去查找View的,如果找到返回一个ViewEngineResult类型,否则将抛出异常,异常中包含查找的位置:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

   注:从上面的错误信息中可以看到ASP.NET MVC在查找View时,除了匹配了.cshtml和.vbhtml的文件外,还匹配了.aspx和.ascx的文件,后者是Web Form框架的页面文件,这是为什么呢?因为默认情况下ASP.NET MVC中会包含MVC使用的Razor 引擎和Web Form使用的Web Form引擎,所以在纯使用MVC开发的情况下,为了优化性能,一般会通过以下代码将Web Form的引擎删除:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  更多View引擎的内容后续介绍。

ViewEngineCollection&ViewEngine

  在ASP.NET中有一个IViewEngine的接口,它定义了查找和释放View,其定义如下:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  而ASP.NET中实现IViewEngine接口的类型关系如下图:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  从该图中可以得到一下信息:
  ● ASP.NET中有两个最终实现的ViewEngine,分别是Razor和WebForm,MVC应用中使用Razor实现View的渲染。
  ● 它们的基类都是VirtualPathProviderViewEngine,就是说它们都是基于相对路径来管理View的。
  ● 它们的基类都是BuildManagerViewEngine,表面它们都和编译有关(注:在ASP.NET中无论是WebForm还是MVC,都可以在页面上编写代码,而这些代码肯定是不能被浏览器理解的,需要经过编译才能够正常工作)。

  ASP.NET中的ViewEngine被一个名为ViewEngines的集合进行管理,如下图:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  MVC中主要使用的是RazorViewEngine,下图是RazorViewEngine的部分代码:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  从代码中可以看到两个重要信息,第一是“_ViewStart”被硬编码为启动页面,这也是为什么在该页面指定布局的原因,另外在其构造方法中硬编码了各种LocationFormats,它们指定了相应类型页面的搜索路径

  那么上面提到的Razor又是什么呢?Razor是ASP.NET的一种可以将服务器代码嵌入到网页中的标记语言,它由Razor标记、服务器代码(C#/VB)以及Html代码组成。在Html中以@符号开始的内容将会被识别为服务器代码,而Razor将识别这些代码将其渲染为Html代码。更多关于Razor的内容可参考:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor

ViewEngineResult

  ViewEngineResult是ViewEngine对View查找后结果的封装,其定义如下:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  它包含了查找后的结果IView类型以及用于查找的ViewEngine本身,另外还有一个字符串列表用来保存查找该View所遍历过的路径。
  下面是RazorViewEngine用来查找和创建RazorView对象的主要代码,其核心实际就是根据action名称和Controller名称与相应的LocationFormats匹配后查找文件是否存在,如果存在则创建IView实例的过程:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  而这里的IView类型就是RazorView:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

View的编译与激活

  上面介绍了ViewEngine用来查找并获取相应IView对象,那IView是用来做什么的呢?下图是IView的接口定义,它只有一个Render方法,用来将指定的View上下文信息通过指定的TextWriter进行渲染,这实际上就是将View文件的内容处理后,写到Http响应数据的过程:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  ASP.NET中实现IView的类结构如下:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  同样的有两种View分别对象Razor和WebForm,而它们的基类都是BuildManagerCompiledView(被BuildManager编译后的View),而且Render方法也是在基类中实现的,具体代码如下:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  RenderView方法实现在对应的子类中,下图为RazorView的RenderView方法:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  上面代码的核心在于:
  1. 通过BuildManager根据View文件的路径对View文件进行编译,并获得编译后的类型(注:BuildManager是ASP.NET中用于对程序集、页面进行编译的工具)。
  2. 通过激活器创建View编译后的类型,下图是默认使用的激活器,其核心是通过依赖解析器或者Activator来直接创建类型实例。

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  3. 实例化后的对象是一个WebViewPage类型,通过对WebViewPage初始化后(包含起始页的查找)调用WebViewPage的ExecutePageHierarchy方法完成渲染。

  注:WebViewPage是Razor对应的页面类型,WebForm对应的是ViewPage和ViewUserControl。

View的渲染

  上面提到View文件编译后是一个WebViewPage对象,而View的渲染也是由该对象完成的,那么WebViewPage是什么?下图是WebViewPage的定义:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  从中可以看到一些重要的属性如Html、Ajax、Url等这些可以在View里面使用的,有用来生成Html、Url、Ajax的帮助类型,也有如携带了数据用于绑定到View上的Model、TempData、ViewBag、ViewData等类型。
  另外WebViewPage继承至WebPageBase:

   ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  WebPageBase类型里面定义了RenderBody、RenderSection等方法。
  了解了WebPage与WebPageBase之后有没有感觉View文件实际上是WebPage的一个子类型,在View中可以随意使用和调用WebPage和WebPageBase中的属性和方法
  下图是对Contact.cshtml文件编译后的代码:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  从代码中证明了之前的猜想,View文件编译后确实是WebViewPage的子类型,而该类型中的Execute方法是将Html代码以字符串的形式进行了拼接,拼接过程中如果遇到特殊方法的调用则拼接特殊方法的返回值:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  以上代码来自布局文件的编译结果。
  而Execute方法也就是最终ASP.NET MVC进行View渲染的实际方法。WebViewPage的ExecutePageHierarchy是因为MVC中页面可能依赖多个View,如默认情况下页面有StartPage中指定的布局View和内容View,为了保证渲染内容顺序不变ExecutePageHierarchy方法中引入了栈机制(后进先出)。

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  注:BuildManager编译的View结果默认路径为"%WinDir\Microsoft.NET\Framework\ {Version No}\Temporary ASPNET Files"目录下,以App_Web_开头的程序集中,程序集的名称是根据路径随机生成的。

使用示例代码演示View的渲染过程

  下面就用代码的方式来演示View的查找、编译、激活以及渲染的全过程:

  ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

  全量代码:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.IO;
 4 using System.Linq;
 5 using System.Web;
 6 using System.Web.Compilation;
 7 using System.Web.Mvc;
 8 using System.Web.WebPages;
 9 
10 namespace My_Blog.Controllers
11 {
12     public class ViewRenderController : Controller
13     {
14         // GET: ViewRender
15         public void Index()
16         {
17             var path = "";
18             var viewEngineResult = this.FindView(out path);//查找View
19             Render(viewEngineResult, path);//渲染View
20         }
21 
22         //View的查找,相当于RazorViewEngine
23         private ViewEngineResult FindView(out string path)
24         {
25             var actionName = "Contact";
26             var controllerName = "Home";
27             var viewLocationFormat = @"~/Views/{1}/{0}.cshtml";
28             //根据Controller和Action名称与地址模板组成View相对路径
29             path = string.Format(viewLocationFormat, actionName, controllerName);
30             //根据文件路径创建RazorView和ViewEngineResult
31             var view = new RazorView(this.ControllerContext, path, "", true, null, null);
32             return new ViewEngineResult(view, new RazorViewEngine());
33         }
34 
35         //View的渲染
36         private void Render(ViewEngineResult viewEngineResult,string path)
37         {
38             Type pageType = BuildManager.GetCompiledType(path);//根据对View文件进行编译
39             var pageInstance = Activator.CreateInstance(pageType);//创建View文件编译后类型实例
40             var webViewPage = this.InitViewPage(pageInstance, viewEngineResult, path);//对实例中相关属性进行初始化
41             webViewPage.ExecutePageHierarchy(//完成View的渲染
42                 new WebPageContext(this.HttpContext, null, null),
43                 this.HttpContext.Response.Output, null);//startpage设置为null,将不会渲染布局页面
44         }
45 
46         private WebViewPage InitViewPage(object instance, ViewEngineResult viewEngineResult, string path)
47         {
48             WebViewPage webViewPage = instance as WebViewPage;
49 
50             if (webViewPage == null)
51             {
52                 throw new InvalidOperationException("无效");
53             }
54             ViewContext viewContext = new ViewContext(this.ControllerContext,
55                 viewEngineResult.View, 
56                 this.ViewData, 
57                 this.TempData, 
58                 this.HttpContext.Response.Output);
59             webViewPage.VirtualPath = path;
60 
61             webViewPage.ViewContext = viewContext;
62             webViewPage.ViewData = viewContext.ViewData;
63             webViewPage.InitHelpers();
64             return webViewPage;
65         }
66     }
67 }
View Code

相关文章:

  • 2021-08-11
  • 2021-09-05
  • 2021-07-07
  • 2021-12-16
  • 2022-02-28
  • 2021-08-14
  • 2022-01-22
  • 2021-12-11
猜你喜欢
  • 2021-07-06
  • 2021-12-15
  • 2021-09-17
  • 2021-05-27
  • 2021-11-16
  • 2021-05-06
相关资源
相似解决方案