为了账号安全,请及时绑定邮箱和手机立即绑定

ASP.NET MVC的Razor引擎:RazorView

标签:
JavaScript

Razor引擎具有两个核心的类型,一个是表示View本身的类型RazorView,另一个则是获取和创建它的RazorViewEngine,我们将用两篇文章对它们分别进行剖析。Razor引擎下的View通过类型RazorView表示,它与表示Web Form引擎View的类型WebFormView都是BuildManagerCompiledView的子类。[本文已经同步到《How ASP.NET MVC Works?》中]

目录      
一、BuildManagerCompiledView      
二、RazorView      
三、实例演示:自定义View模拟RazorView的View呈现机制

一、BuildManagerCompiledView

为了能够清楚地说明实现在BuildManagerCompiledView中的View激活与呈现机制,我们列出了BuildManagerCompiledView中与此相关的内部和受保护的成员。

   1: public abstract class BuildManagerCompiledView : IView

   

   2: {

   

   3:     internal IViewPageActivator ViewPageActivator;

   

   4:

   

   5:     protected BuildManagerCompiledView(ControllerContext controllerContext, string viewPath);

   

   6:     protected BuildManagerCompiledView(ControllerContext controllerContext, string viewPath, IViewPageActivator viewPageActivator);

   

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

   

   8:

   

   9:     public void Render(ViewContext viewContext, TextWriter writer);

   

  10:     protected abstract void RenderView(ViewContext viewContext, TextWriter writer, object instance);

   

  11:

   

  12:     internal IBuildManager     BuildManager { get; set; }

   

  13:     public string              ViewPath { get; protected set; }

   

  14: }

通过《View编译原理》的介绍我们知道采用Razor引擎的View文件(.cshtml或者.vbhtml)最终都会编译成一个WebViewPage类型,所以通过RazorView/WebFormView体现的View的呈现机制最终体现在对WebViewPage对象的激活。我们可以利用BuildManager根据View文件的虚拟路径得到编译后的类型。从名称也可以看出来,BuildManagerCompiledView内部就是利用了BuildManager根据指定的View文件虚拟路径完成对WebViewPage对象激活。

BuildManagerCompiledView的属性ViewPath表示的就是View文件的虚拟路径,该属性在构造函数中被初始化。BuildManagerCompiledView具有三个构造函数,对象本身的构造逻辑体现在内部构造函数上。如上面的代码片断所示,除了将当前ControllerContext和View文件虚拟路径作为构造函数的参数之外,该构造函数还具有额外两个参数,其类型分别是IViewPageActivator和IDependencyResolver。

   1: public interface IViewPageActivator

   

   2: {

   

   3:     object Create(ControllerContext controllerContext, Type type);

   

   4: }

上面的代码片断体现了接口IViewPageActivator的定义。顾名思义,该接口旨在实现对WebViewPage对象的激活,基于类型的对象激活机制实现在Create方法中。BuildManagerCompiledView的构造函数中指定的ViewPageActivator被用于初始化内部字段ViewPageActivator,如果没有通过构造函数显式指定ViewPageActivator对象,默认采用的是一个DefaultViewPageActivator对象。

DefaultViewPageActivator是一个具有如下定义的内部类型,我们可以看到它实际上依赖于一个DependencyResolver对象完成针对WebViewPage对象的激活。这个DependencyResolver对象可以通过构造函数进行显式设置,而默认使用的DependencyResolver对象来源于DependencyResolver类型的静态属性Current。

   1: internal class DefaultViewPageActivator : IViewPageActivator

   

   2: {

   

   3:     private Func<IDependencyResolver> _resolverThunk;

   

   4:     public DefaultViewPageActivator() : this(null)

   

   5:     {}

   

   6:

   

   7:     public DefaultViewPageActivator(IDependencyResolver resolver)

   

   8:     {

   

   9:         Func<IDependencyResolver> func = null;

   

  10:         if (resolver == null)

   

  11:         {

   

  12:             this._resolverThunk = () => DependencyResolver.Current;

   

  13:         }

   

  14:         else

   

  15:         {

   

  16:             if (func == null)

   

  17:             {

   

  18:                 func = () => resolver;

   

  19:             }

   

  20:             this._resolverThunk = func;

   

  21:         }

   

  22:     }

   

  23:

   

  24:     public object Create(ControllerContext controllerContext, Type type)

   

  25:     {

   

  26:         return (this._resolverThunk().GetService(type) ?? Activator.CreateInstance(type));

   

  27:     }

   

  28: }

如果我们在构造BuildManagerCompiledView的时候没有指定具体的ViewPageActivator,那么ASP.NET MVC会根据指定的DependencyResolver来创建默认的DefaultViewPageActivator。如果我们只是根据ControllerContext和View文件虚拟路径来构建BuildManagerCompiledView,最终用于激活WebPageView的实际上就是当前的DependencyResolver。换句话说,我们可以通过注册自定义DependencyResolver的方法以IoC的方式来实现对WebPageView的激活,接下来我们会演示相关的实例。

