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 是把匿名類包裝成一個動態類型。
附上本文的代碼:
http://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功能