原文:
Title:Write your own Code Generator or Template Engine in .NET
Url:http://www.codeproject.com/csharp/smarttemplateengine.asp
Author:Shahed.Khan.
源程序下载: 点击下载此文件
如果你使用过Code Smith或类似的代三生成工具,你可能会去猜想这种类型的工具是如何实现代码生成的.通过我的这篇文章,我来传述一下在Net下写一个代码生成器并不是一件很难的事情.一个模板引擎就是一个软件或一个组件,通过输入各种相关的参数而输出预期固定格式的代码.使用它最大的一个好处就是提交我们的效率,减少我们一些不必要的重复代码工作.例如,当我们要开发一个应用程序,不管我们是采用著名的设计模式(像MVC)还是使用我们自己设计的一些模式,我们都需要面对一个贯穿应用程序的一个通用的架构,之后我们可以通过对这些通用的部分设计出一些通用的模板并使用模板引擎快速地为我们生成我们所需要的代码.
模板引擎在开发效率上会产生巨大的反响,假设当你有一个包含有300个表(每个表至少含有5个字段)的数据库,你可以想象一下对这些数据库字段对象的,你要花多长的时间才能写完?不仅仅这点,当你要完成它的时候,你还不得不将你的数据写入你的数据层, 逻辑层,,这将要增加许多许多的工作量.这时,如果在模板引擎的帮助下,你可以在几分钟内生成一系列的代码(按照相似的设计模式).你要做到,只是定一个简单的模板,之后像这个模板引擎在数据库中的每一个表中的每一个字段,它就会自动产生你想要的代码了.
由此我们可以知道模板引擎对于开发者来说是非常有用和节省时间的.我们不再花费太多的口舌在介绍模板引擎上, 直接进入我们的主题吧
使用STE引擎技术(Technologies Used in Smart Template Engine)
对于在看过Microsoft® Provider Design Pattern, Enterprise Library者,我想你可以先看看我之前的文章“Flexible and Plug-in-based .NET Application using Provider Pattern”和Ready-to-use Mass Emailing Functionality with C#, .NET 2.0, and Microsoft® SQL Server 2005 Service Broker..对于一个在CodeDom的初学者,你可以查看一下CodeDom学习手册CodeDom quick reference
模板引擎的工作流:
为了实现我们的要求,我们需要先写出一个生成器用来把模板转变成可用的Net代码,之后,我们可能使用CodeDom运行生成器的代码并最终输出我们想要的结果
我们来看一下下面这个例子
假设我们想要输出类似下面这样的代码:
要想输出上面这样的代码,我们的模板可能看起来就需要像类似下面这样的写法,这样才能让我们的模板去代替那些类名和相关的架构
所以,我们需要写出一个生成器,让这模板分成一系列的代码,之后运用CodeDom来编译.
注意到生成器中将<%=%>标记和<% %>标记都预先写入了模板所定义好的内容中,下面我们可以从客户端看一看它的工作流程.
客户端 => 模板=>通过生成器生成可用的Net类=>通过CodeDom执行编译器=>生成所要的代码
STE Framework
通过下面这个类关系图,你可以发现多层结构运用于这个任务中.为了更好的理解STE Framework ,我们需要详细了解 Template 和TemplateEngineSettin这两个对象
STE Framework中的 Template 对象
Template对象是一个简单的逻辑层的对像,它通过libraries库,其它类库,负责输出和输出代码(包括异常),是一贯穿于不同层之间的一个对象
STE Framework中的TemplateEngineSeeting 对象
TemplateEngineSetting 对象是逻辑层中的另一个类,主要负现模板引擎的全局设置
STE中所提供的接口
STE 提供者
模板提供者生成器:这个提供者提供生成器相关的功能
产生输出提供者:产生所要的输出
架构描述:正如MS所述,我们在整个架构中将这层视为核心执行的部分.提供者模式的最美丽之处在于其合适的提供者都从所包含的架构的文件中即时实现初始化,同时可以定义无限制的提供者.
在这上面的部分,我在我们的提供者中加入了ASPStyleParseTemplateprovider 和 CodeDomGenerateOutputProvider
在STE Framework中,使用Enterprise Library的作用在于SME可以用它来处理缓存,日志和异常处理部分.
模板生成
这个应用程序的demo考虑了ASP/ASP.Net中的标记特别是<%=%>和<%%>同时兼容ASP和ASP.Net,下面是神奇的代码
让我们更深入了解一下GetParseResult()这个方法.在生成模板中,我们使用了一些相关的正则表达式.在这个demo中,我只用了<%%>和<%=%>两种标记,但是你可以自由地实现你想要的标记.
上面这些代码都是自动注释的,它通过发现在模板中的ASP/ASP.NET标记,之后通过GetCodeWithoutAssemblyNameSpacePropertyAndOther()方法进行处理,之后通过相关方法将#:::#RenderMethod#:::#(举例)替换返回一个之后定义好的值.
产生输出
此提供者通过从生成器中所生成的可利用的Net代码交输出我们想要的代码
我曾经在A tool for dynamic compile and run of C# or VB.NET code中使用过类似这样复杂的引擎 ,之后我根据自身的需要,将部分代码修改了并使用c#编译了这部分
编译引擎的核心部分主要在下面这两个方面法:
创建程序集createAssembly() 将这源码编译成功并让程序集保存在内存中
callEntry(Assembly…) 通过反射调用切入点
• CreateAssembly compiles the source code and makes the assembly in memory, and
• CallEntry(Assembly...) calls the entry point by reflection.
Demo 应用
这个demo应用程序有五个选项
产生代码选项卡: 在模板生成引擎的作用下,通过生成器生成一个模板,你就可以看到所生成的c#代码了.
输出选项卡:你可以看到邮输出提供者通过默认的生成模板所生成的输出代码
Libraries 选项卡:在这里构可以使用特殊的’using’关键字来产生特定的类,这可以让你使用任何的常用的libraries.Net ‘using’将会在预先设计的类结构中
Title:Write your own Code Generator or Template Engine in .NET
Url:http://www.codeproject.com/csharp/smarttemplateengine.asp
Author:Shahed.Khan.
源程序下载: 点击下载此文件
如果你使用过Code Smith或类似的代三生成工具,你可能会去猜想这种类型的工具是如何实现代码生成的.通过我的这篇文章,我来传述一下在Net下写一个代码生成器并不是一件很难的事情.一个模板引擎就是一个软件或一个组件,通过输入各种相关的参数而输出预期固定格式的代码.使用它最大的一个好处就是提交我们的效率,减少我们一些不必要的重复代码工作.例如,当我们要开发一个应用程序,不管我们是采用著名的设计模式(像MVC)还是使用我们自己设计的一些模式,我们都需要面对一个贯穿应用程序的一个通用的架构,之后我们可以通过对这些通用的部分设计出一些通用的模板并使用模板引擎快速地为我们生成我们所需要的代码.
模板引擎在开发效率上会产生巨大的反响,假设当你有一个包含有300个表(每个表至少含有5个字段)的数据库,你可以想象一下对这些数据库字段对象的,你要花多长的时间才能写完?不仅仅这点,当你要完成它的时候,你还不得不将你的数据写入你的数据层, 逻辑层,,这将要增加许多许多的工作量.这时,如果在模板引擎的帮助下,你可以在几分钟内生成一系列的代码(按照相似的设计模式).你要做到,只是定一个简单的模板,之后像这个模板引擎在数据库中的每一个表中的每一个字段,它就会自动产生你想要的代码了.
由此我们可以知道模板引擎对于开发者来说是非常有用和节省时间的.我们不再花费太多的口舌在介绍模板引擎上, 直接进入我们的主题吧
使用STE引擎技术(Technologies Used in Smart Template Engine)
对于在看过Microsoft® Provider Design Pattern, Enterprise Library者,我想你可以先看看我之前的文章“Flexible and Plug-in-based .NET Application using Provider Pattern”和Ready-to-use Mass Emailing Functionality with C#, .NET 2.0, and Microsoft® SQL Server 2005 Service Broker..对于一个在CodeDom的初学者,你可以查看一下CodeDom学习手册CodeDom quick reference
模板引擎的工作流:
为了实现我们的要求,我们需要先写出一个生成器用来把模板转变成可用的Net代码,之后,我们可能使用CodeDom运行生成器的代码并最终输出我们想要的结果
我们来看一下下面这个例子
假设我们想要输出类似下面这样的代码:
程序代码: | [ 复制代码到剪贴板 ] |
public class Test
{
public Test()
{
}
// Do Something
// Do Something
}
{
public Test()
{
}
// Do Something
// Do Something
}
要想输出上面这样的代码,我们的模板可能看起来就需要像类似下面这样的写法,这样才能让我们的模板去代替那些类名和相关的架构
程序代码: | [ 复制代码到剪贴板 ] |
<%private string classname = “Test”;%>
public class <%=classname%>
{
public <%=classname%> ()
{
}
<% for (int i=0; i< 2 ; i++)
{%>
// Do Something
<%}%>
}
public class <%=classname%>
{
public <%=classname%> ()
{
}
<% for (int i=0; i< 2 ; i++)
{%>
// Do Something
<%}%>
}
所以,我们需要写出一个生成器,让这模板分成一系列的代码,之后运用CodeDom来编译.
程序代码: | [ 复制代码到剪贴板 ] |
string classname = “Test”;
MemoryStream mStream = new MemoryStream();
StreamWriter writer = new
StreamWriter(mStream,System.Text.Encoding.UTF8);
writer.Write(@"public class “);
writer.Write(classname);
writer.Write(@"{
public “);
writer.Write(classname);
writer.Write(@"()
{
}
}“);
for (int i=0; i< 10 ; i++)
{
writer.Write(@" // Do Something" );
}
StreamReader sr = new StreamReader(mStream);
writer.Flush();
mStream.Position = 0;
string code = sr.ReadToEnd();
MemoryStream mStream = new MemoryStream();
StreamWriter writer = new
StreamWriter(mStream,System.Text.Encoding.UTF8);
writer.Write(@"public class “);
writer.Write(classname);
writer.Write(@"{
public “);
writer.Write(classname);
writer.Write(@"()
{
}
}“);
for (int i=0; i< 10 ; i++)
{
writer.Write(@" // Do Something" );
}
StreamReader sr = new StreamReader(mStream);
writer.Flush();
mStream.Position = 0;
string code = sr.ReadToEnd();
注意到生成器中将<%=%>标记和<% %>标记都预先写入了模板所定义好的内容中,下面我们可以从客户端看一看它的工作流程.
客户端 => 模板=>通过生成器生成可用的Net类=>通过CodeDom执行编译器=>生成所要的代码
STE Framework
通过下面这个类关系图,你可以发现多层结构运用于这个任务中.为了更好的理解STE Framework ,我们需要详细了解 Template 和TemplateEngineSettin这两个对象
STE Framework中的 Template 对象
Template对象是一个简单的逻辑层的对像,它通过libraries库,其它类库,负责输出和输出代码(包括异常),是一贯穿于不同层之间的一个对象
STE Framework中的TemplateEngineSeeting 对象
TemplateEngineSetting 对象是逻辑层中的另一个类,主要负现模板引擎的全局设置
STE中所提供的接口
STE 提供者
模板提供者生成器:这个提供者提供生成器相关的功能
产生输出提供者:产生所要的输出
架构描述:正如MS所述,我们在整个架构中将这层视为核心执行的部分.提供者模式的最美丽之处在于其合适的提供者都从所包含的架构的文件中即时实现初始化,同时可以定义无限制的提供者.
程序代码: | [ 复制代码到剪贴板 ] |
<smartTemplateEngine.providers>
<parseTemplate defaultProvider="AspStyleParseTemplateProvider">
<providers>
<add name="AspStyleParseTemplateProvider"
type="SmartTemplateEngine.ImplementedProviders.AspStyleParseTemplateProvider,
SmartTemplateEngine.ImplementedProviders" />
</providers>
</parseTemplate>
<generateOutput defaultProvider="CodeDomGenerateOutputProvider">
<providers>
<add name="CodeDomGenerateOutputProvider"
type="SmartTemplateEngine.ImplementedProviders.CodeDomGenerateOutputProvider,
SmartTemplateEngine.ImplementedProviders" />
</providers>
</generateOutput>
</smartTemplateEngine.providers>
<parseTemplate defaultProvider="AspStyleParseTemplateProvider">
<providers>
<add name="AspStyleParseTemplateProvider"
type="SmartTemplateEngine.ImplementedProviders.AspStyleParseTemplateProvider,
SmartTemplateEngine.ImplementedProviders" />
</providers>
</parseTemplate>
<generateOutput defaultProvider="CodeDomGenerateOutputProvider">
<providers>
<add name="CodeDomGenerateOutputProvider"
type="SmartTemplateEngine.ImplementedProviders.CodeDomGenerateOutputProvider,
SmartTemplateEngine.ImplementedProviders" />
</providers>
</generateOutput>
</smartTemplateEngine.providers>
在这上面的部分,我在我们的提供者中加入了ASPStyleParseTemplateprovider 和 CodeDomGenerateOutputProvider
在STE Framework中,使用Enterprise Library的作用在于SME可以用它来处理缓存,日志和异常处理部分.
模板生成
这个应用程序的demo考虑了ASP/ASP.Net中的标记特别是<%=%>和<%%>同时兼容ASP和ASP.Net,下面是神奇的代码
程序代码: | [ 复制代码到剪贴板 ] |
public override Template Parse(Template template)
{
ParseManager manager = new ParseManager(template.Input,
template.Setting.Using_Libraries);
template.GeneratedCode = manager.GetParsedResult();
template.IsSuccessful = true;
return template;
}
{
ParseManager manager = new ParseManager(template.Input,
template.Setting.Using_Libraries);
template.GeneratedCode = manager.GetParsedResult();
template.IsSuccessful = true;
return template;
}
让我们更深入了解一下GetParseResult()这个方法.在生成模板中,我们使用了一些相关的正则表达式.在这个demo中,我只用了<%%>和<%=%>两种标记,但是你可以自由地实现你想要的标记.
程序代码: | [ 复制代码到剪贴板 ] |
public string GetParsedResult()
{
string modifiedcode =
GetCodeWithoutAssemblyNameSpacePropertyAndOther();
string finalcode = references +
@"
namespace
SmartTemplateEngine.ImplementedProviders
{
/// <summary>
/// Summary description for ParseManager.
/// </summary>
public class Parser
{
…
public static string Render()
{
…
#:::#RenderMethod#:::#
….
return returndata;
}
}
}";
finalcode = Regex.Replace(finalcode,
"#:::#RenderMethod#:::#",modifiedcode);
return finalcode;
}
private string GetCodeWithoutAssemblyNameSpacePropertyAndOther()
{
string tempcode = code;
//Modify this part if you want to read the
//<%@ tags also you can implement your own tags here.
tempcode = Regex.Replace(tempcode,
"(?i)<%@\\s*Property.*?%>",string.Empty);
tempcode = Regex.Replace(tempcode,
"(?i)<%@\\s*Assembly.*?%>",string.Empty);
tempcode = Regex.Replace(tempcode,
"(?i)<%@\\s*Import.*?%>",string.Empty);
tempcode = Regex.Replace(tempcode,
"(?i)<%@\\s*CodeTemplate.*?%>",string.Empty);
//For the demo I am only dealing with the <%= and <% tags
tempcode = ParseScript(tempcode);
tempcode = Regex.Replace(tempcode,@"<%=.*?%>",
new MatchEvaluator(this.RefineCalls),
RegexOptions.Singleline);
tempcode = Regex.Replace(tempcode,@"<%%>",
string.Empty,RegexOptions.Singleline);
tempcode = Regex.Replace(tempcode,@"<%[^=|@].*?%>",
new MatchEvaluator(this.RefineCodeTag),
RegexOptions.Singleline);
return tempcode;
}
{
string modifiedcode =
GetCodeWithoutAssemblyNameSpacePropertyAndOther();
string finalcode = references +
@"
namespace
SmartTemplateEngine.ImplementedProviders
{
/// <summary>
/// Summary description for ParseManager.
/// </summary>
public class Parser
{
…
public static string Render()
{
…
#:::#RenderMethod#:::#
….
return returndata;
}
}
}";
finalcode = Regex.Replace(finalcode,
"#:::#RenderMethod#:::#",modifiedcode);
return finalcode;
}
private string GetCodeWithoutAssemblyNameSpacePropertyAndOther()
{
string tempcode = code;
//Modify this part if you want to read the
//<%@ tags also you can implement your own tags here.
tempcode = Regex.Replace(tempcode,
"(?i)<%@\\s*Property.*?%>",string.Empty);
tempcode = Regex.Replace(tempcode,
"(?i)<%@\\s*Assembly.*?%>",string.Empty);
tempcode = Regex.Replace(tempcode,
"(?i)<%@\\s*Import.*?%>",string.Empty);
tempcode = Regex.Replace(tempcode,
"(?i)<%@\\s*CodeTemplate.*?%>",string.Empty);
//For the demo I am only dealing with the <%= and <% tags
tempcode = ParseScript(tempcode);
tempcode = Regex.Replace(tempcode,@"<%=.*?%>",
new MatchEvaluator(this.RefineCalls),
RegexOptions.Singleline);
tempcode = Regex.Replace(tempcode,@"<%%>",
string.Empty,RegexOptions.Singleline);
tempcode = Regex.Replace(tempcode,@"<%[^=|@].*?%>",
new MatchEvaluator(this.RefineCodeTag),
RegexOptions.Singleline);
return tempcode;
}
上面这些代码都是自动注释的,它通过发现在模板中的ASP/ASP.NET标记,之后通过GetCodeWithoutAssemblyNameSpacePropertyAndOther()方法进行处理,之后通过相关方法将#:::#RenderMethod#:::#(举例)替换返回一个之后定义好的值.
产生输出
此提供者通过从生成器中所生成的可利用的Net代码交输出我们想要的代码
程序代码: | [ 复制代码到剪贴板 ] |
public override Template Generate(Template template)
{
try
{
template.IsSuccessful = true;
LanguageType language = LanguageType.CSharp;
string entry = "Render";
string code = template.GeneratedCode.Trim();
…
CompileEngine engine = new CompileEngine(code,
language, entry);
engine.References = template.Setting.References;
engine.Run();
template.Output = engine.OutputText;
….
}
catch (Exception x)
{
template.InnerException = x;
…
}
return template;
}
{
try
{
template.IsSuccessful = true;
LanguageType language = LanguageType.CSharp;
string entry = "Render";
string code = template.GeneratedCode.Trim();
…
CompileEngine engine = new CompileEngine(code,
language, entry);
engine.References = template.Setting.References;
engine.Run();
template.Output = engine.OutputText;
….
}
catch (Exception x)
{
template.InnerException = x;
…
}
return template;
}
我曾经在A tool for dynamic compile and run of C# or VB.NET code中使用过类似这样复杂的引擎 ,之后我根据自身的需要,将部分代码修改了并使用c#编译了这部分
程序代码: | [ 复制代码到剪贴板 ] |
Assembly assembly = CreateAssembly( SourceCode );
CallEntry( assembly, EntryPoint );
CallEntry( assembly, EntryPoint );
编译引擎的核心部分主要在下面这两个方面法:
创建程序集createAssembly() 将这源码编译成功并让程序集保存在内存中
callEntry(Assembly…) 通过反射调用切入点
• CreateAssembly compiles the source code and makes the assembly in memory, and
• CallEntry(Assembly...) calls the entry point by reflection.
程序代码: | [ 复制代码到剪贴板 ] |
private Assembly CreateAssembly(string strRealSourceCode)
{
…
//Create an instance whichever code provider that is needed
CodeDomProvider codeProvider = null;
if (Language == LanguageType.CSharp)
codeProvider = new CSharpCodeProvider();
//create the language specific code compiler
ICodeCompiler compiler = codeProvider.CreateCompiler();
//add compiler parameters
CompilerParameters compilerParams = new CompilerParameters();
// you can add /optimize
compilerParams.CompilerOptions = "/target:library";
compilerParams.GenerateExecutable = false;
compilerParams.GenerateInMemory = true;
…
// add some basic references
compilerParams.ReferencedAssemblies.Add("mscorlib.dll");
//add any aditional references needed
foreach (string refAssembly in References)
{
…
compilerParams.ReferencedAssemblies.Add(refAssembly);
…
}
//actually compile the code
CompilerResults results = compiler.CompileAssemblyFromSource(
compilerParams, strRealSourceCode );
//Do we have any compiler errors
if (results.Errors.Count > 0)
{
foreach (CompilerError error in results.Errors)
LogErrMsgs("Compile Error: " + error.ErrorText );
return null;
}
//get a hold of the actual assembly that was generated
Assembly generatedAssembly = results.CompiledAssembly;
//return the assembly
return generatedAssembly;
}
private void CallEntry(Assembly assembly, string entryPoint)
{
try
{
//Use reflection to call the static Main function
Module[] mods = assembly.GetModules(false);
Type[] types = mods[0].GetTypes();
foreach (Type type in types)
{
MethodInfo mi = type.GetMethod(entryPoint, BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Static);
if (mi != null)
{
if( mi.GetParameters().Length == 1 )
{
if( mi.GetParameters()[0].ParameterType.IsArray )
{
// if Main has string [] arguments
string [] par = new string[1];
mi.Invoke(null,par);
}
}
else
{
OutputText = (string)mi.Invoke(null, null);
}
return;
}
}
…
}
…
}
{
…
//Create an instance whichever code provider that is needed
CodeDomProvider codeProvider = null;
if (Language == LanguageType.CSharp)
codeProvider = new CSharpCodeProvider();
//create the language specific code compiler
ICodeCompiler compiler = codeProvider.CreateCompiler();
//add compiler parameters
CompilerParameters compilerParams = new CompilerParameters();
// you can add /optimize
compilerParams.CompilerOptions = "/target:library";
compilerParams.GenerateExecutable = false;
compilerParams.GenerateInMemory = true;
…
// add some basic references
compilerParams.ReferencedAssemblies.Add("mscorlib.dll");
//add any aditional references needed
foreach (string refAssembly in References)
{
…
compilerParams.ReferencedAssemblies.Add(refAssembly);
…
}
//actually compile the code
CompilerResults results = compiler.CompileAssemblyFromSource(
compilerParams, strRealSourceCode );
//Do we have any compiler errors
if (results.Errors.Count > 0)
{
foreach (CompilerError error in results.Errors)
LogErrMsgs("Compile Error: " + error.ErrorText );
return null;
}
//get a hold of the actual assembly that was generated
Assembly generatedAssembly = results.CompiledAssembly;
//return the assembly
return generatedAssembly;
}
private void CallEntry(Assembly assembly, string entryPoint)
{
try
{
//Use reflection to call the static Main function
Module[] mods = assembly.GetModules(false);
Type[] types = mods[0].GetTypes();
foreach (Type type in types)
{
MethodInfo mi = type.GetMethod(entryPoint, BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Static);
if (mi != null)
{
if( mi.GetParameters().Length == 1 )
{
if( mi.GetParameters()[0].ParameterType.IsArray )
{
// if Main has string [] arguments
string [] par = new string[1];
mi.Invoke(null,par);
}
}
else
{
OutputText = (string)mi.Invoke(null, null);
}
return;
}
}
…
}
…
}
Demo 应用
这个demo应用程序有五个选项
产生代码选项卡: 在模板生成引擎的作用下,通过生成器生成一个模板,你就可以看到所生成的c#代码了.
输出选项卡:你可以看到邮输出提供者通过默认的生成模板所生成的输出代码
Libraries 选项卡:在这里构可以使用特殊的’using’关键字来产生特定的类,这可以让你使用任何的常用的libraries.Net ‘using’将会在预先设计的类结构中