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. Hessian学习(springboot环境)

    Hessian介绍: Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能. 相比WebService,Hessian更简单.快捷.采用的是二进制RPC协议 ...

  2. Flask 中的 5种返回值

    1.Flask中的HTTPResponse 在Flask 中的HttpResponse 在我们看来其实就是直接返回字符串 2.Flask中的Redirect 每当访问"/redi" ...

  3. 音视频编解码——YUV视频格式详解

    一.YUV 介绍 YUV是一种颜色编码方方式,通常由彩色摄像机进行取像,然后把取得的彩色图像信号经过分色.分别放大校正后得到RGB,再经过矩阵变换得到亮度信号Y和两个色差信号B-Y(即U).R-Y(即 ...

  4. 使用new Image()进行预加载

    概述 这篇博文记录了用new Image()进行预加载的总结,供以后开发时参考,相信对其他人也有用. 旧的预加载 一般我们为了让背景图更快的加载,我们常常把背景图放在一个display:none的im ...

  5. xtrabackup备份mysql-2 增量备份

    ---恢复内容开始--- 增量备份 ---恢复内容结束---] 增量备份恢复 1,重演日志 2,恢复数据

  6. 利用SSH反向隧道,连接内网服务器

    前言 公司有一台文件服务器(内部使用,无外网IP),上面主要安装了SVN服务,用来存储和共享各部门的文档,因为都是内网,直接远程(mstsc)上去就可以方便维护,但最近公司租了新的办公室,部分员工被分 ...

  7. Linux - 修改内核启动顺序及删除无用内核

    现象: CentOS7开机启动界面显示多个内核选项 原因: 正常情况下,有两个启动项,一个是"正常启动",另一个是"救援模式启动"(rescue). 如果启动项 ...

  8. mysql修改数据路径

    步凑: 1.关闭mysql服务 [root@localhost /]# service mysqld stop 2.移动mysql文件夹到自定义的目录 [root@localhost /]# mv / ...

  9. SQL Server性能优化(12)非聚集索引的组合索引存储结构

    一,非聚集索引组合索引 用户可以在多个列上建立索引,这种索引叫做复合索引(组合索引).但复合索引在数据库操作期间所需的开销更小,可以代替多个单一索引.当表的行数远远大于索引键的数目时,使用这种方式可以 ...

  10. Atom相关资料整理

    官网地址 https://atom.io/ Atom 中文社区 https://atom-china.org/ 常用插件 Emmet 这款插件是用来支持zend-coding,Emmet的前身是大名鼎 ...