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

ASP.NET MVC的Razor引擎:View编译原理

标签:
Java

通过.cshtml或者.vbhtml文件定义的View能够被执行,必须先被编译成存在于某个程序集的类型,ASP.NET MVC采用动态编译的方式对View文件实施编译。当我们在对ASP.NET MVC进行部署的时候,需要对.cshtml或者.vbhtml文件进行打包。针对某个View的第一次访问会触发针对它的编译,一个View对应着一个类型。我们可以对.cshtml或者.vbhtml进行修改,View文件修改后的第一次访问将会导致View的再一次编译。和ASP.NET 传统的编译方式一样,针对View的编译默认是基于目录的,也就是说同一个目录下的多个View文件被编译到同一个程序集中。[本文已经同步到《How ASP.NET MVC Works?》中]

为了让读者对ASP.NET MVC对View文件的编译机制具有一个深刻的认识,我们通过一个简单的实例来确定View文件最终都被编译成什么类型,所在的程序集又是哪一个。我们在一个ASP.NET MVC应用中为HtmlHelper定义了如下一个扩展方法ListViewAssemblies,该方法用于获取当前被加载的包含View类型的程序集(程序集名称以“App_Web_”为前缀)。
   1: public static class HtmlHelperExtensions

   

   2: {

   

   3:     public static MvcHtmlString ListViewAssemblies(this HtmlHelper helper)

   

   4:     {

   

   5:         TagBuilder ul = new TagBuilder("ul");

   

   6:         foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a=>a.FullName.StartsWith("App_Web_")))

   

   7:         {

   

   8:             TagBuilder li = new TagBuilder("li");

   

   9:             li.InnerHtml = assembly.FullName;

   

  10:             ul.InnerHtml+= li.ToString();

   

  11:         }

   

  12:         return new MvcHtmlString(ul.ToString());

   

  13:     }

   

  14: }

然后我们定义了如下两个Controller类型(FooController和BarController),它们之中各自定义了两个Action方法Action1和Action2。

   1: public class FooController : Controller

   

   2: {

   

   3:     public ActionResult Action1()

   

   4:     {

   

   5:         return View();

   

   6:     }

   

   7:     public ActionResult Action2()

   

   8:     {

   

   9:         return View();

   

  10:     }

   

  11: }

   

  12:

   

  13: public class BarController : Controller

   

  14: {

   

  15:     public ActionResult Action1()

   

  16:     {

   

  17:         return View();

   

  18:     }

   

  19:     public ActionResult Action2()

   

  20:     {

   

  21:         return View();

   

  22:     }

   

  23: }

接下来我们为定义在FooController和BarController的四个Action创建对应的View(对应文件路为:“~/Views/Foo/Action1.cshtml”、“~/Views/Foo/Action2.cshtml”、“~/Views/Bar/Action1.cshtml”和“~/Views/Bar/Action2.cshtml”)。它们具有如下相同的定义,我们在View中显示自身的类型和当前加载的基于View的程序集。

   1: <div>当前View类型:@this.GetType().AssemblyQualifiedName</div>

   

   2: <div>当前加载的View程序集:</div>

   

   3: @Html.ListViewAssemblies()

现在运行我们的程序并在浏览器中通过输入相应的地址“依次”(“Foo/Action1”、“Foo/Action2”、“Bar/Action1”和“Bar/Action2”)访问定义在FooController和BarController的四个Action,四次访问得到的输出结果下图所示。


输出结果至少可以反映三个问题:

  • ASP.NET MVC对View文件进行动态编译生成的类型名称基于View文件的虚拟路径(比如文件路径为“~/Views/Foo/Action1.cshtml”的View对应的类型为“ASP._Page_Views_foo_Action1_cshtml”)。

  • ASP.NET MVC是按照目录进行编译的(“~/Views/Foo/”下的两个View文件最终都被编译到程序集“App_Web_j04xtjsy”中)。

  • 程序集按需加载,即第一次访问“~/View/Foo/”目录下的View并不会加载针对“~/View/Bar/”目录的程序集(实际上此时该程序集尚未生成)。

我们可以通过BuildManager类型的静态方法GetCompiledType和GetCompiledAssembly(如下面的代码片断所示)根据View文件的虚拟路径得到对应的类型和程序集。

   1: public sealed class BuildManager

   

   2: {

   

   3:     //其他成员

   

   4:     public static Type GetCompiledType(string virtualPath);

   

   5:     public static Assembly GetCompiledAssembly(string virtualPath);

   

   6: }

