在MvvmLight下使用MEF实现插件子系统

  MEF即Managed Extensibility Framework,于.net 4.0引入。MEF通过简单地给代码附加“[Import]”和“[Export]”标记,我们就可以清晰地表明组件之间的“服务消费”与“服务提供”关系,MEF在底层使用反射动态地完成组件识别、装配工作。从而使得开发基于插件架构的应用系统变得简单。

  实际上在Codeplex、codeproject上已经有很多类似主题的示例,但因本身在于使用MEF实现一个复杂的系统,初学者只会感觉眼花缭乱[至少当时我在学习MEF官方发布的例子时倍感吃力],特写了一个简单逻辑的例子希望能帮到初学者。  

代码整体结构如下:

在上图中,Extension.Core封装了扩展程序接口,只有实现了改接口的模块才能被客户端发现。WPFPlugOne和WPFPlugTwo分别是两个使用MEF实现了插件。

      我们先来看看实现插件的核心Extension.Core ,其中最主要的是IExtensionService,只有实现了该接口的插件才能被客户端使用。 而IMenuItem,它的定义是为了能够响应客户端的请求,并将请求内容传到插件中。具体实现如下:

View Code
 1 /// <summary>
 2     /// 所有扩展入口
 3     /// </summary>
 4     public interface IExtensionService
 5     {
 6         IMenuItem Item { get; set; }
 7     }
 8 
 9 
10 public interface IMenuItem
11     {
12         string Header { get; }
13         IEnumerable<IMenuItem> Items { get;}
14         bool IsChecked { get; set; }
15         ICommand CheckChangedCommand { get; }
16 
17         GalaSoft.MvvmLight.ViewModelBase ViewModel { get;  }
18     }

 

  在整个应用程序中,为了实现插件的载入、执行等操作,需要引用两个主要的命名空间:

using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;

System.ComponentModel.Composition;使我们的程序拥有了指定属性、字段或参数导入特定的导出的能力。即可以[Import]具有指定协定名称、协定类型且标记了[Export]的模块。
System.ComponentModel.Composition.Hosting;则起到一个容器的作用。

这些只是一个货架,具体的货物来源还得看我们的程序如何来加载:

 1         #region MEF 
 2 
 3         /// <summary>
 4         /// 首先,它跟踪哪些部分可用于组合、它们的依赖项,并且充当任何指定组合的上下文。其次,它提供了应用程序可以启动组合的方法、获取组合部件的实例,或填充可组合部件的依存关系。
 5         /// </summary>
 6         private CompositionContainer _container;
 7 
 8         /// <summary>
 9         /// 导入凡是协定名称为Extension.Core.ExtensionPoints.Host.Views,协定类型为ResourceDictionary 的模块
10         /// 此处在于客户端能统一管理扩展插件的现实方式
11         /// </summary>
12         [ImportMany(Extension.Core.ExtensionPoints.Host.Views, typeof(ResourceDictionary))]
13         private IEnumerable<ResourceDictionary> Views { get; set; }
14 
15         [ImportMany(Extension.Core.ExtensionPoints.Host.Styles, typeof(ResourceDictionary), AllowRecomposition = true)]
16         private IEnumerable<ResourceDictionary> Styles { get; set; }
17 
18         /// <summary>
19         /// 扩展插件入口
20         /// </summary>
21         [ImportMany(Extension.Core.ExtensionPoints.WorkArea.MainMenu.Self, typeof(Extension.Core.IExtensionService))]
22         private IEnumerable<Extension.Core.IExtensionService> Extensions { get; set; }
23 
24         public static IEnumerable<Extension.Core.IExtensionService> ExtensionAdds { get; set; }
25 
26         #endregion
27 
28         /// <summary>
29         /// 组件构成
30         /// 当程序执行到这里,就会将会于应用程序根目录下实现了指定接口的模块进行组合,接下来就可以在程序中直接使用了
31         /// </summary>
32         /// <returns></returns>
33         private bool Compose()
34         {
35             var catalog = new AggregateCatalog();
36             catalog.Catalogs.Add(new DirectoryCatalog("."));
37 
38             _container = new CompositionContainer(catalog);
39 
40             try
41             {
42                 _container.ComposeParts(this);
43             }
44             catch (CompositionException compositionException)
45             {
46                 MessageBox.Show(compositionException.ToString());
47                 _container.Dispose();
48                 return false;
49             }
50             return true;
51         }
52 
53         /// <summary>
54         /// 在满足部件的导入并可安全使用时调用
55         /// </summary>
56         public void OnImportsSatisfied()
57         {
58             foreach (ResourceDictionary r in Views)
59             {
60                 this.Resources.MergedDictionaries.Add(r);
61             }
62             ExtensionAdds = Extensions;
63         }

   下面我们来构建一个具体的插件,名我PlugOne,功能主要是实现对客户端输入数据的修改、删除或添加新的数据。

  首先,为了客户端能够统一管理插件页面并能够被发现,需要将所有要展示出来的页面定义到DataTemplate中。

View Code
 1 ResourceDictionary x:Class="WPFPlugTwo.AddNewData"
 2              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
 5              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
 6                     xmlns:vm="clr-namespace:WPFPlugTwo.ViewModel"
 7              mc:Ignorable="d" >
 8     <DataTemplate DataType="{x:Type vm:AddNewDataViewModel}">
 9         <Grid Background="White" MinWidth="300" MinHeight="180">
