在代码中使用MEF的最佳做法是什么?在启动可扩展应用程序时是否有任何陷阱需要考虑?你有没遇到过你早先应该知道的事情?
答案 0 :(得分:7)
我正在MEF上构建一个完全成熟的可扩展应用程序(并使用带有MVVM模式的WPF)。我采用了我构建的基本应用程序框架,并将其作为SoapBox Core开源。我还在Code Project上发布了一个基于SoapBox Core的演示:Building an Extensible Application with MEF, WPF, and MVVM。
我不确定使用MVVM是否适用于您,但如果确实如此,那么您可以通过查看使用MEF的MVVM实现来学到很多东西。特别是它导入视图的方式。
就最佳实践而言......我创建了一个嵌套的扩展层次结构(因此基本模块称为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扩展(预编程为关闭应用程序)。如果要在“文件”菜单中添加扩展名,可能需要在“退出”菜单项之前显示该扩展名。 IMenuItem继承自IExtension,它有两个属性:
因此,您的扩展将返回InsertRelativeToID的SoapBox.Core.Extensions.Workbench.MainMenu.FileMenu.Exit,并将返回Before以获取BeforeOrAfter属性(枚举)。当工作台导入所有文件菜单扩展时,它会根据这些ID对所有内容进行排序。通过这种方式,后面的扩展相对于现有的扩展插入自己。
答案 1 :(得分:6)
最佳做法是使用Shared(单例)模型。这使我们考虑设计因素,建议您将可导出部分设计为无状态和线程安全,这样它们就不会受到同一实例上多次调用(可能在不同线程上)的影响。
如果单例模型不适合您,建议使用构建器模式(将导出的部分与实际实例化分开)。 您应该记住,使用非共享模型是非常昂贵的,因为它使用反射进行实际实例化(通过使用构建器模式,您可以获得相同的结果,减少痛苦)。
另见http://blogs.microsoft.co.il/blogs/bnaya/archive/2010/01/09/mef-for-beginner-toc.aspx 您知道可以在此处找到信息:http://mef.codeplex.com
答案 2 :(得分:2)
我仍然对MEF很新,但我想在这次讨论中增加更多内容,因为我在试图弄清楚为什么事情不按照我期望的方式工作时不断地经历地狱。
首先,当您使用MEF时,我建议将System.ComponentModel.Composition添加到您的解决方案中,而不是仅仅添加对程序集的引用。虽然MEF中的调试问题感觉像是一个递归的噩梦,但是当你无法弄清楚出了什么问题时,这绝对是不可避免的,也是至关重要的。
这引出了我的下一个观点,即永远不会忘记MEF不知道你不告诉它的内容,或如果你没有正确地告诉它。例如,我的alpha应用程序在MEF中工作得很好 - 我让它在主GUI中组成部分,并且所有由容器加载的程序集(它们是主应用程序的依赖项)都导出了必要的接口。事情很顺利,我能够让MEF在我想要的时间和地点解决实例。
然而,我刚刚开始研究下一个版本;加载了一些插件(导出接口但没有导入要求的插件),而其他插件则没有(那些确实需要导入)。这次我在我的ApplianceManager类中编写了部件,它负责加载插件,但插件需要从应用程序中的其他类(在我的例子中是模型)中解析导入。我认为这应该会自动发生,特别是因为我可以在Catalog构造中看到那些程序集被检测到......但我仍然无法让它工作......这让我回到了我的第一点 - 添加源代码代码,而不仅仅是程序集。调试这几乎让我感到疯狂,但在经过MEF代码的大量努力之后,我最终会弄明白。 :)
我希望看到有人发布这个问题的答案,其中讨论了便于MEF集成的架构。关于工具栏菜单等的答案非常好,但是我希望看到一些关于完全位于MVVM模型方面的内容。就像应该如何安排插件管理器,数据库,插件和共享库一样。我仍然想弄清楚为什么我有一个相当好的时间让我的第一个MEF应用程序工作,但在获得更多的“经验”后,我无法让我的新应用程序100%工作。
更新2010-06-09
我想添加另一种可能的做法,帮助您浏览MEF灌木丛。今天,我需要对那个“我无法弄清楚”的项目进行健全性检查,因此我制作了一个简单的,非常规的UML类图,其中我使用依赖项来标记Imports和Exports。这是我发现的,这使问题非常清楚。
工作申请:
非工作申请:
这不是傻瓜吗?该模型没有加载,它本身就在一个岛上。我相信这就是为什么我的基于MEF的依赖关系没有得到解决的原因(如果有人在我这里错了我可以解释我,我会很感激!)