前言
之前在 “使用T4模板生成代码 - 初探” 文章简单的使用了T4模板的生成功能,但对于一个模板生成多个实例文件,如何实现这个方式呢?无意发现一个解决方案 “MultipleOutputHelper.ttinclude” ,它让基于T4模板批量生成文件实例变得简单起来了。
什么是MultipleOutputHelper.ttinclude
Damien Guard是一个在加利福尼亚州圣何塞的开发人员,他构建出处理使用T4模板输出多文件的解决方案“MultipleOutputHelper.ttinclude”
使用
1. 初始化
获取MultipleOutputHelper.ttinclude文件模板
注意: 文件可以上github.com 托管上面获取( https://github.com/damieng/DamienGKit/tree/master/T4/MultipleOutputHelper)
在T4模板中使用include指令导入MultipleOutputHelper.ttinclude文件或者将MultipleOutputHelper.ttinclude的代码复制在T4模板内。
然后初始化Manager对象,代码如下:
注意: 这里的Manager.ttinclude 就是MultipleOutputHelper.ttinclude文件模板
2. 文件块
使用代码标识区分生成的代码块的范围
该代码声明了一个Employee.generated.cs文件,文件代码内容为:
1
|
public
class
Employee { ... }
|
3. 页眉和页脚
很多模板需要共享一个共同的页眉和页脚时,可以使用import语句进行打开和关闭。简单的使用StartHeader和StartFooter的代码方法进行分割。
4. 编译执行
使用Process方法,进行文件分割。
场景应用
基于之前的“使用T4模板生成代码 - 初探” 文章的场景,进行基于NHibernate Mapper 来获取Domain对象,然后进行批量生成多个代码文件。
1. 自定义T4模板,文件名为“EntityRepositoryTemplate.tt”,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
<#@ template language=
"C#"
debug=
"true"
hostspecific=
"True"
#>
// 导入MultipleOutputHelper.ttinclude文件
<#@include file=
"$(SolutionDir)app\T4\MultipleOutputHelper.ttinclude"
#>
// 导入相关的DLL
<#@ Assembly Name=
"$(SolutionDir)lib\SharpArch.2.0.2\NHibernate.dll"
#>
<#@ Assembly Name=
"$(SolutionDir)lib\SharpArch.2.0.2\SharpArch.NHibernate.dll"
#>
<#@ Assembly Name=
"$(SolutionDir)lib\SharpArch.2.0.2\SharpArch.Domain.dll"
#>
<#@ Assembly Name=
"$(SolutionDir)lib\SharpArch.2.0.2\FluentNHibernate.dll"
#>
<#@ Assembly Name=
"$(SolutionDir)app\Cotide.Data\bin\$(ConfigurationName)\Cotide.Infrastructure.dll"
#>
<#@ Assembly Name=
"$(SolutionDir)app\Cotide.Core\bin\$(ConfigurationName)\Cotide.Domain.dll"
#>
<#@ Assembly Name=
"$(SolutionDir)app\Cotide.Framework\bin\$(ConfigurationName)\Cotide.Framework.dll"
#>
<#@ import
namespace
=
"System.IO"
#>
<#@ import
namespace
=
"System"
#>
<#@ import
namespace
=
"System.Configuration"
#>
<#
// 初始化
SharpArch.NHibernate.NHibernateSession.CloseAllSessions();
SharpArch.NHibernate.NHibernateSession.Reset();
string
projectPath =
@"C:\资料\Person\项目\Codeplex\电子商务\app\"
;
string
nhibernatePath = projectPath +
@"Cotide.Web\NHibernate.config"
;
string
[] mappingAssemblies =
new
[] {
@"C:\资料\Person\项目\Codeplex\电子商务\app\Cotide.Data\bin\Release\Cotide.Infrastructure.dll"
};
// 加载配置
NHibernate.Cfg.Configuration configuration = SharpArch.NHibernate.NHibernateSession.Init(
new
SharpArch.NHibernate.SimpleSessionStorage(), mappingAssemblies,
new
Cotide.Infrastructure.NHibernateMaps.AutoPersistenceModelGenerator().Generate(),
nhibernatePath);
// 获取所有类映射
var allClassMetadata = SharpArch.NHibernate.NHibernateSession.GetDefaultSessionFactory().GetAllClassMetadata();
var manager = Manager.Create(Host, GenerationEnvironment);
foreach
(var entry
in
allClassMetadata)
{
var entityName = entry.Value.EntityName.Split(
'.'
);
var className = entityName[entityName.Length - 1];
// 定义输出文件
manager.StartNewFile(
"I"
+className+
"Repository.cs"
);
#>//-------------------------------------------------------------------
//版权所有:版权所有(C) 2012,Microsoft(China) Co.,LTD
//系统名称:
//文件名称:I<#=className#>Repository.cs
//模块名称:
//模块编号:
//作 者:xcli
//创建时间:2013/4/6 12:49:50
//功能说明:
//-----------------------------------------------------------------
//修改记录:
//修改人:
//修改时间:
//修改内容:
//-----------------------------------------------------------------
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
Cotide.Domain.Contracts.Repositories.Extension;
namespace
Cotide.Domain.Contracts.Repositories
{
public
interface
I<#=className#>Repository : IDbProxyRepository<<#=className#>>
{
}
}
<#
// 结束输出文件
manager.EndBlock();
}
// 执行编译
manager.Process(
true
);
#>
|
输出文件效果:
让程序自动执行基于T4文件的编译工作
在MSDN-”演练:创建自定义文本模板宿主“ 文章里面看到一段代码,进行了T4文件的编译工作,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
|
using
System;
using
System.IO;
using
System.CodeDom.Compiler;
using
System.Collections.Generic;
using
System.Text;
using
Microsoft.VisualStudio.TextTemplating;
namespace
CustomHost
{
//The text template transformation engine is responsible for running
//the transformation process.
//The host is responsible for all input and output, locating files,
//and anything else related to the external environment.
//-------------------------------------------------------------------------
class
CustomCmdLineHost : ITextTemplatingEngineHost
{
//the path and file name of the text template that is being processed
//---------------------------------------------------------------------
internal
string
TemplateFileValue;
public
string
TemplateFile
{
get
{
return
TemplateFileValue; }
}
//This will be the extension of the generated text output file.
//The host can provide a default by setting the value of the field here.
//The engine can change this value based on the optional output directive
//if the user specifies it in the text template.
//---------------------------------------------------------------------
private
string
fileExtensionValue =
".txt"
;
public
string
FileExtension
{
get
{
return
fileExtensionValue; }
}
//This will be the encoding of the generated text output file.
//The host can provide a default by setting the value of the field here.
//The engine can change this value based on the optional output directive
//if the user specifies it in the text template.
//---------------------------------------------------------------------
private
Encoding fileEncodingValue = Encoding.UTF8;
public
Encoding FileEncoding
{
get
{
return
fileEncodingValue; }
}
//These are the errors that occur when the engine processes a template.
//The engine passes the errors to the host when it is done processing,
//and the host can decide how to display them. For example, the host
//can display the errors in the UI or write them to a file.
//---------------------------------------------------------------------
private
CompilerErrorCollection errorsValue;
public
CompilerErrorCollection Errors
{
get
{
return
errorsValue; }
}
//The host can provide standard assembly references.
//The engine will use these references when compiling and
//executing the generated transformation class.
//--------------------------------------------------------------
public
IList<
string
> StandardAssemblyReferences
{
get
{
return
new
string
[]
{
//If this host searches standard paths and the GAC,
//we can specify the assembly name like this.
//---------------------------------------------------------
//"System"
//Because this host only resolves assemblies from the
//fully qualified path and name of the assembly,
//this is a quick way to get the code to give us the
//fully qualified path and name of the System assembly.
//---------------------------------------------------------
typeof
(System.Uri).Assembly.Location
};
}
}
//The host can provide standard imports or using statements.
//The engine will add these statements to the generated
//transformation class.
//--------------------------------------------------------------
public
IList<
string
> StandardImports
{
get
{
return
new
string
[]
{
"System"
};
}
}
//The engine calls this method based on the optional include directive
//if the user has specified it in the text template.
//This method can be called 0, 1, or more times.
//---------------------------------------------------------------------
//The included text is returned in the context parameter.
//If the host searches the registry for the location of include files,
//or if the host searches multiple locations by default, the host can
//return the final path of the include file in the location parameter.
//---------------------------------------------------------------------
public
bool
LoadIncludeText(
string
requestFileName,
out
string
content,
out
string
location)
{
content = System.String.Empty;
location = System.String.Empty;
//If the argument is the fully qualified path of an existing file,
//then we are done.
//----------------------------------------------------------------
if
(File.Exists(requestFileName))
{
content = File.ReadAllText(requestFileName);
return
true
;
}
//This can be customized to search specific paths for the file.
//This can be customized to accept paths to search as command line
//arguments.
//----------------------------------------------------------------
else
{
return
false
;
}
}
//Called by the Engine to enquire about
//the processing options you require.
//If you recognize that option, return an
//appropriate value.
//Otherwise, pass back NULL.
//--------------------------------------------------------------------
public
object
GetHostOption(
string
optionName)
{
object
returnObject;
switch
(optionName)
{
case
"CacheAssemblies"
:
returnObject =
true
;
break
;
default
:
returnObject =
null
;
break
;
}
return
returnObject;
}
//The engine calls this method to resolve assembly references used in
//the generated transformation class project and for the optional
//assembly directive if the user has specified it in the text template.
//This method can be called 0, 1, or more times.
//---------------------------------------------------------------------
public
string
ResolveAssemblyReference(
string
assemblyReference)
{
//If the argument is the fully qualified path of an existing file,
//then we are done. (This does not do any work.)
//----------------------------------------------------------------
if
(File.Exists(assemblyReference))
{
return
assemblyReference;
}
//Maybe the assembly is in the same folder as the text template that
//called the directive.
//----------------------------------------------------------------
string
candidate = Path.Combine(Path.GetDirectoryName(
this
.TemplateFile), assemblyReference);
if
(File.Exists(candidate))
{
return
candidate;
}
//This can be customized to search specific paths for the file
//or to search the GAC.
//----------------------------------------------------------------
//This can be customized to accept paths to search as command line
//arguments.
//----------------------------------------------------------------
//If we cannot do better, return the original file name.
return
""
;
}
//The engine calls this method based on the directives the user has
//specified in the text template.
//This method can be called 0, 1, or more times.
//---------------------------------------------------------------------
public
Type ResolveDirectiveProcessor(
string
processorName)
{
//This host will not resolve any specific processors.
//Check the processor name, and if it is the name of a processor the
//host wants to support, return the type of the processor.
//---------------------------------------------------------------------
if
(
string
.Compare(processorName,
"XYZ"
, StringComparison.OrdinalIgnoreCase) == 0)
{
//return typeof();
}
//This can be customized to search specific paths for the file
//or to search the GAC
//If the directive processor cannot be found, throw an error.
throw
new
Exception(
"Directive Processor not found"
);
}
//A directive processor can call this method if a file name does not
//have a path.
//The host can attempt to provide path information by searching
//specific paths for the file and returning the file and path if found.
//This method can be called 0, 1, or more times.
//---------------------------------------------------------------------
public
string
ResolvePath(
string
fileName)
{
if
(fileName ==
null
)
{
throw
new
ArgumentNullException(
"the file name cannot be null"
);
}
//If the argument is the fully qualified path of an existing file,
//then we are done
//----------------------------------------------------------------
if
(File.Exists(fileName))
{
return
fileName;
}
//Maybe the file is in the same folder as the text template that
//called the directive.
//----------------------------------------------------------------
string
candidate = Path.Combine(Path.GetDirectoryName(
this
.TemplateFile), fileName);
if
(File.Exists(candidate))
{
return
candidate;
}
//Look more places.
//----------------------------------------------------------------
//More code can go here...
//If we cannot do better, return the original file name.
return
fileName;
}
//If a call to a directive in a text template does not provide a value
//for a required parameter, the directive processor can try to get it
//from the host by calling this method.
//This method can be called 0, 1, or more times.
//---------------------------------------------------------------------
public
string
ResolveParameterValue(
string
directiveId,
string
processorName,
string
parameterName)
{
if
(directiveId ==
null
)
{
throw
new
ArgumentNullException(
"the directiveId cannot be null"
);
}
if
(processorName ==
null
)
{
throw
new
ArgumentNullException(
"the processorName cannot be null"
);
}
if
(parameterName ==
null
)
{
throw
new
ArgumentNullException(
"the parameterName cannot be null"
);
}
//Code to provide "hard-coded" parameter values goes here.
//This code depends on the directive processors this host will interact with.
//If we cannot do better, return the empty string.
return
String.Empty;
}
//The engine calls this method to change the extension of the
//generated text output file based on the optional output directive
//if the user specifies it in the text template.
//---------------------------------------------------------------------
public
void
SetFileExtension(
string
extension)
{
//The parameter extension has a '.' in front of it already.
//--------------------------------------------------------
fileExtensionValue = extension;
}
//The engine calls this method to change the encoding of the
//generated text output file based on the optional output directive
//if the user specifies it in the text template.
//----------------------------------------------------------------------
public
void
SetOutputEncoding(System.Text.Encoding encoding,
bool
fromOutputDirective)
{
fileEncodingValue = encoding;
}
//The engine calls this method when it is done processing a text
//template to pass any errors that occurred to the host.
//The host can decide how to display them.
//---------------------------------------------------------------------
public
void
LogErrors(CompilerErrorCollection errors)
{
errorsValue = errors;
}
//This is the application domain that is used to compile and run
//the generated transformation class to create the generated text output.
//----------------------------------------------------------------------
public
AppDomain ProvideTemplatingAppDomain(
string
content)
{
//This host will provide a new application domain each time the
//engine processes a text template.
//-------------------------------------------------------------
return
AppDomain.CreateDomain(
"Generation App Domain"
);
//This could be changed to return the current appdomain, but new
//assemblies are loaded into this AppDomain on a regular basis.
//If the AppDomain lasts too long, it will grow indefintely,
//which might be regarded as a leak.
//This could be customized to cache the application domain for
//a certain number of text template generations (for example, 10).
//This could be customized based on the contents of the text
//template, which are provided as a parameter for that purpose.
}
}
//This will accept the path of a text template as an argument.
//It will create an instance of the custom host and an instance of the
//text templating transformation engine, and will transform the
//template to create the generated text output file.
//-------------------------------------------------------------------------
class
Program
{
static
void
Main(
string
[] args)
{
try
{
ProcessTemplate(args);
}
catch
(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static
void
ProcessTemplate(
string
[] args)
{
string
templateFileName =
null
;
if
(args.Length == 0)
{
throw
new
System.Exception(
"you must provide a text template file path"
);
}
templateFileName = args[0];
if
(templateFileName ==
null
)
{
throw
new
ArgumentNullException(
"the file name cannot be null"
);
}
if
(!File.Exists(templateFileName))
{
throw
new
FileNotFoundException(
"the file cannot be found"
);
}
CustomCmdLineHost host =
new
CustomCmdLineHost();
Engine engine =
new
Engine();
host.TemplateFileValue = templateFileName;
//Read the text template.
string
input = File.ReadAllText(templateFileName);
//Transform the text template.
string
output = engine.ProcessTemplate(input, host);
string
outputFileName = Path.GetFileNameWithoutExtension(templateFileName);
outputFileName = Path.Combine(Path.GetDirectoryName(templateFileName), outputFileName);
outputFileName = outputFileName +
"1"
+ host.FileExtension;
File.WriteAllText(outputFileName, output, host.FileEncoding);
foreach
(CompilerError error
in
host.Errors)
{
Console.WriteLine(error.ToString());
}
}
}
}
|
联想:基于这个代码实例,可以做一个基于T4模板来批量输出代码的小工具,待续.....
参考资料
- MultipleOutputHelper.ttinclude 文件作者(Damien Guard)- 博客( http://damieng.com )
- Damien Guard (Github)( https://github.com/damieng )
- 创建自定义文本模板宿主( MSDN) ( http://msdn.microsoft.com/zh-cn/library/vstudio/bb126579.aspx )
- 使用T4模板生成代码 - 初探 ( http://www.cotide.com/xcli/Blog/Article/44 )
转自 http://www.cnblogs.com/K_tommy/archive/2013/04/06/T4.html