10           .........
11         </Grid>
12     </DataTemplate>
13 </ResourceDictionary>

 而在对应的交互逻辑页面中,也要做一些对应的更改。

View Code
 1 /// <summary>
 2     /// AddNewData.xaml 的交互逻辑
 3     /// </summary>
 4     [Export(Extension.Core.ExtensionPoints.Host.Views,typeof(ResourceDictionary)) ]
 5     public partial class AddNewData : ResourceDictionary
 6     {
 7         public AddNewData()
 8         {
 9             InitializeComponent();
10         }
11     }

  ok,如果一切正常,这个时候私有属性 Views 已经有值了。但是却还不足以让程序调用。前面已经说过IExtensionService是所有扩展库要实现的必要接口,只有实现了该接口的库才能被发现。

View Code
 1 [Export(Extension.Core.ExtensionPoints.WorkArea.MainMenu.Self, typeof(IExtensionService))]
 2     public class MainMenuItem : IExtensionService
 3     {
 4         //导入实现了IMenuItem的导出类型,因为触发扩展插件事件的来源定义menuItem的点击事件 ,
 5         [Import("AddNewDataMenuItem", typeof(IMenuItem))]
 6         public Lazy<IMenuItem> AboutMenuItem { set; get; }
 7 
 8 
 9         public IMenuItem Item
10         {
11             get
12             {
13                 return  AboutMenuItem.Value;
14             }
15             set
16             {
17                 throw new NotImplementedException();
18             }
19         }
20     }
21 
22 
23 [Export("AddNewDataMenuItem", typeof(IMenuItem))]
24     public class AddNewDataMenuItem : IMenuItem
25     {
26         //导出AddNewData页面的数据模型,如果有多个页面同样可以在此导出
27         [Import(Extension.Core.CompositionPoints.WorkArea.ViewModel, typeof(AddNewDataViewModel))]
28         public Lazy<AddNewDataViewModel> ViewModelPage { set; get; }
29         
30         //在MenuItem中显示的插件名次
31         public string Header
32         {
33             get { return "添加数据"; }
34         }
35 
36         //所对应的MenuItem是否还有二级菜单
37         public IEnumerable<IMenuItem> Items
38         {
39             get { return null; }
40         }
41 
42         public bool IsChecked
43         {
44             get;
45             set;
46         }
47         
48 
49         private System.Windows.Input.ICommand _checkChangedCommand;
50         public System.Windows.Input.ICommand CheckChangedCommand
51         {
52             get
53             {
54                 if (_checkChangedCommand == null)
55                     _checkChangedCommand = new RelayCommand(() =>
56                     {
57                        //定义在触发该命令后要做的操作
58                     });
59                 return _checkChangedCommand;
60             }
61         }
62 
63         //指定所对应的数据模型
64         public GalaSoft.MvvmLight.ViewModelBase ViewModel
65         {
66             get
67             {
68                 return ViewModelPage.Value;
69             }
70         }

      此时,一个简单的插件已经诞生了,接下来我们来看客户端是如何来使用它的,前面已经在App.xaml.cs中编写了加载的方法。那么我们要操作的就是静态属性ExtensionAdds了。

View Code
 1 <Window x:Class="MvvmLightMEFSample.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         xmlns:local="clr-namespace:MvvmLightMEFSample"
 5         xmlns:vm="clr-namespace:MvvmLightMEFSample.ViewModel"
 6         xmlns:vw="clr-namespace:MvvmLightMEFSample.Views"
 7         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
 8         Title="{Binding Welcome}"
 9         Height="300" WindowStartupLocation="CenterScreen"
10         Width="500" >
11 
12     <Window.Resources>
13         <DataTemplate DataType="{x:Type MenuItem}" x:Key="extensionItem">
14             <Grid>
15                 <MenuItem HorizontalAlignment="Stretch" Header="{Binding Item.Header}" Command="{Binding DataContext.CheckChangedCommand,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Menu}}}" CommandParameter="{Binding RelativeSource={RelativeSource self}}"/>
16             </Grid>
17         </DataTemplate>
18 
19         <DataTemplate DataType="{x:Type vm:ExtensionWrapperViewModel}">
20             <vw:ExtensionWrapper />
21         </DataTemplate>
22     </Window.Resources>
23 
24     <Grid x:Name="LayoutRoot">
25         <Menu Height="30"  Name="menu1" VerticalAlignment="Top" >
26             <MenuItem Header="固有操作" Width="80">
27                 <MenuItem Header="删除数据"  Command="{Binding FillDataCommand}"/>
28             </MenuItem>
29             <MenuItem Header="扩展操作" x:Name="extensionitem" Width="80" HorizontalAlignment="Center" ItemsSource="{Binding Path=(local:App.ExtensionAdds)}" ItemTemplate="{StaticResource extensionItem}"/>
30         </Menu>
31         <ContentControl Content="{Binding Path=DialogViewModel}"  Margin="0,50,0,0"/>
32         
33     </Grid>
34 </Window>

  

源码下载

posted @ 2013-01-11 21:49  zeoy_aria  阅读(1952)  评论(1编辑  收藏  举报