標籤:
一、簡介
在MVC以外的情境中,我們往往需要完成一些模板引擎產生代碼或頁面的工作;在以前我們一般常用的有Razor、NVeocity、VTemplate。雖然所有的模板系統都具有一些共同特徵,但 Razor卻和我們前面討論的二種視圖引擎截然不同。不同於其它視圖引擎,Razor在使用XML代 碼方面沒有走得那麼極端。它也不完全類似於ASPX,因為它把那些比較笨重的預留位置替換成@符號接運算式或者普通的控制塊。因為不需要特殊的結束標記,所 以Razor最終的代碼很簡練。
本篇介紹的主角是Razor,在非Core的版本中,我們常用開源的RazorEngine來解決我們的問題;但是它卻沒有對應.NET Core的版本。我們也只要自己動手來完成一個支援.NET Core的“模板引擎”版本。
一般情況下使用Razor作為視圖引擎要實現如下步驟:
(1)讀模數板檔案 -> (2)產生Raozr的C#代碼 -> (3)使用Roslyn編譯代碼產生程式集 -> (4)動態載入程式集 -> (5)反射調用
二、非Mvc中使用Razor
我們一般在使用Razor時都是在ASP.NET MVC中使用.cshtml來作為模板,由ASP.NET MVC的視圖引擎(ViewEngine)來產生頁面的代碼的,總之,這裡想說的是,模板引擎是獨立的,它們甚至是獨立的項目,由不同的公司和組織來開發。這次我們要在非Mvc中使用Raozr;首先我們要“脫離”Mvc的環境。
這裡我們只在.NET Core程式中引用微軟Raozr部分的程式集Microsoft.AspNetCore.Razor 1.0版本,這個程式集負責將模板產生出C#代碼。
1.Project.json添加引用
"dependencies": { "Microsoft.AspNetCore.Razor": "1.0.0" "NETStandard.Library": "1.6.0" }2.模板產生代碼
如下是摘錄的YOYOFx架構中的一段代碼,因為我們要產生代碼時一般需要傳入Model資料,這時需要Model Type組織代碼時,要將泛型的情況考慮進去 。這裡的RazorViewTemplate是一個模板基類,這裡包含了模板中調用的外部方法,我們常用到的如HtmlHelper、Render、Url、Raw等方法或類都是通過這個櫃頂模板定義的,RazorViewTemplate是一個自訂類不需要繼承其它類型,如果想擴充模板中使用的方法,只需要在這個類中加入即可。
public class CodeGenerateService { public GeneratorResults Generate(Type modelType,string template) { //準備臨時類名,讀模數板檔案和Razor代碼產生器 string templateType = "YOYO.AspNetCore.ViewEngine.Razor.RazorViewTemplate"; string templateTypeName = modelType != null ? string.Format(templateType + @"<{0}>", modelType.FullName) : templateType; var class_name = "c" + Guid.NewGuid().ToString("N"); var host = new RazorEngineHost(new CSharpRazorCodeLanguage(), () => new HtmlMarkupParser()) { DefaultBaseClass = templateTypeName, DefaultClassName = class_name, DefaultNamespace = "YOYO.AspNetCore.ViewEngine.Razor", GeneratedClassContext = new GeneratedClassContext("Execute", "Write", "WriteLiteral", "WriteTo", "WriteLiteralTo", "RazorViewTemplate.Dynamic", new GeneratedTagHelperContext()) }; host.NamespaceImports.Add("System"); host.NamespaceImports.Add("System.Dynamic"); host.NamespaceImports.Add("System.Linq"); host.NamespaceImports.Add("System.Collections.Generic"); var engine = new RazorTemplateEngine(host); return engine.GenerateCode(new StringReader(template)); } }
通過以上代碼得到GeneratorResults類型的結果,從而可以得知產生過程是否成功,錯誤在位置等資訊。最後通過GeneratedCode屬性,得到產生好的C#代碼。
3.編譯模板
一般Razor的C#代碼產生後,都是通過CodeDom來產生和編譯代碼的;.NET開源後,我們又多了一個強大的選擇Roslyn , Roslyn也是支援.NET Core的,並且在整個.NET平台中,扮演著非常重要的角色,小到這種視圖代碼編譯,大到整個項目的編譯情境都有Roslyn的身影。微軟最新開源的Visual Studio Code中C#外掛程式,OmniSharp就是通過Roslyn來對項目和編輯器提供支援的。
摘錄YOYOFx代碼如下:
public Type Compile(string compilationContent){ var assemblyName = Path.GetRandomFileName(); var sourceText = SourceText.From(compilationContent, Encoding.UTF8); var syntaxTree = CSharpSyntaxTree.ParseText( sourceText, path: assemblyName, options: new CSharpParseOptions()); var compilation = CSharpCompilation.Create(assemblyName, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), syntaxTrees: new[] { syntaxTree }, references: ApplicationReferences ); using (var assemblyStream = new MemoryStream())
{ var result = compilation.Emit(assemblyStream, options: new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb)); this.CompileResult = new CompileResult(){ Success = result.Success, Errors = result.Diagnostics.Select(d => d.ToString()).ToList()}; if (!result.Success){ if (!compilation.References.Any() && !ApplicationReferences.Any()) throw new InvalidOperationException("project.json preserveCompilationContext"); return null; } var templateType = LoadTypeForAssemblyStream(assemblyStream, null); return templateType; }}
在代碼中可以通過CompileResult得到相應的編譯錯誤資訊,同樣包括錯誤的資訊和具體錯誤所在的行。
其中注意的是LoadTypeForAssemblyStream方法,因為在.NET Core中動態載入程式集的方式跟以前有所不同AppDomain的概念現在已經消失,所以要在.NET Core動態載入程式集要使用,命名空間System.Runtime.Loader中的AssemblyLoadContext.Default.LoadFromStream 方法,而在.NET 4.5+中要使用Assembly.Load方法。
三、總結
Razor 不僅僅使用了動態編譯,還有一個強大的模板解析的功能。利用自訂的模板基類還可以在模板裡提供一些輔助方法。這樣看來 Razor 也算是 C# DSL 的一種實現了。
GitHub:https://github.com/maxzhang1985/YOYOFx 如果覺還可以請Star下, 歡迎一起交流。
.NET Core 和 YOYOFx 的交流群: 214741894
如果你覺得本文對你有協助,請點擊“推薦”,謝謝.
.NET Core中使用Razor模板引擎