希望通過此文讓更多地朋友快速入門 MEF
之前在使用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 基本内容已講解結束,如果有遺漏也請博友留言指出。
文章中很多都是白話,非官方語言,怎麼了解的就怎麼寫,如果有不妥之處,還望各位博友指出。
新年快樂