代码生成利器-NCodeGenerate 教程(8) 揭开Razor模板引擎的神秘面纱
NCodeGenerate使用的模板是Razor。了解一下Razor模板的生成原理,对调试NCodeGenerate 很有必要。下面讲解一下。
一、模板基类
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.IO; 6 namespace RazorDemo 7 { 8 public abstract class TemplateBase : IDisposable 9 { 10 public TemplateBase() 11 { 12 this.Output = new StringWriter(); 13 } 14 15 public dynamic Model { get; set; } 16 public StringWriter Output { get; private set; } 17 18 //写入变量 19 public virtual void Write(object value) 20 { 21 this.WriteLiteral(value.ToString()); 22 } 23 24 //写入固定字符串 25 public virtual void WriteLiteral(string value) 26 { 27 this.Output.Write(value); 28 } 29 30 //入口函数 31 public abstract void Execute(); 32 33 void IDisposable.Dispose() 34 { 35 this.Output.Dispose(); 36 } 37 } 38 }
这个类是所有模板的基类,Model 定义了一个 dynamic 类型的模型,用来从外部传参数。Write(object value) 方法是在模板执行时,把对象的值写入输出,WriteLiteral(string value) 方法是把非C#代码内容写入到输出。 在Razor 模板中的内容主要分为三类 :1、C#代码 2、需要输出C#变量 3、 非C# 的内容。
简单的来说(不是很严谨),模板在解析时,对于第一类 C#代码,会原封不动的输出,对于第二类 会产生一条 Write(object value)的调用语句,对于第三类 会产生 一条 WriteLiteral(string value) 的调用语句。
public abstract void Execute(); 是一个虚的方法,在产生C#模板类中,会生成一个继承自模板基类的子类,在子类中override 该方法,并把以上所产生的语句置于该方法之内。
这样讲起来可能不是很清楚。举个例子,模板代码如下:
1 @Model.Text 2 @for(int i=0;i<10;i++) 3 { 4 @:行数 @i.ToString() 5 }
在解析后产生的代码如下:
1 //------------------------------------------------------------------------------ 2 // <auto-generated> 3 // 此代码由工具生成。 4 // 运行时版本:4.0.30319.296 5 // 6 // 对此文件的更改可能会导致不正确的行为,并且如果 7 // 重新生成代码,这些更改将会丢失。 8 // </auto-generated> 9 //------------------------------------------------------------------------------ 10 11 namespace _RazorDemo 12 { 13 using System; 14 15 16 public class _0bb7e1a8_fc50_4d4d_99b0_53d2e3adb560 : RazorDemo.TemplateBase 17 { 18 19 #line hidden 20 21 22 public _0bb7e1a8_fc50_4d4d_99b0_53d2e3adb560() 23 { 24 } 25 26 public override void Execute() 27 { 28 29 Write(Model.Text); 30 31 WriteLiteral("\r\n"); 32 33 34 for (int i = 0; i < 10; i++) 35 { 36 37 WriteLiteral(" "); 38 39 WriteLiteral("行数 "); 40 41 42 Write(i.ToString()); 43 44 WriteLiteral("\r\n"); 45 46 47 } 48 49 WriteLiteral("\r\n"); 50 51 52 } 53 } 54 }
看了产生的代码应该就比较直观了。
第二、模板解析
模板解析主要是通过 RazorEngineHost 类和RazorTemplateEngine 类来实现的。看个例子吧
1 public GeneratorResults ParseToCode(string TemplateCode,string defaultnamespace,string defaultclassname) 2 { 3 GeneratorResults razorResults; 4 var host = new RazorEngineHost(new CSharpRazorCodeLanguage()); 5 host.DefaultBaseClass = typeof(TemplateBase).FullName; 6 host.DefaultNamespace =defaultnamespace; 7 host.DefaultClassName = defaultclassname; 8 host.NamespaceImports.Add("System"); 9 host.GeneratedClassContext = new GeneratedClassContext("Execute","Write", "WriteLiteral"); 10 11 12 var engine = new RazorTemplateEngine(host); 13 using (var reader = new StringReader(TemplateCode)) 14 { 15 razorResults = engine.GenerateCode(reader); 16 17 CSharpCodeProvider codeProvider = new CSharpCodeProvider(); 18 CodeGeneratorOptions options = new CodeGeneratorOptions(); 19 options.BracingStyle = "C"; 20 21 22 using (StringWriter writer = new StringWriter()) 23 { 24 IndentedTextWriter indentwriter = new IndentedTextWriter(writer, " "); 25 26 codeProvider.GenerateCodeFromCompileUnit(razorResults.GeneratedCode, indentwriter, options); 27 indentwriter.Flush(); 28 indentwriter.Close(); 29 LastGeneratedCode = writer.GetStringBuilder().ToString(); 30 31 32 } 33 34 } 35 return razorResults; 36 37 }
代码中的17行-32行是根据CodeDom 来生成C#代码的,如果你只是要生成Assembly,不需要看到生成的代码的话,那么17-32 行不是必须的。代码中的第4行到第9行,使在准备一个 RazorEngineHost 实例。
var host = new RazorEngineHost(new CSharpRazorCodeLanguage()); 是生成一个RazorEngineHost 的实例,new CSharpRazorCodeLanguage() 参数指定模板所用的语言是C#。
host.DefaultBaseClass = typeof(TemplateBase).FullName; 是指定生成的模板类的基类 这里是 前面代码定义的模板基类。
host.DefaultNamespace =defaultnamespace;
host.DefaultClassName = defaultclassname; 这两句代码是指定了生成的模板类的命名空间和类名。
host.NamespaceImports.Add("System"); 是 导入命名空间,模板引擎将根据 这里导入的命名空间,产生C# using 代码,这一句将会产生 using System;
host.GeneratedClassContext = new GeneratedClassContext("Execute","Write", "WriteLiteral"); 这一句代码的含义是 产生的模板类的 方法对应关系。要跟上面定义的模板基类的方法名称对应。
代码中的第12 -15句代码 就是调用模板引擎 产生CodeDom。
第三、模板的执行
1 private string ExecuteInternal(GeneratorResults razorResults, string defaultnamespace, string defaultclassname, dynamic Model) 2 { 3 4 using (var provider = new CSharpCodeProvider()) 5 { 6 var compiler = new CompilerParameters(); 7 compiler.ReferencedAssemblies.Add("System.dll"); 8 compiler.ReferencedAssemblies.Add("System.Core.dll"); 9 compiler.ReferencedAssemblies.Add("Microsoft.CSharp.dll"); 10 compiler.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location); 11 compiler.GenerateInMemory = true; 12 var result = provider.CompileAssemblyFromDom(compiler, razorResults.GeneratedCode); 13 if (result.Errors.HasErrors) 14 { 15 var error = result.Errors.OfType<CompilerError>().Where(i => !i.IsWarning).FirstOrDefault(); 16 if (error != null) throw new Exception(error.ErrorText); //抛出错误 17 } 18 TemplateBase temp= (TemplateBase)result.CompiledAssembly.CreateInstance(defaultnamespace+"."+defaultclassname); 19 temp.Model = Model; 20 try 21 { 22 temp.Execute(); 23 } 24 catch (Exception ex) 25 { 26 throw new Exception("执行错误",ex); 27 } 28 return temp.Output.ToString(); 29 } 30 31 32 }
这段代码就是调用第二步产生的CodeDom,编译生成 Assembly,并执行模板。这里需要解释一下的是。
compiler.ReferencedAssemblies.Add("System.dll");
compiler.ReferencedAssemblies.Add("System.Core.dll");
compiler.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
compiler.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
这4句,就是引入编译所需的Assembly。 其中System.Core.dll 和 Microsoft.CSharp.dll 是让模板具有dynamic 对象的功能。最后一句是引入当前的程序集。
第四、模板引擎最后的调用是这样调用的。
1 Engine engine = new Engine(); 2 dynamic model= new AnonymousDynamicType(new {Text=txtInput.Text}); 3 string result = engine.Execute(txtCode.Text, "_RazorDemo", GetSafeClassName(),model ); 4 txtExecuteResult.Text = result; 5 txtCodeResult.Text = engine.LastGeneratedCode;
其中第2行 AnonymousDynamicType 是把匿名类包装成一个动态类型。
附上本文的代码:
https://files.cnblogs.com/NCodeGenerate/RazorDemo.zip
附: NCodeGenerate 新增了 NCodeGenerate.DBSchema 的文档,在下面的文档连接中。
附上下载地址:
文档:文档
NCodeGenerate 系列文章:
一、代码生成利器-NCodeGenerate 是什么?
二、代码生成利器-NCodeGenerate 教程(1) 遍历数据库内的所有表
三、代码生成利器-NCodeGenerate 教程(2) NCodeGenerate的代码公用之一
四、代码生成利器-NCodeGenerate 教程(3) 生成代码到文件.
五、代码生成利器-NCodeGenerate 教程(4) CodeSmith模板转换
六、代码生成利器-NCodeGenerate 教程(5) 多种数据库的支持
七、代码生成利器-NCodeGenerate 教程(6) 调试功能 NTrace 输出
八、代码生成利器-NCodeGenerate 教程(7) 揭开调试功能 的神秘面纱
九、代码生成利器-NCodeGenerate 教程(8) 揭开Razor模板引擎的神秘面纱
10、代码生成利器-NCodeGenerate 教程(9) 数据类型映射Map功能