BuildManagerCompiledView对View的呈现机制其实很简单。它调用BuildManager的静态方法GetCompiledType根据指定的View文件虚拟路径得到编译后的WebPageView类型,然后将该类型交给ViewPageActivator激活一个具体的WebPageView对象,并调用其Render方法完成对View的最终呈现。BuildManagerCompiledView将利用激活的WebPageView对象呈现View的逻辑定义在抽象方法RenderView中,而Render方法仅仅实现了根据View文件虚拟路径对WebPageView的激活,具体的实现可以通过如下的代码片断来体现。

   1: public abstract class BuildManagerCompiledView : IView

   

   2: {

   

   3:     //其他成员

   

   4:     public void Render(ViewContext viewContext, TextWriter writer)

   

   5:     {

   

   6:         Type viewType = BuildManager.GetCompiledType(ViewPath);

   

   7:         object instance = null;

   

   8:         if (null != viewType)

   

   9:         {

   

  10:             //controllerContext字段表示在构造函数中指定的ControllerContext

   

  11:             instance = this.ViewPageActivator.Create(controllerContext, viewType);

   

  12:         }

   

  13:         this.RenderView(viewContext, writer, instance);

   

  14:     }

   

  15:     protected abstract void RenderView(ViewContext viewContext, TextWriter writer, object instance);

   

  16: }


二、RazorView

表示Razor引擎下的View的类型RazorView直接继承BuildManagerCompiledView。如下面的代码片断所示,它具有额外的三个只读属性属性。LayoutPath表示View使用的布局文件的虚拟路径,而RunViewStartPages和ViewStartFileExtensions属性与通过“_ViewStart.cshtml”或“_ViewStart.vbhtml”文件定义的开始页面有关,前者表示是否需要执行开始页面,后者表示开始页面文件的扩展名。对于Razor引擎默认创建的RazorView,RunViewStartPages属性为True(意味着总是会执行开始页面)。ViewStartFileExtensions属性表示的字符串集合包含两个元素“cshtml”和“vbhtml”。

   1: public class RazorView : BuildManagerCompiledView

   

   2: {

   

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

   

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

   

   5:

   

   6:     protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance);

   

   7:

   

   8:     public string               LayoutPath { get; }

   

   9:     public bool                 RunViewStartPages { get; }

   

  10:     public IEnumerable<string>  ViewStartFileExtensions { get; }

   

  11: }

RazorView通过实现RenderView方法最终完成了对View的呈现。方法传入参数instance是通过BuildManagerCompiledView激活的View对象,通过上面的介绍我们知道这是一个空的WebViewPage<TModel>对象(默认情况下是通过默认构造函数创建的)。RazorView在RenderView方法中对其进行初始后调用ExecutePageHierarchy方法将整个页面内容呈现出来。RazorView实现RenderView方法的逻辑基本上可以通过如下的代码片断来表示。

   1: public class RazorView : BuildManagerCompiledView

   

   2: {

   

   3:     //其他成员

   

   4:     protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)

   

   5:     {

   

   6:         WebViewPage page = instance as WebViewPage;

   

   7:         //初始化WebViewPage

   

   8:         Initialize(page);

   

   9:

   

  10:         //得到表示开启页面的WebPageRenderingBase对象

   

  11:         WebPageRenderingBase startPage;

   

  12:         if (this.RunViewStartPages)

   

  13:         {

   

  14:             startPage = StartPage.GetStartPage(page,"_ViewStart",this.ViewStartFileExtensions);

   

  15:         }

   

  16:         HttpContextBase httpContext = viewContext.HttpContext;

   

  17:         page.ExecutePageHierarchy(new WebPageContext(viewContext.HttpContext, null, null), writer, startPage);

   

  18:     }

   

  19: }


三、实例演示:自定义View模拟RazorView的View呈现机制

