在C#中动态编译T4模板代码

转: http://www.wxzzz.com/1438.html
资料: https://cnsmartcodegenerator.codeplex.com/SourceControl/latest
 

什么是T4模板?

T4,即4个T开头的英文字母组合:Text Template Transformation Toolkit。T4文本模板将按照预期定义的一些代码进行动态生成一些所需的字符串内容。

如何动态编译T4模板?

通常我们在VS中进行*.tt (T4模板的编写),那么如何将t4模板代码动态的在我们自己的程序中进行编译生成呢?实际上微软已经提供了相应的接口进行。实现在自己的程序中进行编译T4模板需要实现一个自定义的宿主,你必须创建继承自 ITextTemplatingEngineHost 的类。接下来我们将一步一步完成我们自己的编译方式,(参考文章 http://msdn.microsoft.com/zh-cn/library/bb126579.aspx

1 首先你必须要有的Microsoft.VisualStudio.TextTemplating点击下载并解压,获得 Microsoft.VisualStudio.TextTemplating.dll 文件

2.在 Visual Studio 中,新建一个名为 CustomHost 的 C# 控制台应用程序。

3. 添加对下列程序集的引用

  • Microsoft.VisualStudio.TextTemplating.*.0
  • Microsoft.VisualStudio.TextTemplating.Interfaces.10.0 和更高版本

4. 用下面的代码替换 Program.cs 或 Module1.vb 文件中的代码:

  1. using System;
  2. using System.IO;
  3. using System.CodeDom.Compiler;
  4. using System.Collections.Generic;
  5. using System.Text;
  6. using Microsoft.VisualStudio.TextTemplating;
  7.  
  8. namespace CustomHost
  9. {
  10. //The text template transformation engine is responsible for running
  11. //the transformation process.
  12. //The host is responsible for all input and output, locating files,
  13. //and anything else related to the external environment.
  14. //-------------------------------------------------------------------------
  15. class CustomCmdLineHost : ITextTemplatingEngineHost
  16. {
  17. //the path and file name of the text template that is being processed
  18. //---------------------------------------------------------------------
  19. internal string TemplateFileValue;
  20. public string TemplateFile
  21. {
  22. get { return TemplateFileValue; }
  23. }
  24. //This will be the extension of the generated text output file.
  25. //The host can provide a default by setting the value of the field here.
  26. //The engine can change this value based on the optional output directive
  27. //if the user specifies it in the text template.
  28. //---------------------------------------------------------------------
  29. private string fileExtensionValue = ".txt";
  30. public string FileExtension
  31. {
  32. get { return fileExtensionValue; }
  33. }
  34. //This will be the encoding of the generated text output file.
  35. //The host can provide a default by setting the value of the field here.
  36. //The engine can change this value based on the optional output directive
  37. //if the user specifies it in the text template.
  38. //---------------------------------------------------------------------
  39. private Encoding fileEncodingValue = Encoding.UTF8;
  40. public Encoding FileEncoding
  41. {
  42. get { return fileEncodingValue; }
  43. }
  44. //These are the errors that occur when the engine processes a template.
  45. //The engine passes the errors to the host when it is done processing,
  46. //and the host can decide how to display them. For example, the host
  47. //can display the errors in the UI or write them to a file.
  48. //---------------------------------------------------------------------
  49. private CompilerErrorCollection errorsValue;
  50. public CompilerErrorCollection Errors
  51. {
  52. get { return errorsValue; }
  53. }
  54. //The host can provide standard assembly references.
  55. //The engine will use these references when compiling and
  56. //executing the generated transformation class.
  57. //--------------------------------------------------------------
  58. public IList<string> StandardAssemblyReferences
  59. {
  60. get
  61. {
  62. return new string[]
  63. {
  64. //If this host searches standard paths and the GAC,
  65. //we can specify the assembly name like this.
  66. //---------------------------------------------------------
  67. //"System"
  68.  
  69. //Because this host only resolves assemblies from the
  70. //fully qualified path and name of the assembly,
  71. //this is a quick way to get the code to give us the
  72. //fully qualified path and name of the System assembly.
  73. //---------------------------------------------------------
  74. typeof(System.Uri).Assembly.Location
  75. };
  76. }
  77. }
  78. //The host can provide standard imports or using statements.
  79. //The engine will add these statements to the generated
  80. //transformation class.
  81. //--------------------------------------------------------------
  82. public IList<string> StandardImports
  83. {
  84. get
  85. {
  86. return new string[]
  87. {
  88. "System"
  89. };
  90. }
  91. }
  92. //The engine calls this method based on the optional include directive
  93. //if the user has specified it in the text template.
  94. //This method can be called 0, 1, or more times.
  95. //---------------------------------------------------------------------
  96. //The included text is returned in the context parameter.
  97. //If the host searches the registry for the location of include files,
  98. //or if the host searches multiple locations by default, the host can
  99. //return the final path of the include file in the location parameter.
  100. //---------------------------------------------------------------------
  101. public bool LoadIncludeText(string requestFileName, out string content, out string location)
  102. {
  103. content = System.String.Empty;
  104. location = System.String.Empty;
  105. //If the argument is the fully qualified path of an existing file,
  106. //then we are done.
  107. //----------------------------------------------------------------
  108. if (File.Exists(requestFileName))
  109. {
  110. content = File.ReadAllText(requestFileName);
  111. return true;
  112. }
  113. //This can be customized to search specific paths for the file.
  114. //This can be customized to accept paths to search as command line
  115. //arguments.
  116. //----------------------------------------------------------------
  117. else
  118. {
  119. return false;
  120. }
  121. }
  122. //Called by the Engine to enquire about
  123. //the processing options you require.
  124. //If you recognize that option, return an
  125. //appropriate value.
  126. //Otherwise, pass back NULL.
  127. //--------------------------------------------------------------------
  128. public object GetHostOption(string optionName)
  129. {
  130. object returnObject;
  131. switch (optionName)
  132. {
  133. case "CacheAssemblies":
  134. returnObject = true;
  135. break;
  136. default:
  137. returnObject = null;
  138. break;
  139. }
  140. return returnObject;
  141. }
  142. //The engine calls this method to resolve assembly references used in
  143. //the generated transformation class project and for the optional
  144. //assembly directive if the user has specified it in the text template.
  145. //This method can be called 0, 1, or more times.
  146. //---------------------------------------------------------------------
  147. public string ResolveAssemblyReference(string assemblyReference)
  148. {
  149. //If the argument is the fully qualified path of an existing file,
  150. //then we are done. (This does not do any work.)
  151. //----------------------------------------------------------------
  152. if (File.Exists(assemblyReference))
  153. {
  154. return assemblyReference;
  155. }
  156. //Maybe the assembly is in the same folder as the text template that
  157. //called the directive.
  158. //----------------------------------------------------------------
  159. string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), assemblyReference);
  160. if (File.Exists(candidate))
  161. {
  162. return candidate;
  163. }
  164. //This can be customized to search specific paths for the file
  165. //or to search the GAC.
  166. //----------------------------------------------------------------
  167. //This can be customized to accept paths to search as command line
  168. //arguments.
  169. //----------------------------------------------------------------
  170. //If we cannot do better, return the original file name.
  171. return "";
  172. }
  173. //The engine calls this method based on the directives the user has
  174. //specified in the text template.
  175. //This method can be called 0, 1, or more times.
  176. //---------------------------------------------------------------------
  177. public Type ResolveDirectiveProcessor(string processorName)
  178. {
  179. //This host will not resolve any specific processors.
  180. //Check the processor name, and if it is the name of a processor the
  181. //host wants to support, return the type of the processor.
  182. //---------------------------------------------------------------------
  183. if (string.Compare(processorName, "XYZ", StringComparison.OrdinalIgnoreCase) == 0)
  184. {
  185. //return typeof();
  186. }
  187. //This can be customized to search specific paths for the file
  188. //or to search the GAC
  189. //If the directive processor cannot be found, throw an error.
  190. throw new Exception("Directive Processor not found");
  191. }
  192. //A directive processor can call this method if a file name does not
  193. //have a path.
  194. //The host can attempt to provide path information by searching
  195. //specific paths for the file and returning the file and path if found.
  196. //This method can be called 0, 1, or more times.
  197. //---------------------------------------------------------------------
  198. public string ResolvePath(string fileName)
  199. {
  200. if (fileName == null)
  201. {
  202. throw new ArgumentNullException("the file name cannot be null");
  203. }
  204. //If the argument is the fully qualified path of an existing file,
  205. //then we are done
  206. //----------------------------------------------------------------
  207. if (File.Exists(fileName))
  208. {
  209. return fileName;
  210. }
  211. //Maybe the file is in the same folder as the text template that
  212. //called the directive.
  213. //----------------------------------------------------------------
  214. string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), fileName);
  215. if (File.Exists(candidate))
  216. {
  217. return candidate;
  218. }
  219. //Look more places.
  220. //----------------------------------------------------------------
  221. //More code can go here...
  222. //If we cannot do better, return the original file name.
  223. return fileName;
  224. }
  225. //If a call to a directive in a text template does not provide a value
  226. //for a required parameter, the directive processor can try to get it
  227. //from the host by calling this method.
  228. //This method can be called 0, 1, or more times.
  229. //---------------------------------------------------------------------
  230. public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
  231. {
  232. if (directiveId == null)
  233. {
  234. throw new ArgumentNullException("the directiveId cannot be null");
  235. }
  236. if (processorName == null)
  237. {
  238. throw new ArgumentNullException("the processorName cannot be null");
  239. }
  240. if (parameterName == null)
  241. {
  242. throw new ArgumentNullException("the parameterName cannot be null");
  243. }
  244. //Code to provide "hard-coded" parameter values goes here.
  245. //This code depends on the directive processors this host will interact with.
  246. //If we cannot do better, return the empty string.
  247. return String.Empty;
  248. }
  249. //The engine calls this method to change the extension of the
  250. //generated text output file based on the optional output directive
  251. //if the user specifies it in the text template.
  252. //---------------------------------------------------------------------
  253. public void SetFileExtension(string extension)
  254. {
  255. //The parameter extension has a '.' in front of it already.
  256. //--------------------------------------------------------
  257. fileExtensionValue = extension;
  258. }
  259. //The engine calls this method to change the encoding of the
  260. //generated text output file based on the optional output directive
  261. //if the user specifies it in the text template.
  262. //----------------------------------------------------------------------
  263. public void SetOutputEncoding(System.Text.Encoding encoding, bool fromOutputDirective)
  264. {
  265. fileEncodingValue = encoding;
  266. }
  267. //The engine calls this method when it is done processing a text
  268. //template to pass any errors that occurred to the host.
  269. //The host can decide how to display them.
  270. //---------------------------------------------------------------------
  271. public void LogErrors(CompilerErrorCollection errors)
  272. {
  273. errorsValue = errors;
  274. }
  275. //This is the application domain that is used to compile and run
  276. //the generated transformation class to create the generated text output.
  277. //----------------------------------------------------------------------
  278. public AppDomain ProvideTemplatingAppDomain(string content)
  279. {
  280. //This host will provide a new application domain each time the
  281. //engine processes a text template.
  282. //-------------------------------------------------------------
  283. return AppDomain.CreateDomain("Generation App Domain");
  284. //This could be changed to return the current appdomain, but new
  285. //assemblies are loaded into this AppDomain on a regular basis.
  286. //If the AppDomain lasts too long, it will grow indefintely,
  287. //which might be regarded as a leak.
  288. //This could be customized to cache the application domain for
  289. //a certain number of text template generations (for example, 10).
  290. //This could be customized based on the contents of the text
  291. //template, which are provided as a parameter for that purpose.
  292. }
  293. }
  294. //This will accept the path of a text template as an argument.
  295. //It will create an instance of the custom host and an instance of the
  296. //text templating transformation engine, and will transform the
  297. //template to create the generated text output file.
  298. //-------------------------------------------------------------------------
  299. class Program
  300. {
  301. static void Main(string[] args)
  302. {
  303. try
  304. {
  305. ProcessTemplate(args);
  306. }
  307. catch (Exception ex)
  308. {
  309. Console.WriteLine(ex.Message);
  310. }
  311. }
  312. static void ProcessTemplate(string[] args)
  313. {
  314. string templateFileName = null;
  315. if (args.Length == 0)
  316. {
  317. throw new System.Exception("you must provide a text template file path");
  318. }
  319. templateFileName = args[0];
  320. if (templateFileName == null)
  321. {
  322. throw new ArgumentNullException("the file name cannot be null");
  323. }
  324. if (!File.Exists(templateFileName))
  325. {
  326. throw new FileNotFoundException("the file cannot be found");
  327. }
  328. CustomCmdLineHost host = new CustomCmdLineHost();
  329. Engine engine = new Engine();
  330. host.TemplateFileValue = templateFileName;
  331. //Read the text template.
  332. string input = File.ReadAllText(templateFileName);
  333. //Transform the text template.
  334. string output = engine.ProcessTemplate(input, host);
  335. string outputFileName = Path.GetFileNameWithoutExtension(templateFileName);
  336. outputFileName = Path.Combine(Path.GetDirectoryName(templateFileName), outputFileName);
  337. outputFileName = outputFileName + "1" + host.FileExtension;
  338. File.WriteAllText(outputFileName, output, host.FileEncoding);
  339.  
  340. foreach (CompilerError error in host.Errors)
  341. {
  342. Console.WriteLine(error.ToString());
  343. }
  344. }
  345. }
  346. }

