在MVC之外的场景中,咱们每每须要完成一些模板引擎生成代码或页面的工做;在之前咱们通常经常使用的有Razor、NVeocity、VTemplate。虽然全部的模板系统都具备一些共同特征,但 Razor却和咱们前面讨论的二种视图引擎大相径庭。不一样于其它视图引擎,Razor在使用XML代 码方面没有走得那么极端。它也不彻底相似于ASPX,由于它把那些比较笨重的占位符替换成@符号接表达式或者普通的控制块。由于不须要特殊的结束标记,所 以Razor最终的代码很简练。html
本篇介绍的主角是Razor,在非Core的版本中,咱们经常使用开源的RazorEngine来解决咱们的问题;可是它却没有对应.NET Core的版本。咱们也只要本身动手来完成一个支持.NET Core的“模板引擎”版本。git
通常状况下使用Razor做为视图引擎要实现以下步骤:github
(1)读取模板文件 -> (2)生成Raozr的C#代码 -> (3)使用Roslyn编译代码生成程序集 -> (4)动态加载程序集 -> (5)反射调用json
咱们通常在使用Razor时都是在ASP.NET MVC中使用.cshtml来做为模板,由ASP.NET MVC的视图引擎(ViewEngine)来生成页面的代码的,总之,这里想说的是,模板引擎是独立的,它们甚至是独立的项目,由不一样的公司和组织来开发。此次咱们要在非Mvc中使用Raozr;首先咱们要“脱离”Mvc的环境。框架
这里咱们只在.NET Core程序中引用微软Raozr部分的程序集Microsoft.AspNetCore.Razor 1.0版本,这个程序集负责将模板生成出C#代码。dom
"dependencies": { "Microsoft.AspNetCore.Razor": "1.0.0" "NETStandard.Library": "1.6.0" }
以下是摘录的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#代码。post
通常Razor的C#代码生成后,都是经过CodeDom来生成和编译代码的;.NET开源后,咱们又多了一个强大的选择Roslyn , Roslyn也是支持.NET Core的,而且在整个.NET平台中,扮演着很是重要的角色,小到这种视图代码编译,大到整个项目的编译场景都有Roslyn的身影。微软最新开源的Visual Studio Code中C#插件,OmniSharp就是经过Roslyn来对项目和编辑器提供支持的。ui
摘录YOYOFx代码以下:this
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
若是你以为本文对你有帮助,请点击“推荐”,谢谢.