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. appendChild()方法遇到的问题

    在使用appendChild()方法中遇到了一个问题: 下面的代码可以正常插入多个新元素 <input type="button" value="在后面插入新元素& ...

  2. DOM扩展:DOM API的进一步增强[总结篇-上]

    DOM1级主要定义了文档的底层结构,并提供了基本的查询操作的API,总体而言这些API已经比较完善,我们可以通过这些API完成大部分的DOM操作.然而,为了扩展DOM API的功能,同时进一步提高DO ...

  3. Java 实现字符串的加密与解密

    package com.wangbo.util; import java.security.Key; import java.security.Security; import javax.crypt ...

  4. Javascript百学不厌 - 尾递归

    虽然偶尔也用过,但是从来没具体来整理过 普通递归: function fac(n) { ) ; ); } fac() 这是个阶乘.但是占用内存,因为: fac(5) (5*fac(4)) (5*(4* ...

  5. Shell-1--概念

  6. $.parseJson()定义和用法

    $.parseJSON() 函数用于将符合标准格式的的JSON字符串转为与之对应的JavaScript对象. 例子: 这里首先给出JSON字符串集,字符串集如下: var data="  { ...

  7. [EXP]Microsoft Windows CONTACT - Remote Code Execution

    [+] Credits: John Page (aka hyp3rlinx) [+] Website: hyp3rlinx.altervista.org [+] Source: http://hyp3 ...

  8. 200行代码实现简版react🔥

    200行代码实现简版react

  9. Intellij新安装初始化配置

    自动编译开关 忽略大小写开关 IDEA默认是匹配大小写,此开关如果未关.你输入字符一定要符合大小写.比如你敲string是不会出现代码提示或智能补充.但是,如果你开了这个开关,你无论输入String或 ...

  10. Spring Boot + Spring Cloud 构建微服务系统(七):API服务网关(Zuul)

    技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡,那我们的各种微服务又要如何提供给外部应用调用呢. 当然,因为是REST API接口,外部客户端直接调用各个微服务是没有问 ...