zoukankan      html  css  js  c++  java
  • MEF 插件式开发之 WPF 初体验

    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 地址给大家

  • 相关阅读:
    java主函数的含义
    this关键字简单应用
    如何将util.Date转化为sql.Date
    利用JavaScript来实现省份—市县的二级联动
    通过Ajax异步提交的方法实现从数据库获取省份和城市信息实现二级联动(xml方法)
    折半查找(java)(边学习边更新)
    第五章 上
    Jframe关闭窗口时的事件
    C#退出程序自动重新启动
    Extjs4.0 开发笔记与Easyui的整合
  • 原文地址:https://www.cnblogs.com/hippieZhou/p/9404043.html
Copyright © 2011-2022 走看看