MEF 在 WPF 中的简单应用

MEF 的开发模式主要适用于插件化的业务场景中,C/S 和 B/S 中都有相应的使用场景,其中包括但不限于 ASP.NET MVCASP WebFormsWPFUWP 等开发框架。当然,DotNet Core 也是支持的。

在上篇文章中,笔者大致讲述如果在控制台程序中创建一个简单的 MEF 应用程序。如果有读者不太清楚,可点击 MEF 插件式开发 - 小试牛刀 进行查看。在本篇博文中,笔者将创建一个简单的 WPF 程序来做一些 MEF 的相关小实验。

首先,我们创建一个工程,工程的目录结构如下图所示

  • MefSample:WPF主程序,用来负责程序相关初始化
  • MefSample.Core:类库,核心接口库,来用约束相关插件的导出接口
  • MefSample.Plugin1:用户控件库,插件1,某一独立具体的业务模块
  • MefSample.Plugin2:用户控件库,插件2,某一独立具体的业务模块
  • MefSample.Service:类库,某一具体服务的实现

我们在相应模块中添加相应代码,来构建一个简单的 MEF 示例程序。

MefSample.Core 中,我们创建一个 IView 的接口,用于插件的导出约束,示例代码如下所示

[Description("视图接口")]
public interface IView
{
}

然后分别在 MefSample.Plugin1MefSample.Plugin2 创建一个 UserControl ,并修改相应的后台代码

MefSample.Plugin1

[Export(typeof(IView))]
public partial class MainView : UserControl,IView
{
public MainView()
{
InitializeComponent();
}
}

MefSample.Plugin2

[Export(typeof(IView))]
public partial class MainView : UserControl,IView
{
public MainView()
{
InitializeComponent();
}
}

加载插件

模块加载分两种:实时加载延迟加载,针对模块数量少,内存消耗小的话,我们可以采用常规的加载方式,但是若工程项目较复杂,模块数较多的话,延迟加载是一种不错的选择方式,这里分别针对这两种加载方式进行简单的代码描述

- 常规加载

在主程序 MefSample 中,我们可以采用之前的加载方式来加载所有的插件,示例代码如下所示

public partial class MainWindow : Window
{
private CompositionContainer container = null;
public MainWindow()
{
InitializeComponent();
}
protected override void OnContentRendered(EventArgs e)
{
var dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
if (dir.Exists)
{
var catalog = new DirectoryCatalog(dir.FullName, "*.dll");
container = new CompositionContainer(catalog);
try
{
container.ComposeParts(this);
}
catch (CompositionException compositionEx)
{
Console.WriteLine(compositionEx.ToString());
} IEnumerable<IView> plugins = container.GetExportedValues<IView>();
foreach (var plugin in plugins)
{
this.tab.Items.Add(new TabItem() { Header = plugin.ToString(),Content = plugin });
}
}
base.OnContentRendered(e);
} protected override void OnClosing(CancelEventArgs e)
{
container?.Dispose();
base.OnClosing(e);
}
}

- 懒加载

要想用到懒加载技术需要借助 Lazy 来实现,稍微将上述代码修改一下就可以了,示例代码如下所示

public partial class MainWindow : Window
{
[ImportMany]
public Lazy<IView>[] plugins { get; set; } private CompositionContainer container = null;
public MainWindow()
{
InitializeComponent();
}
protected override void OnContentRendered(EventArgs e)
{
var dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
if (dir.Exists)
{
var catalog = new DirectoryCatalog(dir.FullName, "*.dll");
container = new CompositionContainer(catalog);
try
{
container.ComposeParts(this);
}
catch (CompositionException compositionEx)
{
Console.WriteLine(compositionEx.ToString());
} foreach (var plugin in plugins)
{
this.tab.Items.Add(new TabItem()
{
// 此时 pulugin 对象还未创建,执行 plugin.Value 才会创建该对象
Header = plugin.ToString(),
Content = plugin.Value
});
}
}
base.OnContentRendered(e);
} protected override void OnClosing(CancelEventArgs e)
{
container?.Dispose();
base.OnClosing(e);
}
}

获取元数据

有时,单纯地加载一个插件并不能满足我们的业务需求,我们可能还需要获取一些插件中的元数据来进行相应处理,这个时候我们就需要借助 IMetaData 来满足我们的业务场景需求。

首先,我们在 MefSample.Core 创建一个新的接口,定义为 IMetadata,并创建一个与之对应的自定义属性 CustomExportMetadata ,相关示例代码如下所示

