原文信息
系统需求:
概观
MyGeneration目前使用了两种脚本引擎,一个是Microsoft Scripting Engine,它提供了JScript和VBScript的生成支持;另一个是 DotNetScript ,它提供了 VB.NET 和 C# 的支持。 DotNetScript 不像Microsoft Scripting Control那样的真正的脚本。实际上,它在运行时编译代码,然后利用编译后的.NET Assembly执行代码。这个教程通过例子说明使用 DotNetScript 开发MyGeneration模板的赞成与反对的理由。在这个例子中,我将使用C#和MS SQL中附带的Northwind数据库。
创建一个新的C#模板
研究默认的模板代码
默认的模板主体代码: 不像 JScript 或VBScript,C#的的模板代码相对较少。这个默认的代码是非常重要的。一个C#模板必须有一个继承了 DotNetScriptTemplate 的名为 GeneratedTemplate 的类。 MyGeneration 通过实例化 GeneratedTemplate ,并调用Render方法开始生成过程。几乎大部分的模板开发工作都在Render方法里面完成。试试执行这个模板,你将会看到literal content goes here作为输出出现。
1 <% 2 public class GeneratedTemplate : DotNetScriptTemplate 3 { 4 public GeneratedTemplate(ZeusContext context) : base(context) {} 5 6 7 public override void Render() 8 { 9 %> 10 Literal content goes here. 11 <% 12 } 13 14 15 } 16 %>
默认的UI接口代码: 在默认的模板的接口代码中,同样需要一个名为GeneratedGui的,继承DotNetScriptGui的强制类。如同模板主体代码的Render方法一样, MyGeneration 将调用Setup方法开始显示用户接口并收集输入。
1 public class GeneratedGui : DotNetScriptGui 2 { 3 public GeneratedGui(ZeusContext context) : base(context) {} 4 5 6 public override void Setup() 7 { 8 } 9 }
获取输入:用户接口的代码块 在这个例子中,用户将通过接口选择一个表。这个是接口代码块的擅长的工作。用户接口获取的输入数据,将在模板主体代码中用来生成代码。
使用MyGeneration的接口代码:
1 public class GeneratedGui : DotNetScriptGui 2 { 3 public GeneratedGui(ZeusContext context) : base(context) {} 4 5 6 public override void Setup() 7 { 8 ui.Title = ".NetScript C# Sample: Java Class"; 9 ui.Width = 340; 10 ui.Height = 200; 11 12 13 // Setup Database selection combobox. 14 GuiLabel label_d = ui.AddLabel("lblDatabases", "Select a database:", "Select a database in the dropdown below."); 15 GuiComboBox cmbDatabases = ui.AddComboBox("databaseName", "Select a database."); 16 17 18 // Setup Tables selection multi-select listbox. 19 GuiLabel label_t = ui.AddLabel("lblTables", "Select table:", "Select table from the combobox below."); 20 GuiComboBox cmbTables = ui.AddComboBox("tableName", "Select a table."); 21 22 23 // bind data to the controls 24 cmbDatabases.BindData(MyMeta.Databases); 25 cmbDatabases.SelectedValue = MyMeta.DefaultDatabase.Name; 26 cmbTables.BindData( MyMeta.Databases[cmbDatabases.SelectedValue].Tables ); 27 28 // Attach the onchange event to the cmbDatabases control. 29 cmbDatabases.AttachEvent("onchange", "cmbDatabases_onchange"); 30 31 ui.ShowGui = true; 32 } 33 34 35 public void cmbDatabases_onchange(GuiComboBox control) 36 { 37 GuiComboBox cmbDatabases = ui["databaseName"] as GuiComboBox; 38 GuiComboBox cmbTables = ui["tableName"] as GuiComboBox; 39 40 41 cmbTables.BindData( MyMeta.Databases[cmbDatabases.SelectedValue].Tables ); 42 } 43 }
使用.NET Windows Form API的接口代码:
下面的代码是不使用 MyGeneration API的替换方案,它可以达到与上面的代码同样的目的。
1 <%#REFERENCE System.Windows.Forms.dll %> 2 <%#NAMESPACE System.Windows.Forms %> 3 public class GeneratedGui : DotNetScriptGui 4 { 5 public GeneratedGui(ZeusContext context) : base(context) {} 6 7 8 public override void Setup() 9 { 10 AcquireInputForm form = new AcquireInputForm(MyMeta, input); 11 12 13 if (form.ShowDialog() != DialogResult.OK) 14 { 15 ui.IsCanceled = true; 16 } 17 } 18 } 19 20 21 public class AcquireInputForm : Form 22 { 23 private ComboBox cboDatabases = new ComboBox(); 24 private ComboBox cboTables = new ComboBox(); 25 private Button btnOk = new Button(); 26 private dbRoot meta; 27 private IZeusInput input; 28 29 30 public AcquireInputForm(dbRoot mymeta, IZeusInput zin) 31 { 32 this.meta = mymeta; 33 this.input = zin; 34 35 36 this.BindComboBox(cboDatabases, meta.Databases); 37 cboDatabases.SelectedItem = meta.DefaultDatabase.Name; 38 cboDatabases.Top = 10; cboDatabases.Left = 10; cboDatabases.Width = 200; 39 cboDatabases.SelectedIndexChanged += new EventHandler(cboDatabases_SelectedIndexChanged); 40 41 42 this.BindComboBox(cboTables, meta.DefaultDatabase.Tables); 43 cboTables.Top = 50; cboTables.Left = 10; cboTables.Width = 200; 44 45 46 btnOk.Text = "Ok"; 47 btnOk.Top = 100; btnOk.Left = 10; btnOk.Width = 200; 48 btnOk.Click += new EventHandler(btnOk_Click); 49 50 51 this.Controls.AddRange( new Control[] {cboDatabases, cboTables, btnOk} ); 52 this.Text = ".NetScript C# Sample: Java Class"; 53 this.Width = 230; 54 this.Height = 160; 55 } 56 57 58 public void cboDatabases_SelectedIndexChanged(object sender, EventArgs args) 59 { 60 this.BindComboBox( 61 cboTables, 62 meta.Databases[ cboDatabases.SelectedItem.ToString() ].Tables 63 ); 64 } 65 66 67 public void btnOk_Click(object sender, EventArgs args) 68 { 69 if ((cboDatabases.SelectedIndex >= 0) && 70 (cboTables.SelectedIndex >= 0)) 71 { 72 input["databaseName"] = cboDatabases.SelectedItem.ToString(); 73 input["tableName"] = cboTables.SelectedItem.ToString(); 74 this.DialogResult = DialogResult.OK; 75 this.Close(); 76 } 77 else 78 { 79 MessageBox.Show("Fill out the required fields.. PLEASE??"); 80 } 81 } 82 83 84 private void BindComboBox(ComboBox cbo, IEnumerable myMetaCollection) 85 { 86 cbo.Items.Clear(); 87 foreach (INameValueItem item in myMetaCollection) 88 { 89 cbo.Items.Add(item.ItemValue); 90 } 91 } 92 }
模板主体 模板主体是生成代码的主要执行地。下面讲解了我如何生成代码的步骤。
1、将期望输出的代码放入到Render的方法中如下的代码,你将看到我将要生成的类。这几乎都是当我要生成一个模板是必做的第一件事情。
1 <% 2 public class GeneratedTemplate : DotNetScriptTemplate 3 { 4 public GeneratedTemplate(ZeusContext context) : base(context) {} 5 6 7 public override void Render() 8 { 9 string databaseName = input["databaseName"].ToString(); 10 string tableName = input["tableName"].ToString(); 11 %> 12 /* 13 * Employee.java 14 * 15 * Created on September 23, 2002, 12:59 PM 16 */ 17 18 19 package com.mygeneration.sample; 20 21 22 import java.sql.*; 23 24 25 import com.mygeneration.businessobjects.*; 26 import com.mygeneration.dataaccess.*; 27 28 29 public class Employee extends BizObj 30 { 31 public Employee() 32 { 33 } 34 35 36 // EmployeeID 37 public String getEmployeeID() 38 { 39 return getString(EmployeeSchema.EmployeeID.getFieldName()); 40 } 41 42 43 public void setEmployeeID(String employeeID) 44 { 45 setString(EmployeeSchema.EmployeeID.getFieldName(), employeeID); 46 } 47 }<% 48 } 49 50 51 } 52 %>
2、添加动态代码把动态的代码添加到模板中,替换掉类名、属性名称以及数据类型。
1 <% 2 public class GeneratedTemplate : DotNetScriptTemplate 3 { 4 public GeneratedTemplate(ZeusContext context) : base(context) {} 5 6 7 public override void Render() 8 { 9 string databaseName = input["databaseName"].ToString(); 10 string tableName = input["tableName"].ToString(); 11 12 13 IDatabase database = MyMeta.Databases[databaseName]; 14 ITable table = database.Tables[tableName]; 15 %>/* 16 * <%= table.Alias %>.java 17 * 18 * Created on <%= DateTime.Now.ToString() %> 19 */ 20 21 22 package com.mygeneration.sample; 23 24 25 import java.sql.*; 26 27 28 import com.mygeneration.businessobjects.*; 29 import com.mygeneration.dataaccess.*; 30 31 32 public class <%= table.Alias %> extends BizObj 33 { 34 public <%= table.Alias %>() 35 { 36 } 37 <% 38 foreach (IColumn column in table.Columns) 39 { 40 string datatype = this.GetJavaType(column); 41 %> 42 // <%= column.Alias %> 43 public <%= datatype %> get<%= column.Alias %>() 44 { 45 return get<%= datatype %>(<%= table.Alias %>Schema.<%= column.Alias %>.getFieldName()); 46 } 47 48 49 public void set<%= column.Alias %>(<%= datatype %> m_<%= column.Alias %>) 50 { 51 set<%= datatype %>(<%= table.Alias %>Schema.<%= column.Alias %>.getFieldName(), m_<%= column.Alias %>); 52 } 53 <% 54 } 55 %> 56 }<% 57 } 58 59 60 private string GetJavaType(IColumn column) 61 { 62 string sqlServerType = column.DataTypeName; 63 int charLength = column.CharacterMaxLength; 64 65 66 switch (sqlServerType) 67 { 68 case "bit": 69 return "Boolean"; 70 case "decimal": 71 case "float": 72 case "numeric": 73 case "money": 74 case "smallmoney": 75 case "real": 76 return "Decimal"; 77 case "tinyint": 78 case "smallint": 79 case "int": 80 case "bigint": 81 return "Integer"; 82 case "smalldatetime": 83 case "datetime": 84 return "Timestamp"; 85 case "varchar": 86 case "char": 87 case "nvarchar": 88 case "nchar": 89 case "text": 90 if (charLength == 1) 91 return "Character"; 92 else 93 return "String"; 94 default: 95 return "Object"; 96 } 97 } 98 } 99 %>
总结
使用 DotNetScript ,你将会把 MyGeneration 的模板开发提升到一个新的水平,提供更强大的功能以及能开发更复杂的系统。