[转载]C# 构建可扩展的应用程序(插件) – suxin – 博客园.
构建可扩展的应用程序,特别是对于WinForm应用程序是特别有好处的。我们知道,企业的需求是瞬息万变的,企业在使用软件的过程中,很可能对于现有的 需求有变动甚至是提出新的需求来,可是我们的软件已经部署在企业的各个客户端中,要想将根据企业新的需求编写的模块集成到现有程序中去,我们必须重新编译 整个软件,然后打包再进行重新部署,这无疑有非常大的工作量。怎样才能将新编写的模块集成到现有程序中去,而又不用重新编译整个应用程序?这就是我们接下 来要讨论的话题。
利用C# 构建可扩展的应用程序,就是利用.Net的反射机制以及特性编程实现,原理我就不介绍了,很多C#的书籍都说的很清楚,接下来,我将演示如何搭建可扩展的应用程序。
一、首先建立一个类库IplugTypes
该类库定义了插件程序必须遵循的接口Iplug和一个自定义特性,在该类库中导入System.Windows.Forms的引用,代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Windows.Forms; 6 7 namespace IplugTypes 8 { 9 /// <summary> 10 /// 插件必须继承该接口规范 11 /// </summary> 12 public interface Iplug 13 { 14 void FormShow(Form mainForm); 15 } 16 [AttributeUsage(AttributeTargets.Class)] 17 public sealed class AssemblyInfoAndCompanyInfoAttribute : System.Attribute 18 { 19 /// <summary> 20 /// 程序集名称(不包括扩展名) 21 /// </summary> 22 public string AssemblyName { get; set; } 23 /// <summary> 24 /// 程序集版本 25 /// </summary> 26 public string AssemblyVersion { get; set; } 27 /// <summary> 28 /// 公司名称 29 /// </summary> 30 public string CompanyName { get; set; } 31 /// <summary> 32 /// 菜单名(在承载的主程序中要显示的名称) 33 /// </summary> 34 public string MenuName { get; set; } 35 public AssemblyInfoAndCompanyInfoAttribute() 36 { 37 38 } 39 40 } 41 }
二、建立WinForm插件程序
建立一个WinForm窗体程序,引入上面建立的IplugTypes类库的引用,如下图:
在该窗体类中继承IPlug接口,在Iplug接口的FormShow方法中实例化本窗体,同时用AssemblyInfoAndCompanyInfo自定义属性类描述该窗体类,代码如下:
1 using System.Drawing; 2 using System.Linq; 3 using System.Text; 4 using System.Windows.Forms; 5 using IplugTypes; 6 7 namespace WinFormIPlug 8 { 9 [AssemblyInfoAndCompanyInfo(AssemblyName = "WinFormIPlug",AssemblyVersion="1.0.0.0",CompanyName="DTXY",MenuName="C#插件")] 10 public partial class Form1 : Form,Iplug 11 { 12 public Form1() 13 { 14 InitializeComponent(); 15 } 16 17 private void button1_Click(object sender, EventArgs e) 18 { 19 MessageBox.Show("这是一个插件程序!"); 20 } 21 22 void Iplug.FormShow(Form mainForm) 23 { 24 Form1 fm = new Form1(); 25 fm.MdiParent = mainForm; 26 fm.Show(); 27 // throw new NotImplementedException(); 28 } 29 } 30 }
将该窗体的输出类型设置为类库,然后进行编译,如下图
三、构建可扩展的应用程序
建立一个名称为WindowsFormMain的窗体应用程序,添加menuStrip控件,并设置该窗体的IsMdiContainer属性为True,指示该窗体为MDI对文档父窗体。如下图:
引入对程序集IplugTypes的引用,导入System.Reflection的命名空间。然后,添加FrmAdd窗体,用于导入插件程序,如下图:
同样在FrmAdd窗体类中引入对程序集IplugTypes的引用,导入System.Reflection的命名空间,该窗体类的代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using IplugTypes; 10 using System.Reflection; 11 using System.IO; 12 13 namespace WindowsFormMain 14 { 15 public partial class FrmAdd : Form 16 { 17 public FrmAdd() 18 { 19 InitializeComponent(); 20 } 21 /// <summary> 22 /// 插件加载路径 23 /// </summary> 24 public string Path { get; private set; } 25 26 private void btnBrowse_Click(object sender, EventArgs e) 27 { 28 29 if (this.openFileDialog1.ShowDialog() == DialogResult.OK) 30 { 31 if (this.openFileDialog1.SafeFileName.Split('.')[1].ToUpper() != "DLL") 32 { 33 MessageBox.Show("您选择的不是动态类型库,扩展名为.DLL!"); 34 return; 35 } 36 string strFilePath = this.openFileDialog1.FileName; 37 Path = strFilePath; 38 this.textBox1.Text = Path; 39 if (!LoadAssembly(strFilePath)) 40 { 41 MessageBox.Show("插件加载失败,请确认插件是否支持Iplug接口!"); 42 } 43 } 44 } 45 private bool LoadAssembly(string strFilePath) 46 { 47 bool isRight = false; 48 try 49 { 50 Assembly asm = Assembly.LoadFrom(strFilePath); 51 var types = from t in asm.GetTypes() 52 where t.IsClass && t.GetInterface("Iplug") != null 53 select t; 54 if (types.Count() <= 0) 55 { 56 return isRight; 57 } 58 foreach (Type item in types) 59 { 60 DisplayAssemblyInfo(item); 61 } 62 isRight = true; 63 } 64 catch (Exception ex) 65 { 66 MessageBox.Show(ex.Message); 67 return isRight; 68 } 69 return isRight; 70 } 71 private void DisplayAssemblyInfo(Type t) 72 { 73 var asmInfo = from n in t.GetCustomAttributes(false) 74 where n.GetType() == typeof(AssemblyInfoAndCompanyInfoAttribute) 75 select n; 76 foreach (AssemblyInfoAndCompanyInfoAttribute item in asmInfo) 77 { 78 string[] strItems = { t.Assembly.GetName().Name, item.AssemblyVersion, item.CompanyName }; 79 ListViewItem lv = new ListViewItem(strItems); 80 this.listView1.Items.Add(lv); 81 } 82 } 83 84 private void btnSub_Click(object sender, EventArgs e) 85 { 86 if (string.IsNullOrEmpty(Path)) 87 { 88 MessageBox.Show("没有任何可导入的插件程序!"); 89 return; 90 } 91 FileInfo fi = new FileInfo(Path); 92 fi.CopyTo(Application.StartupPath + "\\" + fi.Name, true); 93 DialogResult = DialogResult.OK; 94 } 95 96 private void btnCancel_Click(object sender, EventArgs e) 97 { 98 DialogResult = DialogResult.Cancel; 99 } 100 } 101 }
在主窗体Form1中的MenuScript控件的File->Add-in..菜单中实例话FrmAdd窗体并以模式窗体的方式显示出来,代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using IplugTypes; 10 using System.Reflection; 11 12 namespace WindowsFormMain 13 { 14 public partial class Form1 : Form 15 { 16 public Form1() 17 { 18 InitializeComponent(); 19 } 20 21 private void Form1_Load(object sender, EventArgs e) 22 { 23 24 } 25 26 private void addInToolStripMenuItem_Click(object sender, EventArgs e) 27 { 28 FrmAdd fm = new FrmAdd(); 29 fm.ShowDialog(); 30 } 31 } 32 }
现在,插件已导入到应用程序的启动目录下,那怎样在主窗体的MenuScript菜单中创建一个插件窗体的菜单并且单击该菜单会显示出插件窗体来? 我们接下来继续讨论:
我的设计思路是:维护一个XML文件,该XML文件用于记录主窗体MenuScript的菜单项,主窗体在启动时读取该XML文件,然后动态创建菜单。
当然为了演示,我这里只是手动创建一个名叫:Iplug.xml的文件,并且保存在应用程序的启动目录下。读者完全可以在导入插件时,用程序自动创建。该XML文件的格式如下:
1 <?xml version="1.0" encoding="gb2312"?> 2 <root> 3 <menu name="C#插件" assemblyName="WinFormIPlug"> 4 </menu> 5 </root>
当然如果有多个插件,可建立多个<menu>节点,这里<menu>节点的 name属性是该插件要在主程序中显示的菜单名,assemblyName属性是插件程序集的名称(不包括.dll的扩展名)。
建立好该XML文件后,保存到主程序的启动目录下,然后在主窗体启动时读取该XML文件,动态的创建菜单。主窗体的代码如下:
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using IplugTypes; 10 using System.Reflection; 11 using System.Xml; 12 using System.Xml.Linq; 13 using System.IO; 14 15 namespace WindowsFormMain 16 { 17 public partial class Form1 : Form 18 { 19 public Form1() 20 { 21 InitializeComponent(); 22 } 23 24 private void Form1_Load(object sender, EventArgs e) 25 { 26 try 27 { 28 this.AddMenus(); 29 } 30 catch (Exception ex) 31 { 32 MessageBox.Show(ex.Message, "提示", MessageBoxButtons.OK); 33 } 34 } 35 /// <summary> 36 /// 加载程序菜单 37 /// </summary> 38 private void AddMenus() 39 { 40 string strFilePath = Application.StartupPath + "\\" + "Iplug.xml"; 41 if (!File.Exists(strFilePath)) 42 { 43 XmlDocument dom = new XmlDocument(); 44 XmlDeclaration declar = dom.CreateXmlDeclaration("1.0", "utf-8", null); 45 dom.AppendChild(declar); 46 XmlElement root = dom.CreateElement("root"); 47 dom.AppendChild(root); 48 dom.Save(strFilePath); 49 } 50 else 51 { 52 XDocument xd = XDocument.Load(strFilePath); 53 54 var menus = from n in xd.Descendants("menu") 55 select n; 56 if (menus.Count() <= 0) 57 { 58 return; 59 } 60 foreach (var item in menus) 61 { 62 string menuName = item.Attribute("name").Value; 63 ToolStripMenuItem ts = (ToolStripMenuItem)this.menuStrip1.Items.Add(menuName); 64 ts.Tag = item.Attribute("assemblyName").Value; 65 ts.Click += new EventHandler(ts_Click); 66 } 67 } 68 } 69 void ts_Click(object sender, EventArgs e) 70 { 71 try 72 { 73 ToolStripMenuItem tool = (ToolStripMenuItem)sender; 74 string assemblyName = tool.Tag.ToString(); 75 Assembly asm = Assembly.Load(assemblyName); 76 var types = from n in asm.GetTypes() 77 where n.IsClass && n.GetInterface("Iplug") != null 78 select n; 79 if (types.Count() <= 0) 80 { 81 return; 82 } 83 foreach (Type t in types) 84 { 85 Iplug plug = (Iplug)Activator.CreateInstance(t); 86 plug.FormShow(this); 87 } 88 } 89 catch (Exception ex) 90 { 91 MessageBox.Show(ex.Message); 92 } 93 //throw new NotImplementedException(); 94 } 95 96 private void addInToolStripMenuItem_Click(object sender, EventArgs e) 97 { 98 FrmAdd fm = new FrmAdd(); 99 fm.ShowDialog(); 100 } 101 } 102 }
到现在为止,可扩展的应用程序已经构建完毕,希望对大家有所帮助。如有疑问请发邮件suxinzh@gmail.com联系本人。