65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 MEF 完全延迟 DLL 加载的最简单方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (31投票s)

2012年11月22日

CPOL

3分钟阅读

viewsIcon

81025

downloadIcon

3168

使用 MEF 完全延迟加载 DLL 的(通用)最简单方法。

引言

本文提供了一种使用 MEF 的简单通用方法

  1. 使用 MEF 的(通用)最简单方法。
  2. 如何使用 MEF 完全延迟加载 DLL。

背景

MEF 代表“Managed Extensibility Framework”(托管可扩展性框架) - 托管可扩展性框架 (MEF) 是一个用于创建轻量级、可扩展应用程序的库。 它允许应用程序开发人员发现和使用扩展,而无需任何配置。 它还允许扩展开发人员轻松封装代码并避免脆弱的硬依赖关系。 MEF 不仅允许在应用程序中重用扩展,还允许跨应用程序重用扩展。 请参阅 MSDN 链接

有很多关于 MEF 的文章,尽管我从架构师/开发经理/同事讲师/学生那里收到了很多关于该主题的问题,关于如何简单且通用地使用 MEF 而不同时加载所有 dll。

MEF 加载器通用逻辑

以通用的方式将 MEF 用作黑盒子的最简单方法是使用以下代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace MEF_Example.MEF
{

    /// <summary>
    /// MEFLoader class that responsible for :
    ///    The interface for all the MEF loading process, i.e. he is the black-box.
    ///    holding all the already imported object (for better performance) 
    ///    holding all the already exist importers (one for each type)
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public class MEFLoader
    {
        Dictionary<string, List<object>> importers = new Dictionary<string, List<object>>();

        public virtual ICollection<T> LoadByTag<T>(string path, string tag)
        {
            var importer = GetImporter<T>(path);

            return importer.LoadByMEF(path, tag);
        }

        protected MEFImporter<T> GetImporter<T>(string path)
        {
            var importerList = GetImporterList(path);

            var importer = importerList.OfType<MEFImporter<T>>().FirstOrDefault();

            if (importer == null)
            {
                importer = new MEFImporter<T>(path);
                importerList.Add(importer);

                //Write to Log:
                //UILogService.Instance.WriteToLog(E_ErrorSeverity.e_Information, "New MEFImporter was created for Path & Type" + path + importer.GetType().ToString());
            }

            return importer;
        }

        protected List<object> GetImporterList(string path)
        {
            if (importers.ContainsKey(path) == false)
                importers.Add(path, new List<object>());

            return importers[path];
        }

        public virtual ICollection<T> LoadByType<T>(string path)
        {
            return LoadByTag<T>(path, String.Empty);
        }
    }

    /// <summary>
    /// The imported objects metadata interface. i.e. the set of 
    /// properties we can filter by all the already imported objects.
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public interface IMetadata
    {
        string Name { get; }
    }

    /// <summary>
    /// Generic Class is responsible for MEF Import of certain type T.
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public class MEFImporter<T>
    {
        [ImportMany(AllowRecomposition = true)]
        public IEnumerable<Lazy<T, IMetadata>> imports { get; set; }

        MEFImporter()
        {
        }

        public MEFImporter(string path)
            : this()
        {
            directoryCatalog = new DirectoryCatalog(path);
        }

        protected DirectoryCatalog directoryCatalog = null;

        protected void DoImport(string path)
        {
            //An aggregate catalog that combines multiple catalogs
            var catalog = new AggregateCatalog();
            //Adds all the parts found in all assemblies in 
            //the same directory as the executing program
            catalog.Catalogs.Add(directoryCatalog);

            //Create the CompositionContainer with the parts in the catalog
            CompositionContainer container = new CompositionContainer(catalog);

            //Fill the imports of this object
            container.ComposeParts(this);
        }

        public ICollection<T> LoadByMEF(string path, string name)
        {
            var res = new List<T>();

            DoImport(path);
            //Test MEF
            //AppDomain MyDomain = AppDomain.CurrentDomain;
            //var AssembliesLoaded = MyDomain.GetAssemblies();

            foreach (Lazy<T, IMetadata> module in imports)
            {
                if (module.Metadata.Name == name || String.IsNullOrEmpty(name))
                {
                    res.Add(module.Value); //Will create an instance
                }
            }

            return res;
        }
    }
}
  • MEFImporter Generic Class 负责某种类型 T 的 MEF 导入
  • IMetadata 接口是导入对象的元数据接口。 即,我们可以通过所有已导入的对象进行筛选的属性集。
  • 最后,MEFLoader 类负责
    • 所有 MEF 加载过程的接口,即,他是黑盒子。
    • 保存所有已导入的对象(以获得更好的性能)
    • 保存所有已存在的导入器(每种类型一个)

