ASP.NET MVC Razor与View渲染

2018 年 4 月 29 日 DotNet

(点击上方蓝字,可快速关注我们)


来源:7m鱼 

cnblogs.com/selimsong/p/8670744.html


对于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又是什么?


ActionResult及ViewResult


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结果默认路径为"%WinDir\Microsoft.NET\Framework\ {Version No}\Temporary ASPNET Files"目录下,以App_Web_开头的程序集中,程序集的名称是根据路径随机生成的。


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


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



全量代码:


using System;

using System.Collections.Generic;

using System.IO;

using System.Linq;

using System.Web;

using System.Web.Compilation;

using System.Web.Mvc;

using System.Web.WebPages;


namespace My_Blog.Controllers

{

    public class ViewRenderController : Controller

    {

        // GET: ViewRender

        public void Index()

        {

            var path = "";

            var viewEngineResult = this.FindView(out path);//查找View

            Render(viewEngineResult, path);//渲染View

        }


        //View的查找,相当于RazorViewEngine

        private ViewEngineResult FindView(out string path)

        {

            var actionName = "Contact";

            var controllerName = "Home";

            var viewLocationFormat = @"~/Views/{1}/{0}.cshtml";

            //根据Controller和Action名称与地址模板组成View相对路径

            path = string.Format(viewLocationFormat, actionName, controllerName);

            //根据文件路径创建RazorView和ViewEngineResult

            var view = new RazorView(this.ControllerContext, path, "", true, null, null);

            return new ViewEngineResult(view, new RazorViewEngine());

        }


        //View的渲染

        private void Render(ViewEngineResult viewEngineResult,string path)

        {

            Type pageType = BuildManager.GetCompiledType(path);//根据对View文件进行编译

            var pageInstance = Activator.CreateInstance(pageType);//创建View文件编译后类型实例

            var webViewPage = this.InitViewPage(pageInstance, viewEngineResult, path);//对实例中相关属性进行初始化

            webViewPage.ExecutePageHierarchy(//完成View的渲染

                new WebPageContext(this.HttpContext, null, null),

                this.HttpContext.Response.Output, null);//startpage设置为null,将不会渲染布局页面

        }


        private WebViewPage InitViewPage(object instance, ViewEngineResult viewEngineResult, string path)

        {

            WebViewPage webViewPage = instance as WebViewPage;


            if (webViewPage == null)

            {

                throw new InvalidOperationException("无效");

            }

            ViewContext viewContext = new ViewContext(this.ControllerContext,

                viewEngineResult.View, 

                this.ViewData, 

                this.TempData, 

                this.HttpContext.Response.Output);

            webViewPage.VirtualPath = path;


            webViewPage.ViewContext = viewContext;

            webViewPage.ViewData = viewContext.ViewData;

            webViewPage.InitHelpers();

            return webViewPage;

        }

    }

}


上面的代码要点如下:


● 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提供的相关机制,可以极大的减少工作量同时也可以让代码变得更加简洁。


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

淘口令复制以下红色内容,再打开手淘即可购买

范品社,使用¥极客T恤¥抢先预览(长按复制整段文案,打开手机淘宝即可进入活动内容)

登录查看更多
0

相关内容

ASP.NET MVC Framework 是微软官方提供的 MVC 模式编写 ASP.NET Web 应用程序的一个框架。它由 Castle 的 MonoRail 而来。已于 2009 年 3 月 19 日正式发布,目前已经历经数个版本。
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
115+阅读 · 2020年5月10日
【强化学习】深度强化学习初学者指南
专知会员服务
178+阅读 · 2019年12月14日
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
从webview到flutter:详解iOS中的Web开发
前端之巅
5+阅读 · 2019年3月24日
去哪儿网开源DNS管理系统OpenDnsdb
运维帮
21+阅读 · 2019年1月22日
教你实现超流行的骨架屏预加载动态效果
IMWeb前端社区
71+阅读 · 2018年11月27日
浅谈浏览器 http 的缓存机制
前端大全
6+阅读 · 2018年1月21日
设计和实现一款轻量级的爬虫框架
架构文摘
13+阅读 · 2018年1月17日
33款可用来抓数据的开源爬虫软件工具 (推荐收藏)
数据科学浅谈
6+阅读 · 2017年7月29日
Factor Graph Attention
Arxiv
6+阅读 · 2019年4月11日
Video-to-Video Synthesis
Arxiv
9+阅读 · 2018年8月20日
Arxiv
6+阅读 · 2018年2月8日
Arxiv
3+阅读 · 2012年11月20日
VIP会员
相关资讯
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
从webview到flutter:详解iOS中的Web开发
前端之巅
5+阅读 · 2019年3月24日
去哪儿网开源DNS管理系统OpenDnsdb
运维帮
21+阅读 · 2019年1月22日
教你实现超流行的骨架屏预加载动态效果
IMWeb前端社区
71+阅读 · 2018年11月27日
浅谈浏览器 http 的缓存机制
前端大全
6+阅读 · 2018年1月21日
设计和实现一款轻量级的爬虫框架
架构文摘
13+阅读 · 2018年1月17日
33款可用来抓数据的开源爬虫软件工具 (推荐收藏)
数据科学浅谈
6+阅读 · 2017年7月29日
相关论文
Factor Graph Attention
Arxiv
6+阅读 · 2019年4月11日
Video-to-Video Synthesis
Arxiv
9+阅读 · 2018年8月20日
Arxiv
6+阅读 · 2018年2月8日
Arxiv
3+阅读 · 2012年11月20日
Top
微信扫码咨询专知VIP会员