zoukankan      html  css  js  c++  java
  • ASP.NET没有魔法——ASP.NET MVC Razor与View渲染

      对于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的代码可以简单如下:

       

      仅需要调用一个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方法完成特定的操作,相关代码如下所示:

      

      

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

      

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

      

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

      

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

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

      

    View的查找与Razor

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

      

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

      

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

      

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

      

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

    ViewEngineCollection&ViewEngine

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

      

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

      

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

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

      

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

      

      从代码中可以看到两个重要信息,第一是“_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查找后结果的封装,其定义如下:

      

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

      

      而这里的IView类型就是RazorView:

      

    View的编译与激活

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

      

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

      

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

      

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

      

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

      

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

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

    View的渲染

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

      

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

       

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

      

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

      

      

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

      

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

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

      下面就用代码的方式来演示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

      上面的代码要点如下:
      ● FindView方法实际上代表的就是RazorViewEngine,它根据硬编码的View文件搜索模板结合Controller和Action的名称获得View文件的全路径,并创建RazorView和ViewEngineResult对象。
      ● Render则代表了View的编译、激活以及渲染过程。

       运行结果(由于没有布局页面,所以相关的样式和JS都没有被引入):

      

    View的Html Helper与ModelMetadata

      在ASP.NET MVC的View中可以通过一些Helper类型来生成HTML代码,下图为用户注册页面的View代码:

       

      从代码中可以看到ASP.NET MVC并不是完全使用Html来构成页面的,中间有一些通过Html属性(注:Html是WebViewPage类型中的HtmlHelper类型的实例)调用的方法,从方法的名称来看这些方法分别用于生成如数据验证信息、Label标签、文本框以及密码类型文本框等HTML代码。
      ASP.NET MVC提供了一系列的Helper类及其拓展方法,这些Helper类中封装了针对HTML、Ajax(ASP.NET MVC中Ajax Helper的用法可参考:https://www.c-sharpcorner.com/article/Asp-Net-mvc-ajax-helper/)等相关内容的实现,ASP.NET MVC对Html拓展可以分为以下四类:
      1. 用于生成特定标签的拓展,如Form、Input、Label、TextArea、Select等拓展。
      使用方式如下:

      

      将常用的HTML标签进行封装,对于不熟悉Html的开发人员来说ASP.NET MVC提供了一种面向对象编程的方式来对页面进行开发,更重要的是Html方法可以与模型关联,当模型的元数据中有相应的验证特性并且开启了客户端验证时,在渲染标签时会包含相应的验证信息,使用这类标签最大的好处就是可以将关注点全部放在模型上,当模型发生变化时View上不需要做任何的修改

      2. 用于生成Model验证信息的拓展。
      使用方式如下:

      

      ASP.NET MVC中提供了模型验证的机制,当模型验证失败时会有相应的失败信息,而该拓展就是对这些错误信息进行渲染,大大的减少了输出验证信息的工作量。

      3. 根据用途“展示/编辑”生成标签的Display以及Editor拓展。
      使用方式如下(注:DisplayFor是用于显示指定模型属性中的值,如果要显示对应模型属性的名称可用DisplayNameFor):

      

      Display与Editor是根据模型的数据类型来判断如何对模型进行展示。ASP.NET MVC中为Display和Editor提供了一些基础类型的默认模板实现分别通过内部静态类型DefaultEditorTemplates和DefaultDisplayTemplates进行存储,下面是bool类型模板代码:

      

      

      在提供默认模板的同时ASP.NET MVC也提供了自定义模板的机制,可以分别使用DisplayAttribute与UIHintAttribute对特定属性指定渲染模板,如何自定义Display和Editor模板可参考:http://www.growingwiththeweb.com/2012/12/aspnet-mvc-display-and-editor-templates.html

      4. Partial拓展。
      使用方法(注:第一个参数是Partial View的名称,默认情况下Partial View文件存储于Views/Shared目录下,如果文件不在这个目录下需要在参数中体现具体目录):

      

      Partial是ASP.NET MVC中用于将可重用Html进行分离的机制,并且Partial是可以访问数据的,就是说通过parital分离的Html代码,可以根据传入的数据来动态生成Html代码,更多关于Partial View的内容可参考.Net Core的文档:https://docs.microsoft.com/en-us/aspnet/core/mvc/views/partial

      5. Child Action拓展。
      使用方法:

      

      Child Action类似于Partial View,它也是将可重用的部分进行分离,但Partial View更侧重于关注Html代码重用,Child Action还包含了后端逻辑的重用。如购物网站的购物车,它可能出现在任意的页面中,但首页的逻辑、模型与购物车就可能没有任何关系,此时就可以使用Child Action。

    常用的ActionResult

      前面提到过ASP.NET MVC的页面渲染工作实际上是由一个继承至ActionResult的ViewResult对象完成的,ActionResult实际上是ASP.NET MVC中的一个抽象,代表了所有逻辑执行后的结果,而ViewResult是将结果面向人的,所以返回了Html让浏览器显示给人看,除了ViewResult之外还有一些常用的ActionResult如下:
      ● ContentResult:用于将字符串返回到客户端,在Action方法中调用Content方法返回。
      ● FileStreamResult:用于将文件返回到客户端,在Action方法中调用File方法(有多个重载)返回。
      ● HttpNotFoundResult:用于返回HTTP未找到状态,在Action方法中调用HttpNotFound方法返回。
      ● JavaScriptResult:用于将JavaScript返回到客户端并执行,在Action方法中调用JavaScript方法返回。
      ● JsonResult:用于将Json数据返回到客户端,在Action方法中调用Json方法返回。
      ● PartialViewResult:用于渲染partial页面,在Action方法中调用PartialView方法返回。
      ● RedirectResult:用于重定向,在Action方法中调用Redirect方法传入需要重定向的Url进行重定向操作。
      ● RedirectToRouteResult:用于路由重定向,在Action方法中调用RedirectToAction方法重定向到指定的Action。
      ● EmptyResult:返回空,在Action方法中返回Null或将Action方法的返回值设为Void即可。

    小结

      本文介绍了ASP.NET MVC如何在Action方法执行时通过对ActionResult(ViewResult)的执行完成View文件的查找、编译以及渲染的过程,除了ViewResult之外ASP.NET MVC还提供了其它类型的ActionResult如Json、File等,使用这些结果可以创建简单的Web API以及文件下载等功能,另外MVC通过Html Helper类型对View进行了拓展,在开发View时可以最大程度的对View的Html代码和逻辑进行重用,同时也将View与Model(特指ViewModel)进行关联,在开发时可以将关注点放在Model上,无需担心Model修改后View代码的修改。合理的使用View提供的相关机制,可以极大的减少工作量同时也可以让代码变得更加简洁。

    参考:
      https://zhuanlan.zhihu.com/p/29418126
      https://www.codeproject.com/Articles/787320/An-Absolute-Beginners-Tutorial-on-HTML-Helpers-and
      https://www.c-sharpcorner.com/article/Asp-Net-mvc-ajax-helper/
      http://www.growingwiththeweb.com/2012/12/aspnet-mvc-display-and-editor-templates.html
      https://stackoverflow.com/questions/5037580/asp-net-mvc-3-partial-vs-display-template-vs-editor-template
      https://weblogs.asp.net/scottgu/introducing-razor
      https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor

     本文链接:http://www.cnblogs.com/selimsong/p/8670744.html 

    ASP.NET没有魔法——目录

  • 相关阅读:
    Java基础——clone()方法浅析
    Unity shader error: “Too many texture interpolators would be used for ForwardBase pass”
    ar 解压一个.a文件报错: xxx.a is a fat file (use libtool(1) or lipo(1) and ar(1) on it)
    How to set up "lldb_codesign" certificate!
    Unity-iPhone has Conflicting Provisioning Settings
    ETC1/DXT1 compressed textures are not supported when publishing to iPhone
    Xcode 提交APP时遇到 “has one iOS Distribution certificate but its private key is not installed”
    XCode iOS之应用程序标题本地化
    苹果电脑(Mac mini或Macbook或iMac)恢复出厂设置
    Unity 4.7 导出工程在XCode10.1上编译报错
  • 原文地址:https://www.cnblogs.com/selimsong/p/8670744.html
Copyright © 2011-2022 走看看