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. yum-内网yum源服务器配置(CentOS6.5)

    一.安装apache服务1.安装httpd服务 yum -y install httpd (纯内网用rpm包安装也可以) 2.启动httpd服务 service httpd start 二.挂载完整的 ...

  2. BASH 环境

    本节内容 1.  什么是shell 2.  命令的优先级 3.  元字符 4.  登录shell与非登录shell 一  什么是shell shell一般代表两个层面的意思,一个是命令解释器,如bas ...

  3. 进度条(progress_bar)

    环境:linux.centos6.5 #include<stdio.h> #include<unistd.h> int main() { ]={'\0'}; char ch[] ...

  4. Instruments Time Profiler时,无法定位代码,如何破?

    都是地址符号,往深里也一直是地址符号,根本没法判断是哪些代码的执行时间 解决办法: 选下面的.

  5. NLP常用术语解析

    分词(Segment):中英文都存在分词的问题,不过相对来说,英文单词与单词之间本来就有空格进行分割,所以处理起来相对方便.但是中文书写是没有分隔符的,所以分词的问题就比较突出.分词常用的手段可以是基 ...

  6. linux 软件安装篇

    在linux下安装软件,不像windows一样,下一步下一步安装,但是也有很方便的方式.也有自定义的安装方式,总体来说,套路还不算太深,但是要实践才能出真知哦! linux版本有很多,但是大部分命令都 ...

  7. windows快速打开命令窗口方式[利刃篇]

    windows当然是窗口界面操作了,谁有事没事去用什么命令行啊,但是当你要用的时候,也要会用才行哦. 打开命令行的方式小说一下: 1.开始 > 运行 > cmd , enter,  ok ...

  8. web自动化测试---第一个自动化测试用例

    测试环境搭建好之后就可以写自动化测试脚本了,我们以baidu为例,写一个自动化测试脚本 from selenium import webdriver import time driver = webd ...

  9. Win10上安装TensorFlow(官方文档翻译)

    一.推荐两个网站 TensorFlow官方文档:https://www.tensorflow.org/install/install_windows TensorFlow中文社区:http://www ...

  10. C# 算法之链表、双向链表以及正向反向遍历实现

    1.简介 链表是一种非常基础的数据结构之一,我们在日常开发种都会接触到或者是接触到相同类型的链表数据结构.所以本文会使用C#算法来实现一个简单的链表数据结构,并实现其中几个简单的api以供使用. 2. ...