问题是:MEF 加载“路径”文件夹中的所有 DLL!

注意:通过使用 Lazy<>,至少他不会创建所有符合我们要求的对象。

使用代码

问:如何使用?

答:示例

示例 (WPF) 应用程序的基本思想是

3 个按钮 -> 单击 -> 加载与该按钮匹配的自定义控件 -> 通过 MEF 加载自定义控件

实现,MainWindow.xaml

<Window x:Class="MEF_Example.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DockPanel LastChildFill="True">
            <StackPanel DockPanel.Dock="Top" >
                <Button Content="Present Blue Control" Background="Blue" Click="Button_Click" Tag="1"/>
                <Button Content="Present Green Control" Background="Green" Click="Button_Click" Tag="2"/>
                <Button Content="Present Yellow Control" Background="Yellow" Click="Button_Click" Tag="3"/>
            </StackPanel>
            <ContentPresenter x:Name="Worksapce" />
        </DockPanel>     
    </Grid>
</Window>

代码隐藏,MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ControlInterface;

namespace MEF_Example
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        MEF.MEFLoader m_Loader = new MEF.MEFLoader();

        string GetPathByName(string name)
        {
            var res = AppDomain.CurrentDomain.BaseDirectory + @"Controls\";

            return res;
        }

        UserControl GetControlByName<T>(string name)
        {
            UserControl res = null;

            res =  m_Loader.LoadByTag<T>(GetPathByName(name), name).FirstOrDefault() as UserControl;

            return res;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var btn = (e.OriginalSource as Button);

            if (btn == null && btn.Tag != null)
                return;
            int number = 0;
            Int32.TryParse(btn.Tag as string,out number);

            switch (number)
            {
                case 1:
                    //Present Blue Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary1.BlueControl");
                    break;
                case 2:
                    //Present Green Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary1.GreenControl");
                    break;
                case 3:
                    //Present Yellow Control (diffrent dll: ControlsLibrary2.dll):
                    this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary2.YellowControl");
                    break;
            }
        }
    }
}

UserControl 类 (ControlsLibrary1.dll)

BlueControl.xaml

<UserControl x:Class="ControlsLibrary1.BlueControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid Background="Blue">           
    </Grid>
</UserControl>

BlueControl.xaml.cs

namespace ControlsLibrary1
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    [Export(typeof(IControl))]
    [ExportMetadata("Name", "ControlsLibrary1.BlueControl")]
    public partial class BlueControl : UserControl, IControl
    {
        public BlueControl()
        {
            InitializeComponent();
        }
    }
}

GreenControl.xaml

<UserControl x:Class="ControlsLibrary1.GreenControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid Background="Green">           
    </Grid>
</UserControl>

GreenControl.xaml.cs

namespace ControlsLibrary1
{
    /// <summary>
    /// Interaction logic for GreenControl.xaml
    /// </summary>
    [Export(typeof(IControl))]
    [ExportMetadata("Name", "ControlsLibrary1.GreenControl")]
    public partial class GreenControl : UserControl, IControl
    {
        public GreenControl()
        {
            InitializeComponent();
        }
    }
}

UserControl 类 (ControlsLibrary2.dll)

YellowControl.xaml

 <UserControl x:Class="ControlLibrary2.YellowControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid Background="Yellow">           
    </Grid>
</UserControl>

YellowControl.xaml.cs

namespace ControlLibrary2
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    [Export(typeof(IControl))]
    [ExportMetadata("Name", "ControlsLibrary2.YellowControl")]
    public partial class YellowControl : UserControl, IControl
    {
        public YellowControl()
        {
            InitializeComponent();
        }
    }
}

您也可以在此处下载代码

1. 如何使用 MEF 完全延迟加载 DLL?

问:如果我们希望 MEF 也完全延迟加载 DLL,我们可以做什么?

问题是:MEF 加载“路径”文件夹中的所有 Dll!

在我们的示例中,加载 Yellow Control 时,MEF 机制加载了 3 个对象

答:解决方案 1:使用子目录

