利用Razor在ASP.NET MVC中的實現,自訂視圖引擎架構(1)

來源:互聯網
上載者:User

ASP.NET MVC3開始使用Razor作為其視圖引擎,取代了原來ASP.NET Web Form引擎。筆者最近研究了一下MVC3對Razor的實現,從中找到一個切入點,能夠讓我們自訂基於Razor文法的視圖解析引擎。在項目裡面可以用於諸如郵件模板定製等方面。目前,只是一個demo版本,還在進一步完善中。CodePlex : http://codeof.codeplex.com/SourceControl/list/changesets 其中的RazorEx

先來看看效果:

假設有一個模板檔案Action1.cshtml如下:

@{string str = "Hello world!";}<html><head>@TemplateData["Title"]</head><body><h1>@str</h1>    <table>    @foreach (var s in TemplateData["Students"] as IEnumerable<RazorLab.Student>)    {        <tr><td>@s.ID</td><td>@s.Name</td></tr>    }    </table></body></html>

編寫C#代碼如下:

    public class TestController : TemplateController    {        public ActionResult Action1()        {            TemplateData["Title"] = "Hello";            TemplateData["Students"] = new List<Student> {                 new Student{ID = 0 ,Name = "Parker Zhou"},                new Student{ID = 1 ,Name = "Sue Kuang"}            };            return Template(@"D:\Project\C#\MyMvc\RazorLab\Template\Test\Action1.cshtml");        }    }

最終得到的Html如下:

<html><head>Hello</head><body><h1>Hello world!</h1>    <table>        <tr><td>0</td><td>Parker Zhou</td></tr>        <tr><td>1</td><td>Sue Kuang</td></tr>    </table></body></html>

我設計了一個類似MVC的模式,使使用者可以通過Controller向View中傳遞資料,利用Razor解析模板,並填入資料。

 

原理其實很簡單,類似ASP.NET的做法,把模板讀入後解析成類,再和靜態基類一起動態編譯成dll,反射其中的代碼,最後輸出Html。在這個過程中,反射自然不用多說,關鍵是如何解析和動態編譯,這篇我將介紹如何利用微軟的源碼來完成解析。由於我自己代碼還沒有完善,還在單元測試階段,所以先不發上來獻醜了。

 

System.Web.Razor

在MVC3的源碼中,在這裡要關注的是System.Web.Razor這個dll。

它用C#的方式實現了Razor的解析並能產生對應的編譯單元。所謂編譯單元是.NET中的一個類CodeCompileUnit,這個類以CodeDom的方式儲存了源碼結構,可以被用於產生代碼,或者動態編譯。

 

System.Web.Razor.RazorTemplateEngine

在這個Project下最重要的類是System.Web.Razor.RazorTemplateEngine,這也是我們能夠直接利用的類。其中GenerateCode方法能將讀入的模板解析成編譯單元,它有多個重載。下面是Action1.cshtml經過解析後產生的類。其中類名,基類名,名字空間,引用的名字空間等是可以自訂的:

