• 在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>

      

    源码下载

  • 相关阅读:
    使用淘宝Str2varlist与str2numlist 代替 in/exist ,提升性能(Oracle)
    由浅入深理解索引的实现
    你知道数据库索引的工作原理吗?
    深入理解数据库磁盘存储(Disk Storage)
    如何解析oracle执行计划
    Beyond Compare 4 最新中文版 注册码 key
    并发和并行的区别
    代码复用的规则
    Some Java exceptions, messages and errors.
    菜鸟学SSH(十六)——Struts2内部是如何工作的
  • 原文地址:https://www.cnblogs.com/zeoy/p/2853867.html
Copyright © 2020-2023  润新知