让我们将 ControlsLibrary1.dll 和 ControlsLibrary2.dll 编译到“Controls”目录的子目录中

并将 MainWindow 代码更改为

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ControlInterface;

namespace MEF_Example
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        MEF.MEFLoader m_Loader = new MEF.MEFLoader();

        string GetPathByName(string name)
        {
            var res = AppDomain.CurrentDomain.BaseDirectory + @"Controls\";

            var familyname = name.Split(new[] { '.' })[0];

            return res + familyname;
        }

        UserControl GetControlByName(string name)
        {
            UserControl res = null;

            res =  m_Loader.LoadByTag<IControl>(GetPathByName(name), name).FirstOrDefault() as UserControl;

            return res;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var btn = (e.OriginalSource as Button);

            if (btn == null && btn.Tag != null)
                return;
            int number = 0;
            Int32.TryParse(btn.Tag as string,out number);

            switch (number)
            {
                case 1:
                    //Present Blue Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName("ControlsLibrary1.BlueControl");
                    break;
                case 2:
                    //Present Green Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName("ControlsLibrary1.GreenControl");
                    break;
                case 3:
                    //Present Yellow Control (diffrent dll: ControlsLibrary2.dll):
                    this.Worksapce.Content = GetControlByName("ControlsLibrary2.YellowControl");
                    break;
            }
        }
    }
}

现在加载 Yellow 控件时,只有 1 个对象

您也可以在此处下载代码

答:解决方案 2:使用更多导出接口

将每个 dll 添加到他自己的继承接口

namespace ControlInterface
{
    public interface IControl
    {    
    }

    public interface IControlLibrary1 : IControl
    {
    }

    public interface IControlLibrary2 : IControl
    {
    }
}

更改 UserControl 类的导出

BlueControl.xaml.cs (ControlsLibrary1.dll)

namespace ControlsLibrary1
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    [Export(typeof(IControlLibrary1))]
    [ExportMetadata("Name", "ControlsLibrary1.BlueControl")]
    public partial class BlueControl : UserControl, IControlLibrary1
    {
        public BlueControl()
        {
            InitializeComponent();
        }
    }
}

GreenControl.xaml.cs (ControlsLibrary1.dll)

namespace ControlsLibrary1
{
    /// <summary>
    /// Interaction logic for GreenControl.xaml
    /// </summary>
    [Export(typeof(IControlLibrary1))]
    [ExportMetadata("Name", "ControlsLibrary1.GreenControl")]
    public partial class GreenControl : UserControl, IControlLibrary1
    {
        public GreenControl()
        {
            InitializeComponent();
        }
    }
}

YellowControl.xaml.cs (ControlsLibrary2.dll)

namespace ControlLibrary2
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    [Export(typeof(IControlLibrary2))]
    [ExportMetadata("Name", "ControlsLibrary2.YellowControl")]
    public partial class YellowControl : UserControl, IControlLibrary2
    {
        public YellowControl()
        {
            InitializeComponent();
        }
    }
}

通过接口加载 (MainWindow.xaml.cs)

namespace MEF_Example
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        MEF.MEFLoader m_Loader = new MEF.MEFLoader();

        string GetPathByName(string name)
        {
            var res = AppDomain.CurrentDomain.BaseDirectory + @"Controls\";

            return res;
        }

        UserControl GetControlByName<T>(string name)
        {
            UserControl res = null;

            res =  m_Loader.LoadByTag<T>(GetPathByName(name), name).FirstOrDefault() as UserControl;

            return res;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var btn = (e.OriginalSource as Button);

            if (btn == null && btn.Tag != null)
                return;
            int number = 0;
            Int32.TryParse(btn.Tag as string,out number);

            switch (number)
            {
                case 1:
                    //Present Blue Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName<IControlLibrary1>("ControlsLibrary1.BlueControl");
                    break;
                case 2:
                    //Present Green Control (dll: ControlsLibrary1.dll):
                    this.Worksapce.Content = GetControlByName<IControlLibrary1>("ControlsLibrary1.GreenControl");
                    break;
                case 3:
                    //Present Yellow Control (diffrent dll: ControlsLibrary2.dll):
                    this.Worksapce.Content = GetControlByName<IControlLibrary2>("ControlsLibrary2.YellowControl");
                    break;
            }
        }
    }
}

现在加载 Yellow 控件时,只有 1 个对象

您也可以在此处下载代码

© . All rights reserved.