namespace @__TemplatePage.Namespace{    using RazorTemplateEngine;    using System.Collections.Generic;    public class @__TemplateInherit : @__TemplatePage    {#line hidden        public @__TemplateInherit()        {        }        public override void Execute()        {            string str = "Hello world!";            WriteLiteral("\r\n<html>\r\n<head>");            Write(TemplateData["Title"]);            WriteLiteral("</head>\r\n<body>\r\n\t<h1>");            Write(str);            WriteLiteral("</h1>\r\n    <table>\r\n");            foreach (var s in TemplateData["Students"] as IEnumerable<RazorLab.Student>)            {                WriteLiteral("        <tr><td>");                Write(s.ID);                WriteLiteral("</td><td>");                Write(s.Name);                WriteLiteral("</td></tr>\r\n");            }            WriteLiteral("    </table>\r\n</body>\r\n</html>");        }    }}

產生的C#代碼實際上十分容易理解。上述C#代碼可以通過CSharpCodeProvider從CodeCompileUnit得到。(順便提一下,CSharpCodeProvider只能從CodeCompileUnit得到Code,但反過來沒有實現!我查了不少資料都沒有,有興趣要結合NRefactory實現一下)可以想象,我們要做的就是實現一個它的基類@__TemplatePage ,實現其中的TemplateData,WriteLiteral,Write,Execute等,使得在之後的編譯中順利編譯成功。下面是我對基類的實現:

using System;using System.Collections.Generic;using System.Text;namespace RazorTemplateEngine{    /// <summary>    /// This is the base class which the dynamic generated class will inherit from,    /// and the TemplatePageRazorHost define the class name, see TemplatePageRazorHost.DefaultBaseClass    /// for more infomation    /// </summary>    public class __TemplatePage    {        /// <summary>        /// Store the parse result        /// </summary>        private StringBuilder resultBuilder = new StringBuilder();        /// <summary>        /// Store the data passed from controller        /// </summary>        private Dictionary<string, object> templateData = new Dictionary<string, object>();        public StringBuilder ParseResult        {            get { return resultBuilder; }        }        public Dictionary<string, object> TemplateData        {            get { return templateData; }            set { templateData = value; }        }        /// <summary>        /// override  by the dymanic generated class, the method name is defined in         /// GeneratedClassContext.DefaultExecuteMethodName in System.Web.Razor        /// </summary>        public virtual void Execute() { }        /// <summary>        /// implement method in the dymanic generated class , the method name is defined in         /// GeneratedClassContext.DefaultWriteLiteralMethodName in System.Web.Razor        /// </summary>        /// <param name="literal"></param>        public virtual void WriteLiteral(string literal)        {            resultBuilder.Append(literal);        }        /// <summary>        /// implement method in the dymanic generated class , the method name is defined in         /// GeneratedClassContext.DefaultWriteMethodName in System.Web.Razor        /// </summary>        /// <param name="obj"></param>        public virtual void Write(object obj)        {            resultBuilder.Append(obj.ToString());        }    }}

 

System.Web.Razor.RazorEngineHost

對於RazorTemplateEngine,產生類名,基類名,名字空間,引用的名字空間等都是有預設值,但我們可以改變這種預設設定,通過RazorEngineHost這個類,這個類中的許多屬性都是virtual的,可以通過繼承的方式override,這些屬性可以改變RazorTemplateEngine的行為。因此,我們要做的就是實現一個繼承自RazorEngineHost的類,重寫其中必要的屬性,以實現上述的自訂行為。最後RazorEngineHostPostProcessGeneratedCode方法將在RazorTemplateEngine.GenerateCode方法返回結果之後,提供一個再次修改CodeDom的機會,比如加一些額外的名字空間引用。

有了上面的理解,我們要做到其實只剩下下面的範例程式碼了:

實現RazorEngineHost的一個繼承:

public class TestRazorEnginHost : RazorEngineHost    {        public TestRazorEnginHost() : base(new CSharpRazorCodeLanguage())        {        }        public override string DefaultBaseClass        {            get            {                return "PageBase";            }            set            {                base.DefaultBaseClass = value;            }        }        public override string DefaultClassName        {            get            {                return "PageInherit";            }            set            {                base.DefaultClassName = value;            }        }        public override void PostProcessGeneratedCode(System.CodeDom.CodeCompileUnit codeCompileUnit, System.CodeDom.CodeNamespace generatedNamespace, System.CodeDom.CodeTypeDeclaration generatedClass, System.CodeDom.CodeMemberMethod executeMethod)        {            base.PostProcessGeneratedCode(codeCompileUnit, generatedNamespace, generatedClass, executeMethod);            generatedNamespace.Imports.Add(new CodeNamespaceImport("RazorLab"));        }    }

下面的測試代碼用於把一個基於Razor文法的模板C:\Test.cshtml變成C#代碼:

TestRazorEnginHost host = new TestRazorEnginHost();            System.Web.Razor.RazorTemplateEngine rte = new System.Web.Razor.RazorTemplateEngine(host);             FileStream fs = new FileStream(@"C:\Test.cshtml",FileMode.Open);            StreamReader sr = new StreamReader(fs);            var codeDomWrap = rte.GenerateCode(sr);            CSharpCodeProvider provider = new CSharpCodeProvider();            CodeGeneratorOptions options = new CodeGeneratorOptions();            options.BlankLinesBetweenMembers = false;            options.IndentString = "\t";            StringWriter sw = new StringWriter();            string code = string.Empty;            try            {                provider.GenerateCodeFromCompileUnit(codeDomWrap.GeneratedCode, sw, options);                sw.Flush();                code = sw.GetStringBuilder().ToString();                Debug.WriteLine(code);            }            catch { }            finally            {                sw.Close();            }

 

目前,對於模板嵌套,強型別綁定等MVC架構特有支援的功能還沒有時間仔細研究。相信如果這個思路投入生產的話,這樣的需求應該是會有的。過幾天,我把代碼放到CodePlex上去,有興趣的同仁可以聯絡我,畢竟一個人的力量是有限的。下篇,我將介紹如何動態編譯,並把資料填入模板中。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.