Fork me on GitHub
ASP.NET MVC 2.0添加Razor模板引擎 (on .NET4)

根据ScottGu的博客记述(http://weblogs.asp.net/scottgu/archive/2010/07/02/introducing-razor.aspx),在未来不久将会发布一个ASP.NET MVC 3.0的Preview版本,在这个版本中可以使用多个内置的模板引擎,以它发布出来的截图来看,其中包括NHaml,Spark以及微软刚刚发布的ASP.NET Web Pages(Razor)。 ASP.NET Web Pages包含在Web Matrix中,提供了一种新的模板模式,其扩展名为 .vbhtml/.cshtml,可以使用类似以下语法来做视图显示:

@{var i = 11;}@(i+1)<br>@if (i%2==1){<p>true</p>}else{<p>false</p>}

输出结果为:

12<br> <p>true</p>

在不久之后Ms还会对此提供Visual Studio 高亮及智能感知支持。

这种模板如此简捷,如果能用在现有的ASP.NET MVC 2.0上做为一个模板引擎是不错的。

首先我们要安装ASP.NET Web Pages,下载地址:http://bbs.eice.com.cn/showtopic-409.aspx ,当然直接安装WebMatrix也是可以。

安装之后在IIS中就会添加对cshtml及vbhtml的支持。

安装后程序集文件会被复制到Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies目录下。

其中包括

Microsoft.Data.dllMicrosoft.Web.Infrastructure.dllMicrosoft.Webpages.Compilation.dllMicrosoft.Webpages.Configuration.dllMicrosoft.Webpages.dllMicrosoft.Webpages.Helpers.dllMicrosoft.Webpages.Helpers.Toolkit.dll

下面我们就动手对ASP.NET MVC程序添加一个Razor的模板引擎:

首先建立一个ASP.NET MVC的项目,然后对其中的Web.Config的system.web/compilation/assemblies节点上添加内容:

<add assembly="Microsoft.WebPages.Compilation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/><add assembly="Microsoft.Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/><add assembly="Microsoft.WebPages.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/><add assembly="Microsoft.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>


并对system.web/compilation/buildProviders添加内容:

<add extension=".cshtml" type="Microsoft.WebPages.Compilation.InlinePageBuildProvider" />

 

并引用相应的

Microsoft.Data.dll Microsoft.Webpages.dll Microsoft.Webpages.Helpers.dll Microsoft.Webpages.Compilation.dll 

几个文件

准备工作做好了,下面就来实现相应的模板引擎,我们先来实现一个IView对象:

public class WebPageView : IView    {        public WebPageView(string viewPath)            : this(viewPath, null)        {        }        public WebPageView(string viewPath, string masterPath)        {            if (string.IsNullOrEmpty(viewPath))            {                throw new ArgumentException("viewPath can't null", "viewPath");            }            this.ViewPath = viewPath;            this.MasterPath = masterPath ?? string.Empty;        }        public virtual void Render(ViewContext viewContext, TextWriter writer)        {            if (viewContext == null)            {                throw new ArgumentNullException("viewContext");            }            WebPage page = WebPage.CreateInstanceFromVirtualPath(this.ViewPath);//load cshtml file            if (page == null)            {                throw new InvalidOperationException("cshtml file not exists");            }            else            {                this.RenderViewPage(viewContext, page);            }        }        private void RenderViewPage(ViewContext context, WebPage page)        {            if (!string.IsNullOrEmpty(this.MasterPath))            {                page.LayoutPage = this.MasterPath;            }            page.VirtualPath = this.ViewPath;            page.ExecutePageHierarchy(CreatePageContext(context)                , context.HttpContext.Response.Output, null);            //execute cshtml file        }        internal static WebPageContext CreatePageContext(ViewContext content)        {            var pc = new WebPageContext();            var t = pc.GetType();            t.InvokeMember("HttpContext", BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Instance                , null, pc, new[] { content.HttpContext });            t.InvokeMember("ViewContext", BindingFlags.SetProperty                | BindingFlags.NonPublic | BindingFlags.Instance                , null, pc, new[] { content });            return pc;        }        /// <summary>Gets or sets the master path.</summary>        /// <returns>The master path.</returns>        public string MasterPath { get; private set; }        /// <summary>Gets or sets the view path.</summary>        /// <returns>The view path.</returns>        public string ViewPath { get; private set; }    }

 

然后我们再来实现一个IViewEngine对象:

    /// <summary>    /// WebPage View Engine    /// </summary>    class WebPageEngine : VirtualPathProviderViewEngine    {               public WebPageEngine()        {            // how to find the template path            base.MasterLocationFormats = new string[] {                 "~/Views/{1}/{0}.cshtml",                 "~/Views/Shared/{0}.cshtml"             };            base.AreaMasterLocationFormats = new string[] {                 "~/Areas/{2}/Views/{1}/{0}.cshtml",                 "~/Areas/{2}/Views/Shared/{0}.cshtml"            };            base.ViewLocationFormats = new string[] {                 "~/Views/{1}/{0}.cshtml",                "~/Views/Shared/{0}.cshtml"            };            base.AreaViewLocationFormats = new string[] {                 "~/Areas/{2}/Views/{1}/{0}.cshtml",                 "~/Areas/{2}/Views/Shared/{0}.cshtml"             };            base.PartialViewLocationFormats = base.ViewLocationFormats;            base.AreaPartialViewLocationFormats = base.AreaViewLocationFormats;        }        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)        {            return new WebPageView(partialPath, null);        }        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)        {            return new WebPageView(viewPath, masterPath);        }    }

 

这样我们就实现了Razor的模板引擎了,我们只要在Global.asax中将模板引擎添加进去:

        protected void Application_Start()        {            AreaRegistration.RegisterAllAreas();            RegisterRoutes(RouteTable.Routes);            ViewEngines.Engines.Clear();            ViewEngines.Engines.Add(new WebPageEngine());        }

 

并且将Global的基类改为WebPageHttpApplication:

public class MvcApplication : WebPageHttpApplication{//...}

这样整个程序就可以工作了

我们在Views/Home下添加一个Index.cshtml:

@Html.ActionLink("Home","index","User")<br>@ViewData["Message"]

 

这样在我们访问/Home/Index的时候就可以得到ASP.NET MVC默认工程的HomeController.Index所生成的页面了:

<a href="/User">Home</a>
<br>
欢迎使用 ASP.NET MVC!

 

可见在这个模板引擎中,先天对ASP.NET MVC有良好的支持,本身已经集成了Helper、ViewData等诸多ASP.NET MVC的特性。

让我们期待ASP.NET MVC 3.0及Razor对VS的支持吧

8189E6B8-FBE4-4F01-8F9F-5687C0EA9F59

  1. Asp.net Mvc Framework 系列
  2. Ado.net Entity Framework系列
 我们知道页面引擎尤其是Razor的核心是如何对页面上的代码的解析。

而其中的核心的是一个语法解析树中的定位应该改变也算是非常频繁的操作。

对于其中关键的LocateOwner函数真的令我疑惑。

 

简单说明一下Razor中的语法树。Razor源码中是这样定义的。

image

 

 

基本机构,由上图可以知道。

Block就是一个集合,可以看成是一篇文章或一个句子,span就是具体表达的一个小块。

 

其中Block 提供了一个为'变动'提供定位span的方法。

 

/// <summary> 
      
/// 定位改变所属的 块 
      
/// </summary> 
      
/// <param name="change"></param> 
      
/// <returns></returns> 
      public Span LocateOwner(TextChange change) { 
          
// Ask each child recursively 
          Span owner = null;
          
foreach (SyntaxTreeNode element in Children) { 
              Span span 
= element as Span;
              
if (span == null) { 
                  owner 
= ((Block)element).LocateOwner(change);//如果是块则递归往下找 
              } 
              
else { 
                  
if (change.Position < span.Start.AbsoluteIndex) {//超出-属于span 
                      
// Early escape for cases where changes overlap multiple spans 
                      
// In those cases, the span will return false, and we don't want to search the whole tree 
                      
// So if the current span starts after the change, we know we've searched as far as we need to 
                      break
                  } 
                  owner 
= span.OwnsChange(change) ? span : owner;//deph 
              }
              
if (owner != null) {//是否已经得到答案 
                  break
              } 
          }
          
return owner; 
      }
//再给出span.OwnsChange(change) 的源码

/// <summary> 
      
/// Determines if the specified change belongs to this span. 
      
/// </summary> 
      
/// <remarks> 
      
/// Used for Partial Parsing to identify which span to augment with the specified change. 
      
/// Some changes have no owner, because they overlap multiple spans. 
      
/// Also, just because a span owns a change, doesn't mean it can accept it 
      
/// </remarks> 
      public virtual bool OwnsChange(TextChange change) { 
          
int changeOldEnd = change.Position + change.OldLength; 
          
return change.Position >= Start.AbsoluteIndex && 
                 (changeOldEnd 
< EndIndex || (changeOldEnd == EndIndex && CanGrow)); 
      }

 

 

这里很明显可以看到作者采用深度优先的策略,这不是很奇怪吗?为什么不采用广度优先?

不是更容易定位吗?难道我又错了?想到半夜都没想通。大家说说看。

 

posted on 2010-12-19 10:00  HackerVirus  阅读(264)  评论(0编辑  收藏  举报