5. 仅对于 Visual Basic,打开“项目”菜单,单击“CustomHost 属性”。 在“启动对象”列表中单击“CustomHost.Program”。

6. 在“文件”菜单上,单击“全部保存”。

7. 在“生成”菜单上,单击“生成解决方案”。

进行编译并测试

若要测试自定义宿主,您需要编写一个文本模板,然后运行自定义宿主,将文本模板的名称传递给它并验证模板转换。

创建文本模板测试自定义宿主

1. 在C盘根目录中创建一个文本文件,将其命名为 TestTemplate.tt 。

可以使用任何文本编辑器(例如记事本)来创建文件。

2. 将以下内容添加到文件中:

  1. Text Template Host Test
  2.  
  3. <#@ template debug="true" #>
  4.  
  5. <# //Uncomment this line to test that the host allows the engine to set the extension. #>
  6. <# //@ output extension=".htm" #>
  7.  
  8. <# //Uncomment this line if you want to debug the generated transformation class. #>
  9. <# //System.Diagnostics.Debugger.Break(); #>
  10.  
  11. <# for (int i=0; i<3; i++)
  12. {
  13. WriteLine("This is a test");
  14. }
  15. #>

3. 保存并关闭文件。

完成/使用

进行使用测试吧,我们需要在cmd(命令提示符窗口)程序中进行测试。

1. 打开 cmd 窗口

2. 在窗口中键入   你的程序编译的文件路径CustomHost.exe c:TestTemplate.tt (中间包含一个空格),然后按下Enter (回车)键。

此时在C盘根目录中就已经生成了 TestTemplate1.txt,内容为:

Text Template Host Test




This is a test
This is a test
This is a test
 
 
原文地址:https://www.cnblogs.com/chencidi/p/6426715.html