一、一个典型的自定义代码生成器应用场景——消息管理
@H_502_0@无论对于怎么样的应用,我们都需要维护一系列的消息。消息的类型很多,比如验证消息、确认消息、日志消息等。我们一般会将消息储存在一个文件或者数据库中进行维护,并提供一些API来获取相应的消息项。这些API一般都是基于消息的ID来获取的,换句话说,消息获取的方式是以一种“弱类型”的编程方式实现的。如果我们能够根据消息存储的内容动态地生成相应的C#或者VB.NET代码,那么我们就能够以一种强类型的方式来获取相应的消息项了。 @H_502_0@比如说,现在我们定义了如下一个MessageEntry类型来表示一个消息条目。为了简单,我们尽量简化MessageEntry的定义,仅仅保留三个属性Id、Value和Category。Category表示该消息条目所属的类型,你可以根据具体的需要对其分类(比如根据模块名称或者Severity等)。Value是一个消息真实的内容,可以包含一些占位符({0},{1},…{N})。通过指定占位符对用的值,最中格式化后的文本通过Format返回。1: public class MessageEntry
2: {
3: public string Id { get; private set; }
4: public string Value { get; private set; }
5: public string Category { get; private set; }
6: public MessageEntry(string id,string value,string category)
7: {
8: this.Id = id;
9: this.Value = value;
10: this.Category = category;
11: }
12: public string Format(params object[] args)
13: {
14: return string.Format(this.Value,args);
15: }
16: }
1: <?xml version="1.0" encoding="utf-8" ?>
2: <messages>
3: <message id="MandatoryField" value="The {0} is mandatory." category="Validation"/>
4: <message id="GreaterThan" value="The {0} must be greater than {1}." category="Validation"/>
5: <message id="ReallyDelete" value="Do you really want to delete the {0}." category="Confirmation"/>
6: </messages>
1: namespace Artech.CodeDomGenerator
2: {
3: public class Messages
4: {
5: public class Validation
6: {
7: public static Artech.CodeDomGenerator.MessageEntry MandatoryField = new Artech.CodeDomGenerator.MessageEntry(@H_669_301@"MandatoryField",@H_669_301@"The {0} is mandatory.",@H_669_301@"Validation");
8: public static Artech.CodeDomGenerator.MessageEntry GreaterThan = new Artech.CodeDomGenerator.MessageEntry(@H_669_301@"GreaterThan",@H_669_301@"The {0} must be greater than {1}.",@H_669_301@"Validation");
9: }
10: public class Confirmation
11: {
12: public static Artech.CodeDomGenerator.MessageEntry ReallyDelete = new Artech.CodeDomGenerator.MessageEntry(@H_669_301@"ReallyDelete",@H_669_301@"Do you really want to delete the {0}.",@H_669_301@"Confirmation");
13: }
14: }
15: }
1: The User Name is mandatory.
2: The Age must be greater than 18.
3: Do you really want to delete the Order record.
二、通过CodeDom实现动态代码生成
@H_502_0@CodeDOM 提供了表示许多常见的源代码元素类型的类型。您可以设计一个生成源代码模型的程序,使用CodeDOM 元素构成一个对象图。而这个对象图包含C#或者VB.NET代码包含的基本元素:命名空间、类型、类型成员(方法、属性、构造函数、事件等),并且包括方法实现的具体语句(Statement)。也就是说它的结构就是对一个具体.vb或者.cs文件代码的反映。在这里我不会具体介绍CodeDOM体系结构,有兴趣的读者可以参与MSDN官方文档。 @H_502_0@CodeDOM最终体现出来的是一个叫做CodeCompileUnit对象,这个对象通过如下定义的MessageCodeGnerator的BuildCodeObject方法返回。下面给出了生成CodeCompileUnit的全部实现,即使你对CodeDOM完全不了解,结合上面给出的保存消息的XML和我们最终期望的C#代码的结构,相信也能够看懂整个实现逻辑。 @H_502_0@总的来说,BuildCodeObject方法的目的就是一个将XML转换成CodeCompileUnit对象。首先在BuildCodeObject方法中,添加了一个命名空间(Artech.CodeDomGenerator),并在该命名空间中定义了一个Messages的类。在Messages类会为每一个消息类别定义一个嵌套类,类型的名称就是消息类别的名称(比如Validation、Confirmation等)。我们具体的MessageEntry通过公共静态属性的形式进行定义,并且采用Inline的方式进行初始化。1: public class MessageCodeGenerator
2: {
3: public CodeCompileUnit BuildCodeObject(XmlDocument messages)
4: {
5: var codeObject = new CodeCompileUnit();
6: var codeNamespace = new CodeNamespace(@H_669_301@"Artech.CodeDomGenerator");
7: codeObject.Namespaces.Add(codeNamespace);
8: var codeType = new CodeTypeDeclaration(@H_669_301@"Messages");
9: codeNamespace.Types.Add(codeType);
10: GenerateCatetoryClasses(codeType,messages);
11: return codeObject;
12: }
13:
14: private void GenerateCatetoryClasses(CodeTypeDeclaration root,XmlDocument messageDoc)
15: {
16: var messageEntries = messageDoc.GetElementsByTagName(@H_669_301@"message").Cast<XmlElement>();
17: var categories = (from element in messageEntries
18: select element.Attributes[@H_669_301@"category"].Value).Distinct();
19:
20: foreach (var category in categories)
21: {
22: var categoryType = new CodeTypeDeclaration(category);
23: root.Members.Add(categoryType);
24:
25: foreach (var element in messageDoc.GetElementsByTagName(@H_669_301@"message").Cast<XmlElement>().
26: Where(element => element.Attributes[@H_669_301@"category"].Value == category))
27: {
28: GenerateMessageProperty(element,categoryType);
29: }
30: }
31: }
32:
33: private void GenerateMessageProperty(XmlElement messageEntry,CodeTypeDeclaration type)
34: {
35: string id = messageEntry.Attributes[@H_669_301@"id"].Value;
36: string value = messageEntry.Attributes[@H_669_301@"value"].Value;
37: string categotry = messageEntry.Attributes[@H_669_301@"category"].Value;
38:
39: var field = new CodeMemberField(typeof(MessageEntry),id);
40: type.Members.Add(field);
41: field.Attributes = MemberAttributes.Public | MemberAttributes.Static;
42: field.InitExpression = new CodeObjectCreateExpression(
43: typeof(MessageEntry),
44: new CodePrimitiveExpression(id),
45: new CodePrimitiveExpression(value),
46: new CodePrimitiveExpression(categotry));
47: }
48: }
三、通过CodeDomProvider转化给予某种语言的代码
@H_502_0@CodeCompileUnit最终体现的代码的结构,但是CodeCompileUnit本身是不基于某种具体的编程语言的,也就是说CodeCompileUnit是语言中性的。最终我们需要另一个对象将CodeCompileUnit转换成基于某种编程的语言的代码:CodeDomProvider。 @H_502_0@在上面的代码中,我们利用上面定义的MessageCodeGenerator类型,将上述我们提到的包含消息定义的XML文件转换成CodeDomProvider对象。最终通过CodeDomProvider将其分别转换成C#代码和VB。NET代码。1: var generator = new MessageCodeGenerator();
2: var messageDoc = new XmlDocument();
3: messageDoc.Load(@H_669_301@"Messages.xml");
4: var codeObject = generator.BuildCodeObject(messageDoc);
5: CodeDomProvider provider = CodeDomProvider.CreateProvider(@H_669_301@"CSharp");
6: CodeGeneratorOptions options = new CodeGeneratorOptions();
7: using (StreamWriter writer = new StreamWriter(@H_669_301@"messages.cs"))
8: {
9: provider.GenerateCodeFromCompileUnit(codeObject,writer,options);
10: }
11:
12: provider = CodeDomProvider.CreateProvider(@H_669_301@"VisualBasic");
13: using (StreamWriter writer = new StreamWriter(@H_669_301@"messages.vb"))
14: {
15: provider.GenerateCodeFromCompileUnit(codeObject,options);
16: }
17:
18: Process.Start(@H_669_301@"messages.cs");
19: Process.Start(@H_669_301@"messages.vb");
1: //------------------------------------------------------------------------------
2: // <auto-generated>
3: // This code was generated by a tool.
4: // Runtime Version:4.0.30319.1
5: //
6: // Changes to this file may cause incorrect behavior and will be lost if
7: // the code is regenerated.
8: // </auto-generated>
9: //------------------------------------------------------------------------------
10:
11: namespace Artech.CodeDomGenerator {
12:
13:
14: public class Messages {
15:
16: public class Validation {
17:
18: public static Artech.CodeDomGenerator.MessageEntry MandatoryField = new Artech.CodeDomGenerator.MessageEntry(@H_669_301@"MandatoryField",@H_669_301@"Validation");
19:
20: public static Artech.CodeDomGenerator.MessageEntry GreaterThan = new Artech.CodeDomGenerator.MessageEntry(@H_669_301@"GreaterThan",@H_669_301@"Validation");
21: }
22:
23: public class Confirmation {
24:
25: public static Artech.CodeDomGenerator.MessageEntry ReallyDelete = new Artech.CodeDomGenerator.MessageEntry(@H_669_301@"ReallyDelete",@H_669_301@"Confirmation");
26: }
27: }
28: }
1: '------------------------------------------------------------------------------
2: ' <auto-generated>
3: ' This code was generated by a tool.
4: ' Runtime Version:4.0.30319.1
5: '
6: ' Changes to this file may cause incorrect behavior and will be lost if
7: ' the code is regenerated.
8: ' </auto-generated>
9: '------------------------------------------------------------------------------
10:
11: Option Strict Off
12: Option Explicit On
13:
14:
15: Namespace Artech.CodeDomGenerator
16:
17: Public Class Messages
18:
19: Public Class Validation
20:
21: Public Shared MandatoryField As Artech.CodeDomGenerator.MessageEntry = New Artech.CodeDomGenerator.MessageEntry(@H_669_301@"MandatoryField",@H_669_301@"Validation")
22:
23: Public Shared GreaterThan As Artech.CodeDomGenerator.MessageEntry = New Artech.CodeDomGenerator.MessageEntry(@H_669_301@"GreaterThan",@H_669_301@"Validation")
24: End Class
25:
26: Public Class Confirmation
27:
28: Public Shared ReallyDelete As Artech.CodeDomGenerator.MessageEntry = New Artech.CodeDomGenerator.MessageEntry(@H_669_301@"ReallyDelete",@H_669_301@"Confirmation")
29: End Class
30: End Class
31: End Namespace