/// <summary>
/// 元数据接口
/// 第二种方法可参考:MetadataViewImplementation 方式
/// </summary>
public interface IMetadata
{
[DefaultValue(0)]
int Priority { get; }
string Name { get; }
string Description { get; }
string Author { get; }
string Version { get; }
} [MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class CustomExportMetadata : ExportAttribute, IMetadata
{
public int Priority { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public string Author { get; private set; }
public string Version { get; private set; } public CustomExportMetadata() : base(typeof(IMetadata))
{
} public CustomExportMetadata(int priority) : this()
{
this.Priority = priority;
} public CustomExportMetadata(int priority, string name) : this(priority)
{
this.Name = name;
} public CustomExportMetadata(int priority, string name, string description) : this(priority, name)
{
this.Description = description;
} public CustomExportMetadata(int priority, string name, string description, string author) : this(priority, name, description)
{
this.Author = author;
} public CustomExportMetadata(int priority, string name, string description, string author, string version) : this(priority, name, description, author)
{
this.Version = version;
}
}

然后为我们的每个插件打上相应标签

MefSample.Plugin2

[Export(typeof(IView))]
[CustomExportMetadata(1,"Plugin 1","这是第一个插件","hippiezhou","1.0")]
public partial class MainView : UserControl,IView
{
public MainView()
{
InitializeComponent();
}
}

MefSample.Plugin2

[Export(typeof(IView))]
[CustomExportMetadata(2, "Plugin 2", "这是第二个插件", "hippiezhou", "1.0")]
public partial class MainView : UserControl, IView
{
public MainView()
{
InitializeComponent();
}
}

最后,修改我们的主程序

public partial class MainWindow : Window
{
[ImportMany]
public Lazy<IView,IMetadata>[] Plugins { get; set; } private CompositionContainer container = null;
public MainWindow()
{
InitializeComponent();
}
protected override void OnContentRendered(EventArgs e)
{
var dir = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
if (dir.Exists)
{
var catalog = new DirectoryCatalog(dir.FullName, "*.dll");
container = new CompositionContainer(catalog);
try
{
container.ComposeParts(this);
}
catch (CompositionException compositionEx)
{
Console.WriteLine(compositionEx.ToString());
} foreach (var plugin in Plugins)
{
this.tab.Items.Add(new TabItem()
{
//获取元数据
Header = plugin.Metadata.Name,
Content = plugin.Value
});
}
}
base.OnContentRendered(e);
} protected override void OnClosing(CancelEventArgs e)
{
container?.Dispose();
base.OnClosing(e);
}
}

依赖注入

最后说一下关于依赖注入的问题,在上篇博文中我们简单叙述了什么是 IOC。谈到控制反转就不得不提一下依赖注入了。在本次实验中,笔者通过往 Plugin2 插件注入一个简单服务来进一步理解。

首先,我们在 MefSample.Core 中创建一个服务接口 IService,示例代码如下所示

[Description("服务接口")]
public interface IService
{
void QueryData(int numuber, Action<int> action);
}

其次,我们在 MefSample.Service 中实现一个相应的服务 DataService,示例代码如下所示

[Description("具体服务")]
[Export(nameof(DataService),typeof(IService))]
public class DataService : IService
{
public void QueryData(int numuber, Action<int> action)
{
action(numuber * 100);
}
}

最后,我们在 MefSample.Plugin2 的模块构造函数中注入一个服务 IService 类型的服务,示例代码如下所示

[Export(typeof(IView))]
[CustomExportMetadata(2, "Plugin 2", "这是第二个插件", "hippiezhou", "1.0")]
public partial class MainView : UserControl, IView
{
public readonly IService Service;
[ImportingConstructor]
public MainView([Import("DataService")]IService service)
{
InitializeComponent(); Service = service;
Service.QueryData(10, sum => { this.tb.Text = sum.ToString(); });
}
}

当我们重新编译程序项目并运行的话,会发现插件二模块的界面上会显示 1000。这里需要注意的是,你也可以不通过构造函数来注入服务,而是以属性的方式。但是我个人不建议这么做,因为它并不能很好的阐释了 DI 。这里建议读者朋友阅读一下 Artech 大叔发布的相关系列文章,确实相当精彩,很是值得阅读。

总结

MEF 对初学者来说可能不是很好理解,但是多写几个 Demo 试验几次就好了。如果后续还有时间的话,我会与大家简单分享一下 Prism 框架的使用。这里分享几个 Github 地址给大家

MEF 插件式开发之 WPF 初体验的更多相关文章

  1. MEF 插件式开发之 DotNetCore 初体验

    背景叙述 在传统的基于 .Net Framework 框架下进行的 MEF 开发,大多是使用 MEF 1,对应的命名空间是 System.ComponentModel.Composition.在 Do ...

  2. MEF 插件式开发之 DotNetCore 中强大的 DI

    背景叙述 在前面几篇 MEF 插件式开发 系列博客中,我分别在 DotNet Framework 和 DotNet Core 两种框架下实验了 MEF 的简单实验,由于 DotNet Framewor ...

  3. Android插件化开发之OpenAtlas生成插件信息列表

    上一篇文章.[Android插件化开发之Atlas初体验]( http://blog.csdn.net/sbsujjbcy/article/details/47446733),简单的介绍了使用Atla ...

  4. MEF 插件式开发 - WPF 初体验

    原文:MEF 插件式开发 - WPF 初体验 目录 MEF 在 WPF 中的简单应用 加载插件 获取元数据 依赖注入 总结 MEF 在 WPF 中的简单应用 MEF 的开发模式主要适用于插件化的业务场 ...

  5. MEF 插件式开发 - 小试牛刀

    原文:MEF 插件式开发 - 小试牛刀 目录 MEF 简介 实践出真知 面向接口编程 控制反转(IOC) 构建入门级 MEF 相关参考 MEF 简介 Managed Extensibility Fra ...

  6. MEF 插件式开发之 小试牛刀

    MEF 简介 Managed Extensibility Framework 即 MEF 是用于创建轻量.可扩展应用程序的库. 它让应用程序开发人员得以发现和使用扩展且无需配置. 它还让扩展开发人员得 ...

  7. [MEF插件式开发] 一个简单的例子

    偶然在博客园中了解到这种技术,顺便学习了几天. 以下是搜索到一些比较好的博文供参考: MEF核心笔记 <MEF程序设计指南>博文汇总 先上效果图 一.新建解决方案 开始新建一个解决方案Me ...

  8. [转][MEF插件式开发] 一个简单的例子

    偶然在博客园中了解到这种技术,顺便学习了几天. 以下是搜索到一些比较好的博文供参考: MEF核心笔记 <MEF程序设计指南>博文汇总 先上效果图 一.新建解决方案 开始新建一个解决方案Me ...

  9. jQuery插件的开发之$.extend(),与$.fn.extend()

        jQuery插件的开发包括两种: 一种是类级别的插件开发,即给jQuery添加新的全局函数,相当于给jQuery类本身添加方法.jQuery的全局函数就是属于jQuery命名空间的函数,另一种 ...

随机推荐

  1. Trinity的分步运行

    当使用Trinity组装时,如果数据量过大,可以考虑使用--min_kmer_cov 2参数丢弃uniquely occurring kmer, 从而降低内存消耗 设置--no_distributed ...

  2. Adobe Photoshop CC 2019画板背景色白底如何去掉?

    Adobe Photoshop CC 2019画板背景色白底切透明图片很不方便,有两种方法可以解决: 第一种方法: 新建文档的时候直接背景内容直接选择透明 若设计师提供的设计稿是白底也没关系,就是第二 ...

  3. 3.html基础标签:表格

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. 菜刀(代码执行)函数和命令执行函数详解及Getshell方法

    i春秋作家:大家奥斯的哦 原文来自:https://bbs.ichunqiu.com/thread-41471-1-1.html 代码执行函数 VS 命令执行函数 一直想整理这两块的内容,但是一直没时 ...

  5. so静态分析进阶练习——一个CreakeMe的分析思路

    i春秋作家:HAI_ 原文来自:https://bbs.ichunqiu.com/thread-41371-1-1.html 说明 拿到一个CreakeMe,写一个分析思路.CreakMe主要是对.s ...

  6. kaili 安装中文输入法

    kaili 安装中文输入法 更换为国内可用的源: vim /etc/apt/sources.list 添加以下内容: deb http://mirrors.ustc.edu.cn/kali sana ...

  7. ubuntu解压rar文件

    一般通过默认安装的ubuntu是不能解压rar文件的,只有在安装了rar解压工具之后,才可以解压.其实在ubuntu下安装rar解压工具是非常简 单的,只需要两个步骤就可以迅速搞定. ubuntu 下 ...

  8. c++中的.hpp文件

    http://blog.chinaunix.net/uid-24118190-id-75239.html hpp,其实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的 ...

  9. 【BJOI2019】光线 模拟

    题目大意:有一束光线要依次穿过$n$块玻璃. 第i块玻璃的透射率为$a_i$,反射率为$b_i$. 问你有多少光能最终穿过所有玻璃. 数据范围:$n≤5\times 10^5$,答案对$9982443 ...

  10. Alienware-15-R3 装Ubuntu 16.04.3 LTS

    前言:Alienware-15-R3默认安装的系统是win10.现在卸载win0,装Ubuntu 16.04.3 LTS. 一.下载Ubuntu 16.04.3 LTS镜像文件,下载地址:https: ...