什么是MEF最佳实践?

23

在您的代码中使用MEF的一些最佳实践是什么?在启动可扩展应用程序时需要注意哪些陷阱?你是否遇到过任何你早该知道的问题?

3个回答

7
我正在构建一个完整的可扩展应用程序,使用MEF(并且使用WPF和MVVM模式)。我采用了我构建的基本应用程序框架,并将其开源为SoapBox Core。我还在Code Project上发布了一个基于SoapBox Core的演示:使用MEF、WPF和MVVM构建可扩展应用程序
如果您正在使用MVVM,则不确定是否适用于您,但是如果适用,则可以通过查看MVVM与MEF的实现来学习很多东西。特别是它导入视图的方式。
至于最佳实践...我创建了一组嵌套的扩展(因此基本模块称为Host,它只是组合应用程序并导入几个基本扩展)。然后这些扩展公开其他扩展点,当您运行它时,应用程序会自动构建(组合和扩展的交叉)。
为了保持一切清晰,我将扩展层次结构放入一组静态类中。例如,这里是核心框架提供的所有扩展点:
namespace SoapBox.Core.ExtensionPoints
{
    public static class Host
    {
        public const string Styles = "ExtensionPoints.Host.Styles";
        public const string Views = "ExtensionPoints.Host.Views";
        public const string StartupCommands = "ExtensionPoints.Host.StartupCommands";
        public const string ShutdownCommands = "ExtensionPoints.Host.ShutdownCommands";
    }
    public static class Workbench
    {
        public const string ToolBars = "ExtensionPoints.Workbench.ToolBars";
        public const string StatusBar = "ExtensionPoints.Workbench.StatusBar";
        public const string Pads = "ExtensionPoints.Workbench.Pads";
        public const string Documents = "ExtensionPoints.Workbench.Documents";

        public static class MainMenu
        {
            public const string Self = "ExtensionPoints.Workbench.MainMenu";
            public const string FileMenu = "ExtensionPoints.Workbench.MainMenu.FileMenu";
            public const string EditMenu = "ExtensionPoints.Workbench.MainMenu.EditMenu";
            public const string ViewMenu = "ExtensionPoints.Workbench.MainMenu.ViewMenu";
            public const string ToolsMenu = "ExtensionPoints.Workbench.MainMenu.ToolsMenu";
            public const string WindowMenu = "ExtensionPoints.Workbench.MainMenu.WindowMenu";
            public const string HelpMenu = "ExtensionPoints.Workbench.MainMenu.HelpMenu";
        }
    }

    public static class Options
    {
        public static class OptionsDialog
        {
            public const string OptionsItems = "ExtensionPoints.Options.OptionsDialog.OptionsItems";
        }
    }
}

因此,如果您希望扩展程序向文件菜单添加某些内容,您需要导出实现IMenuItem接口且合同名称为SoapBox.Core.ExtensionPoints.Workbench.MainMenu.FileMenu的内容。

每个扩展程序都有一个“ID”,它只是一个字符串标识符。这些现有的ID在另一个层次结构中定义:

namespace SoapBox.Core.Extensions
{
    public static class Workbench
    {
        public static class MainMenu
        {
            public const string File = "File";
            public const string Edit = "Edit";
            public const string View = "View";
            public const string Tools = "Tools";
            public const string Window = "Window";
            public const string Help = "Help";

            public static class FileMenu
            {
                public const string Exit = "Exit";
            }

            public static class ViewMenu
            {
                public const string ToolBars = "ToolBars";
            }

            public static class ToolsMenu
            {
                public const string Options = "Options";
            }
        }
    }
}

如您所见,FileMenu已经包含了一个Exit扩展(预设为关闭应用程序)。如果您想要向文件菜单添加扩展,您可能希望它出现在Exit菜单项之前。IMenuItem继承自IExtension,后者有两个属性:

  • InsertRelativeToID
  • BeforeOrAfter

因此,您的扩展将返回SoapBox.Core.Extensions.Workbench.MainMenu.FileMenu.Exit作为InsertRelativeToID,并将返回Before作为BeforeOrAfter属性(一个枚举)。当工作台导入所有文件菜单扩展时,它会基于这些ID对所有内容进行排序。通过这种方式,后来的扩展相对于现有扩展进行插入。


6
最佳实践是使用共享(单例)模式。这引出了一些设计考虑,建议您将可导出的部分设计为无状态和线程安全,以便它们不会受到对同一实例的多次调用(可能在不同的线程上)的影响。
在不适合使用单例模式的情况下,建议使用构建器模式(将导出部分与实际实例化分离)。请记住,使用非共享模式相当昂贵,因为它使用反射进行实际实例化(使用构建器模式可以在更少的痛苦中获得相同的结果)。
此外,请参阅此处http://blogs.microsoft.co.il/blogs/bnaya/archive/2010/01/09/mef-for-beginner-toc.aspx。 当然,您也知道可以在此处找到信息:http://mef.codeplex.com

2
我对MEF还非常陌生,但我想在这个讨论中添加更多内容,因为我经常在试图弄清楚为什么事情不按照我的预期工作时遭受折磨。
首先,在使用MEF时,我建议将System.ComponentModel.Composition添加到您的解决方案中,而不仅仅是添加对程序集的引用。虽然在MEF中调试问题感觉像是递归噩梦,但当您无法弄清楚出了什么问题时,这绝对是不可避免和至关重要的。
这带我到我的下一个观点,那就是永远不要忘记MEF不知道您没有告诉它的东西,或者如果您没有正确地告诉它。例如,我的Alpha应用程序与MEF配合得很好——我在主GUI中组成了部分,由容器加载的所有程序集(这些程序集是主应用程序的依赖项)都导出了必要的接口。事情进展顺利,我能够让MEF在我想要的时间和地点解析实例。
然而,我刚刚开始着手下一个版本;一些插件已加载(那些导出接口但没有导入要求的插件),而其他插件则没有加载(需要导入)。这次我在ApplianceManager类中组成了部分,该类负责加载插件,但插件需要从应用程序中的其他类(在我的情况下是Model)解析导入。我认为这应该自动发生,特别是因为我可以看到在目录构建中检测到了那些程序集……但我仍然无法使它工作……这让我回到了我的第一个观点——添加源代码,而不仅仅是程序集。调试这个问题几乎把我逼疯了,但经过大量勤奋地步进MEF代码之后,我最终会弄清楚的。 :)
我想看到有人发布一个回答,谈论有助于简化MEF集成的体系结构。关于工具栏菜单等的答案确实很好,但我希望看到一些完全驻留在MVVM模型侧面的事情的讨论。例如插件管理器、数据库、插件和共享库应该如何排列。我仍在努力弄清楚为什么我第一个MEF应用程序相对顺利,但在更多“经验”之后,我无法使我的新应用程序100%工作。
更新2010-06-09:
我想再添加一个可能的做法,以帮助您穿越MEF的灌木丛。今天,我需要对我“无法理解”的那个项目进行一个健全性检查,所以我制作了一个简单的非传统UML类图,在其中使用依赖关系标记导入和导出。以下是我发现的内容,这使问题非常清晰。

无法工作的应用程序: alt text

这不是很愚蠢吗?模型没有被加载,而且它孤零零地在一个岛上。我认为这就是为什么我的基于MEF的依赖项无法解析的原因(如果有人可以纠正我,我会非常感激!)


啊...我一定清除了我的账户中的一些图片。我得去找原图。谢谢你告诉我。 - Dave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接