在现有演示实例的基础上我们创建了如下一个HomeController,默认的Action方法Index中通过调用BuildManager的静态方法GetCompiledType得到并呈现出四个View文件对应的类型名称。

   1: public class HomeController : Controller

   

   2: {

   

   3:     public void Index()

   

   4:     {

   

   5:         Response.Write(BuildManager.GetCompiledType("~/Views/Foo/Action1.cshtml") + "<br/>");

   

   6:         Response.Write(BuildManager.GetCompiledType("~/Views/Foo/Action2.cshtml") + "<br/>");

   

   7:         Response.Write(BuildManager.GetCompiledType("~/Views/Bar/Action1.cshtml") + "<br/>");

   

   8:         Response.Write(BuildManager.GetCompiledType("~/Views/Bar/Action2.cshtml") + "<br/>");

   

   9:     }

   

  10: }

直接运行我们的程序后会在浏览器中得到代表四个View文件编译类型名称的字符串,具体显示效果下图所示。与上图显示的View类型名称相比较,我们会发现它们是一致的。


上面我们简单地介绍ASP.NET MVC以目录为单位的动态View编译,有人可能会问一个问题:编译生成的程序集存放在哪里?在默认情况下,View文件被动态编译后生成的程序集被临时存放在ASP.NET的临时目录“%WinDir%\Microsoft.NET\Framework\{Version No}\Temporary ASP.NET Files\”下,不过我们可以通过如下所示的配置节<system.web>/<compilation>的tempDirectory 属性来改变动态编译的临时目录。如果我们改变了这个临时目录,需要确保工作进程运行帐号具有访问该目录的权限。
   1: <configuration>

   

   2:   <system.web>

   

   3:     <compilation tempDirectory="c:\Temporary ASP.NET Files\" .../>

   

   4: </configuration>

一个寄宿于IIS的Web应用会在上述的临时目录下创建一个与Web应用同名的子目录,所以我们很容易地找到应用对应的编译目录。但是对于将Visual Studio Development Server作为宿主的Web应用都会编译到名称为Roor的子目录下。如果这样的应用太多,我们往往不太容易准确地找到基于某个应用的编译目录。有时候我们可以根据目录最后的修改时间来找到它,但是我个人倾向于直接删除整个Root目录,然后运行我们的程序后会重新生成一个只包含该应用编译目录的Root目录。

对于上面演示的实例,我将Web应用寄宿于IIS下并且命名为MvcApp,我本机的目录“C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\mvcapp\c4ea0afa\  
a83bd407”下可以找到动态编译的生成的文件。如下图所示,两个View目录(“~/Views/Foo”和“~/Views/Bar”)编译生成的程序集就在这个目录下面。


读者一定很好奇一个View文件通过动态编译最终会生成一个怎样的类型?对应前面演示的实例,我们已经知道了四个View文件编译生成的类型名称和所在的程序集,我们只需要通过Reflector打开对应的程序集就能得到View文件编译类型的定义。如下所示的是View文件“~/Views/Foo/Action.cshtml”编译后生成的ASP._Page_Views_Foo_Action1_cshtml类型的定义。

   1: [Dynamic(new bool[] { false, true })]

   

   2: public class _Page_Views_Foo_Action1_cshtml : WebViewPage<object>

   

   3: {

   

   4:     public override void Execute()

   

   5:     {

   

   6:         this.WriteLiteral("<div>当前View类型:</div>\r\n<div>");

   

   7:         this.Write(base.GetType().AssemblyQualifiedName);

   

   8:         this.WriteLiteral("</div><br/>\r\n<div>当前加载的View程序集:</div>\r\n");

   

   9:         this.Write(base.Html.ListViewAssemblies());

   

  10:     }

   

  11:

   

  12:     protected global_asax ApplicationInstance

   

  13:     {

   

  14:         get

   

  15:         {

   

  16:             return (global_asax) this.Context.ApplicationInstance;

   

  17:         }

   

  18:     }

   

  19: }

 


点击查看更多内容
TA 点赞

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

评论

作者其他优质文章

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

100积分直接送

付费专栏免费学

大额优惠券免费领

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

举报

0/150
提交
取消