之前在使用Prism框架時接觸到了可擴展性框架MEF(Managed Extensibility Framework),體驗到MEF帶來的極大的便利性與可擴展性。
此篇將編寫一個可組合的應用程序,幫助大家快速熟悉MEF並將其應用於實際項目中。
有關MEF中的名詞含義及功能實現,請大家移步:火車票
介紹下將要編寫的Demo程序(下圖),使用winform開發。
- 通過組合操作,程序動態加載可用部件進行組合操作。
- 通過解體操作,程序卸載所加載的所有部件。
新建項目后需引用程序集:
System.ComponentModel.Composition
主程序的核心代碼如下:
public partial class Form1 : Form, IPartImportsSatisfiedNotification { [ImportMany(AllowRecomposition = true)] private IEnumerable<Lazy<IPlugin, IPluginMetadata>> plugins; private AggregateCatalog catalog; private CompositionContainer container; public Form1() { InitializeComponent(); if (catalog == null) catalog = new AggregateCatalog(); this.container = new CompositionContainer(catalog); this.container.ComposeParts(this); } #region Implementation of IPartImportsSatisfiedNotification public void OnImportsSatisfied() { flowLayoutPanel1.Controls.Clear(); if (plugins != null && plugins.Count() != 0) { plugins.ToList().ForEach((a) => { Button btn = new Button(); btn.Cursor = System.Windows.Forms.Cursors.Hand; btn.Width = 100; btn.Height = 50; btn.Text = a.Metadata.ThePluginName; btn.Click += (d, b) => { a.Value.Run(); }; flowLayoutPanel1.Controls.Add(btn); }); } } #endregion public void CompositionAction() { catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly())); } ...... }
1. IPartImportsSatisfiedNotification接口 : 在組件導入完成后,調用該接口中的方法(OnImportsSatisfied)。
2. MEF中最常用目錄有三種:程序集目錄(AssemblyCatalog),文件目錄(DirectoryCatalog),聚合目錄(AggregateCatalog)
程序集目錄(AssemblyCatalog): 顧名思義可以向目錄中添加程序集已存在類型中尋找可用於導入的部件。
var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
文件夾目錄(DirectoryCatalog):從文件夾中尋找可用於導入的部件
var catalog = new DirectoryCatalog("Extensions");
聚合目錄(AggregateCatalog):可以向聚合目錄包含上述兩種方式
var catalog = new AggregateCatalog( new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()), new DirectoryCatalog("Extensions"));
3. CompositionContainer : 組合容器,管理部件的組合並提供了一系列用於創建部件實例擴展方法等,詳細資料
4. 目錄與容器的一般使用方法:
var catalog = new AggregateCatalog(); var container = new CompositionContainer(catalog); container.ComposeParts(this);
5. 導入:ImportAttribute 與 ImportManyAttribute
用於以下三種用途:字段,屬性,方法
[import] private IData _data; [import] public IData Data{set;get;} [import] public Action ClickAction;
ImportManyAttribute : 通過組合容器將所有符合契約的導出進行填充 (真別扭,說白了就是導入多個)
[ImportMany] private IEnumerable<IPlugin> plugins;
AllowRecomposition : 是否允許重組
AllowRecomposition = true : 比如在程序運行的過程中,動態向聚合目錄中添加可導出的部件,可以引發重組操作
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
需要注意的是:DirectoryCatalog 不會引發重組操作,可通過Refresh方法指定重組的策略。
6. System.Lazy<T> : MEF提供延遲導入。
下面來看一下,插件式如何實現的:
[ExportPluginAttribute(typeof(IPlugin), ThePluginName = "TheFristPlugin")] public class TheFristPlugin : IPlugin { public TheFristPlugin() { this.TheName = "TheFristPlugin"; } #region Implementation of IPlugin public string TheName { get; set; } public void Run() { MessageBox.Show("TheFristPlugin"); } #endregion }
1. 簡單說一下:導入與導出之前的關系
一個基於MEF開發的可擴展的程序,在容器中必然有很多的導出(Export),而這些Export又是怎么樣找到自己的歸宿呢。
Export 與 Import 依靠一種契約,來確定對方是否是自己的兄弟,說白了就是接口,比如上述程序所定義的IPlugin接口
public interface IPlugin { void Run(); }
使用 ExportAttribute 特性導出:
[Export("count")] public int count{ get{return 0;} } [Export(typeof(Action))] public void SendMsg(){return;} [Export] public class Person{}
有一個需求,主程序要求插件必須要指定插件名稱:
1. 在IPlugin接口中定義:Name字段
2. 使用元數據
3. 使用自定義導出特性(與第二種方案類似)
如何使用元數據?
1.定義元數據視圖,此處視圖使用接口類型
public interface IPluginMetadata { string ThePluginName { get; } }
2. 導出部件時,使用ExportMetaData特性
[ExportMetadata("ThePluginName", "TheFivePlugin")] [Export(typeof(mef.test.wform.Interface.IPlugin))] public class TheFivePlugin : mef.test.wform.Interface.IPlugin { public void Run() { MessageBox.Show("TheFivePlugin"); } }
3. 導入元數據
[ImportMany(AllowRecomposition = true)] private IEnumerable<Lazy<IPlugin, IPluginMetadata>> plugins;
4. 訪問元數據
Lazy<T,TMetadata>.Value.Metadata
結束
到此為止,MEF 基本內容已講解結束,如果有遺漏也請博友留言指出。
文章中很多都是白話,非官方語言,怎么理解的就怎么寫,如果有不妥之處,還望各位博友指出。
新年快樂