为了让读者了解RazorView实现 View呈现的本质,我们按照其实现原理自定义一个简单的RazorView类型。我们在一个ASP.NET MVCWeb应用中定义了如下一个表示自定义RazorView的SimpleRazorView类型。SimpleRazorView直接实现了IView接口,在构造函数中初始化的属性ViewPath表示View文件的虚拟路径。

   1: public class SimpleRazorView: IView

   

   2: {

   

   3:     public string ViewPath { get; private set; }

   

   4:

   

   5:     public SimpleRazorView(string viewPath)

   

   6:     {

   

   7:         this.ViewPath = viewPath;

   

   8:     }

   

   9:

   

  10:     public void Render(ViewContext viewContext, TextWriter writer)

   

  11:     {

   

  12:         Type viewType    = BuildManager.GetCompiledType(this.ViewPath);

   

  13:         object instance  = Activator.CreateInstance(viewType);

   

  14:         WebViewPage page = (WebViewPage)instance as WebViewPage;

   

  15:

   

  16:         page.VirtualPath     = this.ViewPath;

   

  17:         page.ViewContext     = viewContext;

   

  18:         page.ViewData        = viewContext.ViewData;

   

  19:         page.InitHelpers();

   

  20:

   

  21:         WebPageContext pageContext     = new WebPageContext(viewContext.HttpContext, null, null);

   

  22:         WebPageRenderingBase startPage = StartPage.GetStartPage(page,"_ViewStart",new string[]{"cshtml","vbhtml"});

   

  23:         page.ExecutePageHierarchy(pageContext, writer, startPage);

   

  24:     }

   

  25: }

在用于呈现View的Render方法中,我们利用BuildManager根据当前View文件的虚拟路径得到动态编译后的类型,然后利用该类型以反射的方式创建一个WebViewPage对象。接下来我们初始化该WebViewPage对象的VirtualPath、VirewContext和ViewData属性,并调用InitHelpers方法对HtmlHelper、UrlHelper和AjaxHelper进行初始化。

SimpleRazorView总是会执行开始页面,所以我们通过调用ViewStartPage的静态方法GetStartPage根据指定的开始页面文件名(_ViewStart)和扩展名列表(cshtml和vbhtml)得到表示开始页面的WebPageRenderingBase对象。最后我们创建WebPageContext对象,并将它和表示开始页面的WebPageRenderingBase对象作为参数调用WebViewPage的ExecutePageHierarchy方法实现对整个页面的呈现。

为了验证SimpleRazorView能够正常完成对View内容的呈现,我们定义了如下一个HomeController。在默认的Action方法Index中,我们创建一个Contact对象作为当前ViewData的Model。然后通过指定View文件的虚拟路径(“~/Views/Home/Index.cshtml”)创建我们自定义的SimpleRazorView对象。最后我们创建ViewContext,并将其作为参数调用SimpleRazorView的Render方法将默认的View呈现出来。

   1: public class HomeController : Controller

   

   2: {

   

   3:     public void Index()

   

   4:     {

   

   5:         ViewData.Model = new Contact {

   

   6:             Name         = "张三",

   

   7:             PhoneNo      = "123456789",

   

   8:             EmailAddress = "zhangsan@gmail.com" };

   

   9:         SimpleRazorView view    = new SimpleRazorView("~/Views/Home/Index.cshtml");

   

  10:         ViewContext viewContext = new ViewContext(ControllerContext, view, ViewData, TempData, Response.Output);

   

  11:         view.Render(viewContext, viewContext.Writer);

   

  12:     }

   

  13: }

   

  14:

   

  15: public class Contact

   

  16: {

   

  17:     [DisplayName("姓名")]

   

  18:     public string Name { get; set; }

   

  19:

   

  20:     [DisplayName("电话号码")]

   

  21:     public string PhoneNo { get; set; }

   

  22:

   

  23:     [DisplayName("电子邮箱地址")]

   

  24:     public string EmailAddress { get; set; }

   

  25: }

我们的View很简单,如下面的代码片断所示,这是一个Model类型为Contact的强类型View,在该View中我们直接调用HtmlHelper<TModel>的扩展方法EditorForModel将作为Model的Contact对象以编辑模式呈现在一个表单之中。

   1: @model Contact

   

   2: @{

   

   3:     ViewBag.Title = Model.Name;

   

   4: }

   

   5:

   

   6: @using (Html.BeginForm())

   

   7: {

   

   8:     @Html.EditorForModel()

   

   9:     <input type="submit" value="保存" />

   

  10: }

为了验证我们自定义的SimpleRazorView对布局文件和_ViewStart页面的支持,我们在“~/Views/Shared/”目录下定义了如下一个名为“_Layout.cshtml”的布局文件。布局文件的设置通过定义在“~/Views/”目录下具有如下定义的“_ViewStart.cshtml”文件来指定。

   1: _Layout.cshtml:

   

   2: <html>

   

   3:     <head>

   

   4:         <title>@ViewBag.Title </title>

   

   5:     </head>

   

   6:     <body>

   

   7:         <h3>编辑联系人信息</h3>

   

   8:         @RenderBody()

   

   9:     </body>

   

  10: </html>

   

  11:

   

  12: _ViewStart.cshtml:

   

  13: @{

   

  14:     Layout = "~/Views/Shared/_Layout.cshtml";

   

  15: }

运行我们的程序后直接会在浏览器中呈现如下图所示的效果,可以看出这和我们直接在Action方法Index方法返回一个ViewResult对象没有本质的区别。


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消