Cinch V2:我的 Cinch MVVM 框架的第 2 版:第 1 部分






4.93/5 (70投票s)
如果Jack Daniels(一家威士忌品牌)也制作MVVM框架的话。
目录
引言
对于了解我的人来说,你们可能会注意到我没有像往常一样写很多文章,并且可能有点担心我。放心,我没死,我只是非常非常忙而已。
你可能会问,忙什么呢?嗯,有些人可能也知道我非常喜欢WPF,而且我之前写了一个相当受欢迎的ModelView-ViewModel (MVVM) 框架,叫做Cinch,我在CodeProject上发表过。当时,我对那个框架很满意,因为它似乎能处理所有常见的MVVM任务,比如OpenFileDialog/MessageBox/SaveFileDialog弹出窗口等。但正如我们软件工程师/开发人员所知,我们的世界变化很快,偶尔会有些非常棒的东西出现(上次对我来说是LINQ),而现在是MEF。对于那些没听说过MEF的人来说,它是Managed Extensibility Framework的缩写。因此,考虑到这一点,以及现在对附加属性有了更好的支持(以Blend Behaviours/TargettedTriggers的形式),我认为是时候重新审视Cinch并对其进行更新了。
所以我做的正是这件事,在过去一个月左右的时间里我一直在努力工作,现在我已经完成了Cinch的更新,并且对结果非常满意。对于那些没读过我原来的Cinch文章的人,你可以在这里找到它们的链接:这里。
我应该提到一点,Cinch V1只针对WPF。对于Cinch V2,这一点已经改变,Cinch V2与Silverlight(4或更高版本)和WPF的兼容性一样好。Cinch V2是一个相当大的框架,试图处理许多不同的事情;因此,有很多类,我认为即使有这篇文档,如果没有兼容性矩阵,你还是会觉得有点混乱。事实上,我认为这个兼容性矩阵非常重要,它将包含在每一篇Cinch V2文章的引言中。
兼容性矩阵列出了类及其一般工作区域,以及它们是否与WPF或SL或两者兼容。
工作区域 | 类名 | WPF | Silverlight(4或更高版本) | 两者 |
业务对象 | EditableValidatingObject.cs | 是 | ||
业务对象 | ValidatingObject.cs | 是 | ||
业务对象 | DataWrapper.cs | 是 | ||
Commands | EventToCommandArgs.cs | 是 | ||
Commands | SimpleCommand.cs | 是 | ||
Commands | WeakEventHandlerManager.cs | 是 | ||
事件 | CloseRequestEventArgs.cs | 是 | ||
事件 | UICompletedEventArgs.cs | 是 | ||
弱事件 | WeakEvent.cs | 是 | ||
弱事件 | WeakEventHelper.cs | 是 | ||
弱事件 | WeakEventProxy.cs | 是 | ||
扩展方法 | DispatcherExtensions.cs | 是 | ||
扩展方法 | GenericListExtensions.cs | 是 | ||
交互行为 | CommandDrivenGoToStateAction.cs | 是 | ||
交互行为 | FocusBehaviourBase.cs | 是 | ||
交互行为 | NumericTextBoxBehaviour.cs | 是 | ||
交互行为 | SelectorDoubleClickCommandBehavior.cs | 是 | ||
交互行为 | TextBoxFocusBehavior.cs | 是 | ||
交互触发器 | CompletedAwareCommandTrigger.cs | 是 | ||
交互触发器 | CompletedAwareGotoStateCommandTrigger.cs | 是 | ||
交互触发器 | EventToCommandTrigger.cs | 是 | ||
消息中介者 | MediatorMessageSinkAttribute.cs | 是 | ||
消息中介者 | MediatorSingleton.cs | 是 | ||
服务实现 | ChildWindowService.cs | 是 | ||
服务实现 | SLMessageBoxService.cs | 是 | ||
服务实现 | ViewAwareStatus.cs | 是 | ||
服务实现 | ViewAwareStatusWindow.cs | 是 | ||
服务实现 | VSMService.cs | 是 | ||
服务实现 | WPFMessageBoxService.cs | 是 | ||
服务实现 | WPFOpenFileService.cs | 是 | ||
服务实现 | WPFSaveFileService.cs | 是 | ||
服务实现 | WPFUIVisualizerService.cs | 是 | ||
服务接口 | IChildWindowService.cs | 是 | ||
服务接口 | IMessageBoxService.cs | 是 | ||
服务接口 | IViewAwareStatus.cs | 是 | ||
服务接口 | IViewAwareStatusWindow.cs | 是 | ||
服务接口 | IVSM.cs | 是 | ||
服务接口 | IMessageBoxService.cs | 是 | ||
服务接口 | IOpenFileService.cs | 是 | ||
服务接口 | ISaveFileService.cs | 是 | ||
服务接口 | IUIVisualizerService.cs | 是 | ||
服务测试实现 | TestChildWindowService.cs | 是 | ||
服务测试实现 | TestMessageBoxService.cs | 是 | ||
服务测试实现 | TestViewAwareStatus.cs | 是 | ||
服务测试实现 | TestViewAwareStatusWindow.cs | 是 | ||
服务测试实现 | TestVSMService.cs | 是 | ||
服务测试实现 | TestMessageBoxService.cs | 是 | ||
服务测试实现 | TestOpenFileService.cs | 是 | ||
服务测试实现 | TestSaveFileService.cs | 是 | ||
服务测试实现 | TestUIVisualizerService.cs | 是 | ||
多线程 | AddRangeObservableCollection.cs(这是特定于SL的实现) | 是 | ||
多线程 | AddRangeObservableCollection.cs(这是特定于WPF的实现) | 是 | ||
多线程 | BackgroundTaskManager.cs | 是 | ||
多线程 | ISynchronizationContext.cs | 是 | ||
多线程 | UISynchronizationContext.cs | 是 | ||
多线程 | ApplicationHelper.cs | 是 | ||
多线程 | DispatcherNotifiedObservableCollection.cs | 是 | ||
菜单 | CinchMenuItem.cs | 是 | ||
实用程序 | ArgumentValidator.cs | 是 | ||
实用程序 | IWeakEventListener.cs(这是一个Silverlight中缺失的System 类,所以我创建了它) |
是 | ||
实用程序 | ObservableHelper.cs | 是 | ||
实用程序 | PropertyChangedEventManager.cs(这是一个Silverlight中缺失的System 类,所以我创建了它) |
是 | ||
实用程序 | PropertyObserver.cs | 是 | ||
实用程序 | BindingEvaluator.cs | 是 | ||
实用程序 | ObservableDictionary.cs | 是 | ||
实用程序 | TreeHelper.cs | 是 | ||
验证 | RegexRule.cs | 是 | ||
验证 | Rule.cs | 是 | ||
验证 | SimpleRule.cs | 是 | ||
ViewModels | EditableValidatingViewModelBase.cs | 是 | ||
ViewModels | IViewStatusAwareInjectionAware.cs | 是 | ||
ViewModels | ValidatingViewModelBase.cs | 是 | ||
ViewModels | ViewMode.cs | 是 | ||
ViewModels | ViewModelBase.cs | 是 | ||
ViewModels | ViewModelBaseSLSpecific.cs | 是 | ||
ViewModels | ViewModelBaseWPFSpecific.cs | 是 | ||
Workspaces | ChildWindowResolver.cs | 是 | ||
Workspaces | CinchBootStrapper.cs(Silverlight版本) | 是 | ||
Workspaces | CinchBootStrapper.cs(WPF版本) | 是 | ||
Workspaces | PopupNameToViewLookupKeyMetadataAttribute.cs | 是 | ||
Workspaces | IWorkspaceAware.cs | 是 | ||
Workspaces | MockView.cs | 是 | ||
Workspaces | NavProps.cs | 是 | ||
Workspaces | PopupResolver.cs | 是 | ||
Workspaces | ViewnameToViewLookupKeyMetadataAttribute.cs | 是 | ||
Workspaces | ViewResolver.cs | 是 | ||
Workspaces | WorkspaceData.cs | 是 |
现在我已经向您展示了哪些类可以与WPF/Silverlight一起使用,让我们继续本文的其余部分,好吗?但首先,这里是旧Cinch V1文章的链接。
万一你错过了Cinch V1,并且对MVVM感兴趣,我强烈建议你先阅读所有Cinch V1的文章,这将让你更深入地理解这些Cinch V2文章的内容。
Cinch V1 文章链接
有些人可能从未见过旧的Cinch V1文章,所以我也会在这里列出它们,因为Cinch V2仍然使用与Cinch V1相同的功能,我将把人们重定向到这些文章。
- Cinch入门文章
- Cinch及其内部机制的演练 I
- Cinch及其内部机制的演练 II
- 如何使用Cinch开发ViewModels
- 如何使用Cinch对ViewModels进行单元测试,包括如何测试可能在Cinch ViewModels中运行的BackgroundWorker线程
- 使用Cinch的演示应用程序
有什么新内容
我收到了很多关于Cinch V1的邮件,很多人都在使用它,并且普遍对其框架非常积极和感激,那么为什么我会考虑改变它呢?嗯,简单的答案是,更好的东西出现了,我喜欢与时俱进,这是我的框架,所以我认为如果我想改变它,我有权这样做,所以我做了。
问题:如果我正在使用Cinch V1,我的代码库是否仍然有效?
答案:大部分核心的Cinch V1类和功能都已保留,但在代码库的几个区域,我不得不进行重大更改,或者我想出了一个更好的处理方式,迫使我进行重大更改。在大多数情况下,我相当有信心这些问题可以轻松解决。那么,哪些东西改变了呢?嗯,它们如下:
- 服务注入/IOC
- BackgroundTaskManager
- 日志记录
- MVVM菜单
- Commands
- Workspaces
- 附加行为
- View生命周期DPs
- 按键手势触发命令
这看起来可能很多,但我相信一旦你看到新框架的功能,你就会明白为什么我不得不做这些改变。老实说,我对新框架比对旧框架满意得多,所以我并不介意这些中断。“不破不立”。
如果我目前使用Cinch V1,什么会失效
正如刚所说,以下区域将会失效:
服务
服务可用性的方式完全不同。以前使用Unity IOC容器,并可以选择提供不同的IOC容器。现在它独家使用MEF。
因此,ViewModelBase
上不再有服务解析器,所有服务都应作为ViewModel构造函数注入。服务解析器的丢失,我其实很乐意,因为它有点破坏了单一职责模式。本文将全部介绍新服务如何工作,因此到本文结束时,您将更了解Cinch V2采用的新方法。
BackgroundTaskManager
我意识到我从未允许将任何状态对象传递给后台工作线程Func<T>
委托,所以我对BackgroundTaskManager<T>
做了一些小的修改,现在它是BackgroundTaskManager<TArg, TResult>
,以适应这一点。您会注意到一个新的WorkerArgument
属性,它的类型是object
,现在新的工作Func
委托看起来是这样的:Func<TArg, TResult>
,其中WorkerArgument
在BackgroundTaskManager<TArg, TResult>
代码内部被强制转换为TArg
类型。
Cinch日志
Cinch V1提供了使用log4Net和Simple Logging Facade (SLF) 的基本日志记录。我对此进行了思考,我认为框架中的日志记录不是一个好主意,因为我实际上迫使人们单独使用log4Net进行Cinch,然后他们还需要在此基础上添加自己的日志需求。所以,经过反复考虑,我决定为Cinch V2移除日志记录;如果您认为这是一个坏主意,请告诉我,我可以将其添加回来。
MVVM菜单项
它们的工作方式与我在Cinch V1文章CinchIII.aspx#WPFMenuItems中讨论的相同,但Cinch V1仅针对WPF,而Cinch V2现在支持Silverlight,所以保留名为WPFMenuItem
的MVVM菜单似乎不太合适,因此所做的唯一改变是将类名更改为更适合跨技术框架的名称,新名称是CinchMenuItem
。
命令,也称为SimpleCommand
Cinch V1引入了一个简单的委托式ICommand
实现,当时它还不错,但对于Cinch V2,SimpleCommand
得到了极大的改进,现在需要两个泛型参数。SimpleCommand
现在看起来是这样的:SimpleCommand<T1,T2>
,其中T1
是ICommand.CanExecute(Object parameter)
的参数类型(所以这实际上是CanExecute(T1 parameter)
),而T2
是ICommand.Execute(Object parameter)
的参数类型(所以这实际上是Execute(T2 parameter)
。这个新的SimpleCommand
实现还有更多内容。我将在后续文章中讨论。
Workspaces
Cinch V1通过DataTemplate
来提供工作区。我重新审视了这部分,使DataTemplate
在视图优先场景下工作,保持了视图设计者的友好性和Blendable性。新的工作区管理还允许将一些上下文数据发送到视图。
附加行为
Cinch V1包含了一些附加行为,例如:
NumericTextBox
SelectorDoubleClick
EventCommander
SingleEventCommand
在Cinch V2中,所有这些都已迁移到基于Blend交互的行为/触发器,这有助于提高Blendability。
视图生命周期事件
Cinch V1有两个附加DP,可用于在各种视图生命周期事件(如Loaded/Unloaded)发生时触发ViewModel的ICommand
。
LifetimeEvent
UserControlLifeTimeEvent
在Cinch V2中,这两个附加DP已被一个名为ViewAwareStatus
的UI服务取代,我们将在本文和下一篇文章中讨论它。
按键手势触发ViewModel中的ICommands
Cinch V1有附加DP,可用于在各种按键手势(如CTRL + F1)发生时触发ViewModel的ICommand
。
BindableInput
在Cinch V2中,对此完全没有支持,因为WPF 4/.NET 4.0已经内置了此功能。
我认为这完成了会失效的部分;虽然不少,但我真的相信迁移到Cinch V2不会太痛苦,而且说实话,我很高兴V2的最终结果,我认为这些小中断是值得的。
Cinch V2 文章链接
- Cinch V2:介绍以及MEFedMVVM和ViewModel/Service解析(本文)
- Cinch V2:服务/UI服务
- Cinch V2:全新功能
- Cinch V2:深入分析变化和不变之处
- Cinch V2:解剖WPF演示应用程序
- 解剖SL4演示应用程序
这就是文章路线图的样子,我想现在是时候深入本文了,让我们开始吧!
MEFedMVVM 和 ViewModel/Service 解析
现在,那些了解我的人会知道我喜欢代码,而且我喜欢自己编写代码,偶尔,我也能与他人很好地合作(不像狒狒,它们不擅长与他人合作,真调皮的狒狒)。
因此,考虑到这一点,在我们继续之前,我需要告诉你们一个简短的故事。
在我决定重写Cinch的时候,我知道我想使用MEF,我开始研究它,大约4天后,Marlon Grech(C# MVP / WPF Disciples创始人)向小组发送了一封关于他开发的酷炫MEF ViewModel解析器项目的邮件,几天后,Glenn Block(Microsoft MEF项目经理)也向小组发送了邮件,说他想出了一个酷炫的MEF ViewModel解析器项目。
有趣的东西……选择,选择,选择。
现在,我为Cinch V2想要的只是没有除标准Microsoft依赖项之外的任何依赖项,所以我开始检查这两个库,并创建了我自己的启用了MEF的ViewModel解析器项目,我确实完成了它,它在很大程度上基于Glenn Block的方案,他称之为“Brook”,谢谢你,Glenn。
然后发生的事情是,我一直在读写Marlon的实现,不断地烧脑,最后,我只是觉得Marlon的实现更接近我想要做的,然后我又收到了Marlon发来的一封邮件,说他将就他的库做一个演讲,我参加了。
在那个演讲中,Marlon展示了你可以基本上引用他的库和几个属性,然后你就会得到:
- 视图优先(对设计者/Blend友好,我认为这是我们做MVVM时应该追求的)
- 视图-ViewModel的连接
- 用于显示设计时数据的设计时服务
- 用于显示运行时数据的运行时服务
- 漂亮的扩展点,可以轻松实现策略模式,从而实现单元测试的Mock
- 支持多个XAP文件(在处理大型Silverlight项目时,我认为这是必须的)
- 如果你不喜欢Marlon的处理方式,可以替换你自己的容器解析策略(但这不会发生,你会喜欢他处理方式的,因为我认为那是正确的方式)
所以最终,经过深思熟虑,我决定为Cinch V2引入对Marlon的作品(他称之为MefedMVVM)的依赖。这不是我轻易做出的决定,但一旦你看到Marlon的库的功能,我相信你们都会同意这是值得的。
本文的其余部分将专门介绍在Cinch V2的上下文中,如何使用Marlon的MefedMVVM。
什么是MEF
在我们深入了解Marlon的MefedMVVM的精妙之处之前,我认为最好先稍微介绍一下MEF;毕竟,MefedMVVM完全基于MEF,所以需要对MEF有一定的了解。
大概是了解MEF的最好地方是MEF CodePlex站点,你可以通过本文中的任何一个MEF链接访问。
我从MEF CodePlex站点摘录了以下内容:http://mef.codeplex.com/wikipage?title=Overview&referringTitle=Home。
什么是MEF?
Managed Extensibility Framework(简称MEF)简化了可扩展应用程序的创建。MEF提供了发现和组合功能,你可以利用这些功能来加载应用程序扩展。
MEF解决了什么问题?
MEF为运行时可扩展性问题提供了一个简单的解决方案。到目前为止,任何想要支持插件模型的应用程序都需要从头开始创建自己的基础设施。这些插件通常是应用程序特定的,无法在多个实现之间重用。
- MEF提供了一种标准的方法,使宿主应用程序能够暴露自己并消费外部扩展。扩展本质上可以被不同应用程序重用。然而,一个扩展仍然可以以应用程序特定的方式实现。扩展本身可以相互依赖,MEF将确保它们按正确的顺序连接在一起(这是你无需担心的另一件事)。
- MEF提供了一套发现方法,供应用程序定位和加载可用的扩展。
- MEF允许为扩展添加元数据,从而便于进行丰富的查询和筛选。
MEF如何工作?
粗略地说,MEF的核心由一个目录(catalog)和一个CompositionContainer组成。目录负责发现扩展,而容器协调创建并满足依赖关系。
- MEF的一等公民是ComposablePart(参见Parts)。一个可组合部分提供一个或多个Exports,并且可能依赖于一个或多个外部提供的服务或Imports。可组合部分还管理一个实例,该实例可以是给定类型的对象实例(这是默认MEF实现中的情况)。然而,MEF是可扩展的,并且可以提供额外的ComposablePart实现,只要它们符合Import/Export合同。
- Exports和Imports都有一个Contract。Contracts是exports和imports之间的桥梁。一个export contract可以包含进一步的元数据,用于在发现时进行过滤。例如,它可以指示export提供的特定功能。
- MEF的容器与Catalogs交互,以访问可组合部分。容器本身解析部分的依赖关系,并将exports暴露给外部世界。如果你愿意,你可以直接将可组合部分实例添加到容器中。
- 目录返回的ComposablePart很可能是应用程序的扩展。它可能拥有对宿主应用程序提供的组件的imports(依赖项),并且很可能导出其他组件。
- 默认的MEF可组合部分实现使用基于属性的元数据来声明exports和imports。这允许MEF通过发现完全确定哪些部分、imports和exports可用。
这张图试图说明MEF工作的一些内部机制。
关于目录的简要说明
在MEF中,有多种形式的Catalogs,它们都做着略微不同的事情,以解析Parts(Imports/Exports)。我们来看看其中一些Catalog类型,好吗?
- AssemblyCatalog:用于发现给定程序集中的所有exports。
- DirectoryCatalog:用于发现目录中所有程序集中的所有exports。
- AggregateCatalog:当AssemblyCatalog和DirectoryCatalog单独不足以满足需求,需要组合使用时,应用程序可以使用AggregateCatalog。AggregateCatalog将多个目录组合成一个目录。一个常见的模式是添加当前执行的程序集,以及第三方扩展的目录。你可以将目录集合传递给AggregateCatalog的构造函数,或者直接添加到
Catalogs
集合中,即catalog.Catalogs.Add(...)
。- TypeCatalog:用于发现一组特定类型中的所有exports。
- DeploymentCatalog (SL only):Silverlight中的MEF包含DeploymentCatalog,用于动态下载远程XAPs。
因此,一旦你对目录设置有了大致的了解,你就必须配置CompositionContainer,那里提供了所有当前的部分。
以上是对MEF工作原理的快速浏览,现在我们将深入了解Marlon的MefedMVVM在Cinch V2中的内部工作原理。
介绍MefedMVMM
正如我之前提到的,Cinch V2依赖于Marlon Grech的杰出作品MefedMVVM。即使你最终不喜欢或不使用Cinch V2,你也应该花时间去了解MefedMVVM,因为它非常棒。
那么,Marlon试图用MefedMVVM做什么?Cinch V2又是如何能够如此轻松地使用它呢?嗯,Marlon的目标是创建一个库,可以以最小的麻烦与任何其他MVVM框架一起使用。
他实现了这一点吗?当然。用MefedMVVM配合Cinch V2使用起来简直易如反掌,只需要一个引用、几个属性和一个视图XAML中的附加DP,它就能工作。
回想一下我之前说的话,这就是使用MefedMVVM,因此也与Cinch V2一起开箱即用的功能:
- 视图优先(对设计者/Blend友好,我认为这是我们做MVVM时应该追求的)
- 视图-ViewModel的连接
- 用于显示设计时数据的设计时服务
- 用于显示运行时数据的运行时服务
- 漂亮的扩展点,可以轻松实现策略模式,从而实现单元测试的Mock
- 支持多个XAP文件(在处理大型Silverlight项目时,我认为这是必须的)
- 如果你不喜欢Marlon的处理方式,可以替换你自己的容器解析策略(但这不会发生,你会喜欢他处理方式的,因为我认为那是正确的方式)
man……这性价比太高了……我向Marlon致敬,他功劳巨大,他做得非常出色,而且使用起来非常简单。Marlon,非常感谢。
现在你知道Cinch V2正在使用MefedMVVM,让我们深入了解这一切是如何工作的,好吗?
基本思路
MefedMVVM的基本思想是涵盖以下方面:
- 视图应能够通过使用单个附加DP来定位其ViewModel。
- ViewModel将被找到,并且所有必需的服务(构造函数参数)将由步骤1中的ViewModel解析附加DP自动注入。
- 如果视图在设计时在Blend中被查看,则使用设计时服务来提供设计时数据。
几点说明
我想再详细讨论一下我刚才提到的几点。
Blend vs. VS2010
这就是基本思路。有些人可能注意到我谈论的是Blend设计时支持,而不是VS2010。情况就是这样;据我所知,VS2010不支持设计时数据,而MefedMVVM基本上只在Blend中工作。我无法验证这一点,因为VS2010似乎甚至无法打开任何使用Blend“System.Windows.Interactivity.dll”的视图,在我看来这简直是疯了。当然,我可能错了。
设计时数据
正如你们中的一些人可能知道的,Blend允许你使用d:DataContext
设计时属性来创建设计时数据,Blend会根据你的实际ViewModel为你创建一个设计时ViewModel。
Karl Shifflett 有一篇很好的文章介绍这个:http://karlshifflett.wordpress.com/2009/10/28/ddesigninstance-ddesigndata-in-visual-studio-2010-beta2/
这还可以,我猜,但据我所知,它有一些相当严格的限制,比如:
- 所有属性都必须是读/写(getter/setter)。
- 所讨论的ViewModel必须有一个默认构造函数。
- 使XAML混淆,带有大量的设计时标记。
这可能适合你,但我告诉你,这并不适合我。我不想有一个允许我的ViewModel可能处于无效状态的默认构造函数。好吧,我可以从默认构造函数调用另一个构造函数,但读/写属性呢,我可能不想要。
现在我不是在贬低任何人,我认为微软在倾听社区的声音并提供工具来支持设计时数据方面做得很好。只是对我而言,我宁愿没有默认构造函数,也不希望我所有的属性都是读/写。
那么还有什么选择呢?嗯,MefedMVVM引入了UI服务来提供数据。这些UI服务可以在运行时/设计时或两者兼而有之。
这种方法的优点是:
- 你的ViewModel没有默认构造函数。
- 视图的XAML中没有设计时标记。
- 你的所有属性都可以设计为只读或读/写访问。
- 你在设计时和运行时使用ViewModel的方式相同;它将是相同的ViewModel,根据你是在设计时还是运行时,它将注入设计时或运行时服务。
总之,这应该让你对MefedMVVM如何处理事务有一个初步了解。让我们继续吧,好吗?
视图-ViewModel 解析
当你想要使用设计时数据和MefedMVVM(以及因此的Cinch V2)时,你需要考虑的最重要的事情之一是视图优先的方法,它允许设计者实际看到最终产品的样子。正如我之前所说,MefedMVVM通过使用一个附加属性来实现这一点,该属性允许视图动态地查找其ViewModel。
让我们看一看,好吗?在MefedMVVM中,有一个名为ViewModel
的附加DP,位于ViewModelLocator
类中。我们只需要像下面这个示例视图中那样使用那个附加DP:
<UserControl x:Class="CinchV2DemoWPF.ImageLoaderView"
xmlns:CinchV2="clr-namespace:Cinch;assembly=Cinch.WPF"
xmlns:meffed="http:\\www.codeplex.com\MEFedMVVM"
meffed:ViewModelLocator.ViewModel="ImageLoaderViewModel">
</UserControl>
让我们继续从ViewModel
DP向下跟踪,看看MefedMVVM到底为我们做了什么。下面是实际DP代码的样子:
/// <summary>
/// ViewModel Attached Dependency Property
/// </summary>
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.RegisterAttached("ViewModel", typeof(string), typeof(ViewModelLocator),
new PropertyMetadata((string)String.Empty,
new PropertyChangedCallback(OnViewModelChanged)));
/// <summary>
/// Gets the ViewModel property. This dependency property
/// indicates ....
/// </summary>
public static string GetViewModel(DependencyObject d)
{
return (string)d.GetValue(ViewModelProperty);
}
/// <summary>
/// Sets the ViewModel property. This dependency property
/// indicates ....
/// </summary>
public static void SetViewModel(DependencyObject d, string value)
{
d.SetValue(ViewModelProperty, value);
}
/// <summary>
/// Handles changes to the ViewModel property.
/// </summary>
private static void OnViewModelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
try
{
string vmContractName = (string)e.NewValue;
var element = d as FrameworkElement;
if (!String.IsNullOrEmpty(vmContractName) && element != null)
{
ViewModelRepository.AttachViewModelToView(vmContractName, element);
}
}
catch (Exception ex)
{
Debug.WriteLine("Error while resolving ViewModel. " + ex);
}
}
可以看出,它使用属性的值作为ViewModel契约名称,然后调用ViewModelRepository.AttachViewModelToView
方法,同时传入ViewModel契约名称和实际视图元素。继续沿着这条线索,让我们看看ViewModelRepository.AttachViewModelToView
方法,它看起来是这样的:
public static void AttachViewModelToView( string vmContract, FrameworkElement view)
{
var vmExport = Instance.Resolver.GetViewModelByContract(vmContract, view);
if (vmExport != null)
{
Debug.WriteLine("Attaching ViewModel " +
vmExport.Metadata[ExportViewModel.NameProperty]);
if ((bool)vmExport.Metadata[ExportViewModel.IsDataContextAwareProperty])
ViewModelRepository.Instance.dataContextAwareVMInitializer.CreateViewModel(
vmExport, view);
else
ViewModelRepository.Instance.basicVMInitializer.CreateViewModel(vmExport, view);
}
else
{
RegisterMissingViewModel(vmContract, view);
}
}
可以看出,这段代码现在正在尝试通过使用Instance.Resolver.GetViewModelByContract()
方法来获取一个已解析(MEF Export)的ViewModel实例。一旦有了实例,它就会调用两个CreateViewModel()
初始化方法中的一个。稍后详细讨论;现在,我只想谈谈ViewModel Export最初是如何解析的。正如我刚所说,MefedMVVM使用Instance.Resolver.GetViewModelByContract()
来实现这一点,所以让我们来看看:
/// <summary>
/// Gets the ViewModel export
/// </summary>
/// <param name="vmContractName">The contract for the view model to get</param>
/// <returns></returns>
public Export GetViewModelByContract(string vmContractName, object contextToInject)
{
if(Container == null)
return null;
var viewModelTypeIdentity = AttributedModelServices.GetTypeIdentity(typeof(object));
var requiredMetadata = new Dictionary<string, Type>();
requiredMetadata[ExportViewModel.NameProperty] = typeof(string);
requiredMetadata[ExportViewModel.IsDataContextAwareProperty] = typeof(bool);
var definition = new ContractBasedImportDefinition(
vmContractName, viewModelTypeIdentity,
requiredMetadata, ImportCardinality.ZeroOrMore, false,
false, CreationPolicy.Any);
SetContextToExportProvider(contextToInject);
var vmExports = Container.GetExports(definition);
SetContextToExportProvider(null);
var vmExport = vmExports.FirstOrDefault(
e => e.Metadata[ExportViewModel.NameProperty].Equals(vmContractName));
if (vmExport != null)
return vmExport;
return null;
}
正如我们所见,我们正在进行某种查询,其中我们使用元数据(这是MEF自带的功能)来查询一个特定的Export,其中NameProperty ==传入的ViewModel名称(它来自原始的View附加DP(meffed:ViewModelLocator.ViewModel="ImageLoaderViewModel"
))。这很酷,我们现在有一个与我们指定的ViewModel匹配的Export(ViewModel)。是不是看起来一切都很酷?但是等等,我们在View上有一个查找字符串,这怎么能让我们得到一个ViewModel呢?
嗯,答案在于使用自定义的Export属性,例如这个:
[ExportViewModel("ImageLoaderViewModel")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ImageLoaderViewModel : ViewModelBase
{
}
其中ExportViewModelAttribute
可在MefedMVVM中使用,它继承自MEF的ExportAttribute
,因此拥有使用标准MEFExportAttribute
的所有好东西,例如元数据,这就是上面的代码能够查询Exports元数据以查找元数据Name值等于请求ViewModel的Export的原因。
注意:需要注意的是,你还必须决定当ViewModel被MEF化时,你希望使用什么样的实例化策略。假设你想要独立的实例(我认为这是最常见的需求),你将使用[PartCreationPolicy(CreationPolicy.NonShared)]
;否则,你将选择[PartCreationPolicy(CreationPolicy.Shared)]
,它每次都会给你相同的单例共享VM。
在实际操作中,你只需要在你的视图中使用meffed:ViewModelLocator.ViewModel
附加DP,并用MefedMVVM的ExportViewModelAttribute
标记你的ViewModel,并决定导出的ViewModel所需的实例化(单例(共享)或新实例(非共享)),搞定。
服务解析
那么,到现在为止,你一定已经意识到,MefedMVVM和Cinch V2都广泛使用服务。这些服务可以是数据服务(在设计时或运行时提供数据),也可以是核心框架服务,如MessageBox/OpenFile/SaveFile服务。
实际上,这并不重要,在MefedMVVM(以及因此的Cinch V2)中,它们的处理方式是相同的。我们真正要实现的目标是,MefedMVVM为视图构建的ViewModel(如上所述)在构造函数参数中传递了正确的服务。
注意:这与Cinch V1中服务的工作方式不同,不再有IOC容器或ServiceResolver。 MefedMVVM为我们处理了所有这些。
那么它究竟是如何做到的呢?嗯,和以前一样,有一个特殊的属性叫做ExportServiceAttribute
,可以用来标记你的服务。让我们来看几个例子。
通用核心服务示例
假设你有一个核心通用服务,它的服务合同如下:
using System;
using System.Windows;
namespace Cinch
{
/// <summary>
/// This interface defines a interface that will allow
/// a ViewModel to open a file
/// </summary>
public interface IOpenFileService
{
/// <summary>
/// FileName
/// </summary>
String FileName { get; set; }
/// <summary>
/// Filter
/// </summary>
String Filter { get; set; }
/// <summary>
/// Filter
/// </summary>
String InitialDirectory { get; set; }
/// <summary>
/// This method should show a window that allows a file to be selected
/// </summary>
/// <param name="owner">The owner window of the dialog</param>
/// <returns>A bool from the ShowDialog call</returns>
bool? ShowDialog(Window owner);
}
}
然后可以用它来实现实际的服务,如下所示:
using System;
using System.Collections.Generic;
using System.Windows;
using Microsoft.Win32;
using System.ComponentModel.Composition;
using MEFedMVVM.ViewModelLocator;
namespace Cinch
{
/// <summary>
/// This class implements the IOpenFileService for WPF purposes.
/// </summary>
[PartCreationPolicy(CreationPolicy.Shared)]
[ExportService(ServiceType.Both, typeof(IOpenFileService))]
public class WPFOpenFileService : IOpenFileService
{
.....
.....
}
}
看看我们如何简单地使用MefedMVVM的ExportServiceAttribute
,并告诉它这是一个通用服务,应该同时用于设计时和运行时?另一个值得注意的地方是,由于我们使用的是MEF,我们可以利用标准的MEF PartCreationPolicyAttribute
来指定部件的生命周期和实例化。在这种情况下,使用了CreationPolicy.Shared
,因此只有一个实例会被共享给所有导入者。
设计时与运行时服务示例
考虑一个不同的例子,假设我们需要获取一些数据(例如,从WCF服务或文件系统,随便什么),并且我们希望能够可视化此服务的时设计时数据,我们可能会从如下的服务合同开始:
/// <summary>
/// Data service used by the <c>ImageLoaderViewModel</c> to obtain data
/// </summary>
public interface IImageProvider
{
void FetchImages(string imagePath, Action<List<ImageData>> callback);
}
然后,我们有一个运行时服务实现,看起来像这样(注意ServiceType
设置为ServiceType.Runtime
,并且这次我们不希望共享服务,所以我们只需使用CreationPolicy.NonShared
来确保每个ViewModel都获得自己的副本):
/// <summary>
/// Runtime implementation of the
/// Data service used by the <c>ImageLoaderViewModel</c> to obtain data
/// </summary>
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportService(ServiceType.Runtime, typeof(IImageProvider))]
public class RunTimeImageProvider : IImageProvider
{
}
然后,我们将拥有相同的服务定义,但用于设计时,可以在主Assembly
中,也可以在完全不同的Assembly
中。在与Cinch V2相关的演示应用程序中,我使用完全独立的设计时程序集,这些程序集甚至不被主应用程序引用,并且设计时数据仍然由MefedMVVM支持,这部分归功于Blend的工作方式,但稍后会详细介绍。现在,重要的是要注意,设计服务看起来与运行时服务相同,但上面设置了ServiceType.Designtime
。
/// <summary>
/// Runtime implementation of the
/// Data service used by the <c>ImageLoaderViewModel</c> to obtain data
/// </summary>
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportService(ServiceType.DesignTime, typeof(IImageProvider))]
public class DesigntimeImageProvider : IImageProvider
{
}
所以,这就是创建核心服务和设计时/运行时服务所需的全部内容。很简单,不是吗?
但我猜你们想知道这一切是如何运作的。精确地说,MefedMVVM如何知道要将哪些服务注入到解析的ViewModel构造函数中?嗯,答案部分在于理解Blend是如何工作的(请记住,MefedMVVM realmente的目标是与Blend一起工作)。
当你在Blend中加载一个解决方案时,Blend是如何工作的?嗯,Blend有一个AppDomain
,它知道当前解决方案文件中的所有程序集。所以你可以利用这一点,看看其中任何一个程序集是否引用了MefedMVVM DLL,如果它引用了,那么它就是一个候选者,可以进行检查以导出部件(服务/ViewModel)。而这正是MefedMVVM所做的,所以基本上,当你添加对MefedMVVM DLL的引用时,你就允许MefedMVVM将引用MefedMVVM的程序集包含在包含Export
的整体CompositionContainer
中。
这就是为什么你可以拥有一个完全独立的设计时服务DLL,而主应用程序甚至不引用它。
这是解释MefedMVVM最初如何检查正确DLLs的谜题的一部分。但是MefedMVVM如何知道要将这两个服务中的哪一个注入到解析的ViewModel中呢?嗯,这是通过两样东西实现的:
让我们来分析一下这两个项目的内部工作原理,好吗?
MefedMVVM引导程序
引导程序负责设置设计时组合器或运行时组合器,并将它们添加到MEFedMVVMCatalog
中。这是来自MefedMVVM引导程序的代码:
/// <summary>
/// Checks if the bootstapper is inialized if now it inializes it
/// </summary>
public static CompositionContainer EnsureLocatorBootstrapper()
{
IComposer composer = null;
if (Designer.IsInDesignMode)
{
if (designTimeComposer == null)
designTimeComposer = new DefaultDesignTimeComposer();
composer = designTimeComposer;
}
else
{
// if the composer is not set then we should use the default one
if (runtimeComposer == null)
runtimeComposer = new DefaultRuntimeComposer();
composer = runtimeComposer;
}
var catalog = composer.InitializeContainer();
MEFedMVVMExportProvider provider =
new MEFedMVVMExportProvider(MEFedMVVMCatalog.CreateCatalog(catalog));
CompositionContainer container = new CompositionContainer(provider);
provider.SourceProvider = container;
return container;
}
你能看到那里有一个DefaultDesignTimeComposer
吗?我们来看看:
/// <summary>
/// Default composer for Design time. This will load all assemblies that
/// have the DesignTimeCatalog attibute
/// </summary>
public class DefaultDesignTimeComposer : IComposer
{
#region IComposer Members
public ComposablePartCatalog InitializeContainer()
{
return GetCatalog();
}
#endregion
private AggregateCatalog GetCatalog()
{
IList<AssemblyCatalog> assembliesLoadedCatalogs =
(from assembly in AppDomain.CurrentDomain.GetAssemblies()
//only load assemblyies with this attribute
//where assembly.GetCustomAttributes(
// typeof(DesignTimeCatalogAttribute), true).Count() != 0
where assembly.GetReferencedAssemblies().Where(
x => x.Name.Contains("MEFedMVVM.WPF")).Count() > 0 ||
assembly.ManifestModule.Name == "MEFedMVVM.WPF.dll"
select new AssemblyCatalog(assembly)).ToList();
if (assembliesLoadedCatalogs.Where(x => x.Assembly.ManifestModule.Name
!= "MEFedMVVM.WPF.dll").Count() == 0)
{
Debug.WriteLine("No assemblies found for Design time. Quick tip... ");
return null;
}
var catalog = new AggregateCatalog();
foreach (var item in assembliesLoadedCatalogs)
catalog.Catalogs.Add( item);
return catalog;
}
}
这是DefaultRuntimeComposer
:
/// <summary>
/// Implementation for a default runtime composer
/// </summary>
public class DefaultRuntimeComposer : IComposer
{
#region IComposer Members
public ComposablePartCatalog InitializeContainer()
{
return GetCatalog();
}
#endregion
private AggregateCatalog GetCatalog()
{
var catalog = new AggregateCatalog();
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
var extensionPath = String.Format(@"{0}\Extensions\", baseDirectory);
//catalog.Catalogs.Add( MEFedMVVMCatalog.CreateCatalog(
// new DirectoryCatalog(baseDirectory) ));
//catalog.Catalogs.Add( MEFedMVVMCatalog.CreateCatalog(
// new DirectoryCatalog(baseDirectory, "*.exe" )));
//if (Directory.Exists(extensionPath))
// catalog.Catalogs.Add(MEFedMVVMCatalog.CreateCatalog(
// new DirectoryCatalog(extensionPath)));
catalog.Catalogs.Add(new DirectoryCatalog(baseDirectory));
catalog.Catalogs.Add(new DirectoryCatalog(baseDirectory, "*.exe"));
if (Directory.Exists(extensionPath))
catalog.Catalogs.Add(new DirectoryCatalog(extensionPath));
return catalog;
}
}
所以你可以看到,根据你是在设计时还是运行时,加载的内容是不同的。我非常喜欢的一点是,如果你对它的工作方式不满意,Marlon让你能够将你自己的IComposer
实现替换到LocatorBootstrapper
中,这让你能够控制设计时/运行时目录是如何创建的。
但这仍然没有回答MefedMVVM如何知道将哪些服务(设计时或运行时)注入到ViewModels中。那么它是如何工作的呢?那是由一个特殊的目录MEFedMVVMCatalog
完成的。
MEFedMVVMCatalog
设计时或运行时exports的检索是由MEFedMVVMCatalog
完成的,它的工作原理如下:
/// <summary>
/// Custome MEF Catalog to return services that are marked as Runtime when you are at
/// runtime and design time services when you are at design time
/// </summary>
public class MEFedMVVMCatalog : ComposablePartCatalog
{
private readonly ComposablePartCatalog _inner;
private readonly IQueryable<ComposablePartDefinition> _partsQuery;
public MEFedMVVMCatalog(ComposablePartCatalog inner, bool designTime)
{
_inner = inner;
if (designTime)
_partsQuery = inner.Parts.Where(p => p.ExportDefinitions.Any(
ed => !ed.Metadata.ContainsKey("IsDesignTimeService") ||
ed.Metadata.ContainsKey("IsDesignTimeService") &&
(ed.Metadata["IsDesignTimeService"].Equals(ServiceType.DesignTime) ||
ed.Metadata["IsDesignTimeService"].Equals(ServiceType.Both))));
else
_partsQuery = inner.Parts.Where(p => p.ExportDefinitions.Any(
ed => !ed.Metadata.ContainsKey("IsDesignTimeService") ||
ed.Metadata.ContainsKey("IsDesignTimeService") &&
(ed.Metadata["IsDesignTimeService"].Equals(ServiceType.Runtime) ||
ed.Metadata["IsDesignTimeService"].Equals(ServiceType.Both))));
}
public override IQueryable<ComposablePartDefinition> Parts
{
get
{
return _partsQuery;
}
}
public static MEFedMVVMCatalog CreateCatalog(ComposablePartCatalog inner)
{
return new MEFedMVVMCatalog(inner, Designer.IsInDesignMode);
}
}
如果我们回到MefedMVVM的LocatorBootstrapper
再看一遍,
var catalog = composer.InitializeContainer();
MEFedMVVMExportProvider provider =
new MEFedMVVMExportProvider(MEFedMVVMCatalog.CreateCatalog(catalog));
CompositionContainer container = new CompositionContainer(provider);
provider.SourceProvider = container;
return container;
我们可以看到这段代码对添加到CompositionContainer
的内容有直接影响,因此当CompositionContainer
被查询时,我们知道我们获得的是设计时Exports、运行时Exports或两者兼有。
这就是MefedMVVM如何解析设计时/运行时或共享服务。
视图上下文
我还有最后一个故事要讲。在MefedMVVM开发过程中,Marlon、Glenn Block(Microsoft MEF项目经理)和我一直在就如何处理需要链接到视图的ViewModels进行一些长时间的讨论。我们都在电子邮件上花费了大量时间,并提出了许多想法,然后格伦最终透露了秘密,告诉Marlon和我说,我们可以使用一个叫做ExportProvider
的东西来实现我们所寻求的。
现在回到我在本文开头包含的MEF概述图
你可以看到CustomExportProvider
是CompositionContainer
可以使用的一个东西。关于创建自定义ExportProviders的信息并不多,Glenn有一篇不错的博客文章,他谈到了这个。
MEF中的Parts携带exports和imports。在组合过程中,容器组合Parts并满足Imports。为了做到这一点,它会查询一系列的export provider,如下图所示。
如果你看一下ExportProvider API,你会看到以下内容:
乍一看,你可能在想,哇,这看起来一点都不简单。这些方法中的大多数都是指定一组要检索的exports、返回它们的格式以及返回的是单个项目还是集合的不同方式。GetExport
/ GetExports
方法返回延迟实例化的对象,类型为Export
。GetExportedObject
/ GetExportedObjects
方法返回实际创建Exports的实例。
幸运的是,大约95%的方法都是围绕一个核心方法进行的语法糖,这是编写自定义ExportProvider时唯一需要实现的方法。
该方法接受一个ImportDefinition
并返回一个Exports集合。
ImportDefinition
你可以把ImportDefinition
看作类似于SQL的Where
子句。它指定了返回哪些Exports的过滤器。ImportDefinition
有两个主要组成部分。约束是一个Expression<Func<ExportDefinition, bool>>
,表示export过滤器。cardinality
是一个枚举,指定exports的基数,它可以是ZeroOrOne
(最多一个,但允许零个)、ExactlyOne
或ZeroOrMore
(0到N的集合)。我们暂时搁置讨论其他参数。
这些定义来自几个地方。Parts携带imports定义;例如,当你用一个或多个Import属性修饰一个Part时,它将为每个Import创建ImportDefinition
,这些ImportDefinition
会被目录拾取。ExportProvider
有几个公共方法接受定义作为参数。由于容器是一个ExportProvider
,这意味着定义可以直接通过其方法传递。最后,如果调用了ExportProvider
上不接受ImportDefinition
的任何重载的GetExport
(s)/GetExportObject
(s)方法,内部就会创建一个ImportDefinition
。
例如,下面的代码片段演示了创建一个匹配所有使用“Service”作为后缀的exports的定义。
这是一个非常简单的约束,但你可以让你的想象力尽情发挥,看看通过表达式你能做什么。
所有斜体文本摘自Glenn Block的博客文章:http://blogs.msdn.com/b/gblock/archive/2008/12/25/using-exportprovider-to-customize-container-behavior-part-i.aspx,发布日期2010年6月13日。
现在你可能会问,这跟创建ViewModels有什么关系呢?嗯,回想一下,我开始这个部分时说,Marlon、Glenn Block(Microsoft MEF项目经理)和我都在寻找一种方法来创建一个可以作为常规Export使用MEF的视图感知服务,但这次,视图将作为其创建的一部分被注入。要做到这一点,你需要创建一个自定义的ExportProvider
并以某种方式将其与你的CompositionContainer
关联起来。在MefedMVVM的情况下,这个链接是通过使用MEFedMVVMExportProvider
来实现的,它接收MEFedMVVMCatalog
,我们在上一小节中已经讨论过。
回想一下这几行代码:
var catalog = composer.InitializeContainer();
MEFedMVVMExportProvider provider =
new MEFedMVVMExportProvider(MEFedMVVMCatalog.CreateCatalog(catalog));
CompositionContainer container = new CompositionContainer(provider);
provider.SourceProvider = container;
return container;
看到了吗?MEFedMVVMExportProvider
。让我们仔细看看,好吗?MEFedMVVMExportProvider
类的完整代码如下所示。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using MEFedMVVM.Services.Contracts;
namespace MEFedMVVM.ViewModelLocator
{
public class MEFedMVVMExportProvider : ExportProvider, IDisposable
{
private CatalogExportProvider _exportProvider;
public MEFedMVVMExportProvider(ComposablePartCatalog catalog)
{
_exportProvider = new CatalogExportProvider(catalog);
//support recomposition
_exportProvider.ExportsChanged += (s, e) => OnExportsChanged(e);
_exportProvider.ExportsChanging += (s, e) => OnExportsChanging(e);
}
public ExportProvider SourceProvider
{
get
{
return _exportProvider.SourceProvider;
}
set
{
_exportProvider.SourceProvider = value;
}
}
protected override System.Collections.Generic.IEnumerable<Export> GetExportsCore(
ImportDefinition definition, AtomicComposition atomicComposition)
{
var exports = _exportProvider.GetExports(definition, atomicComposition);
return exports.Select(export =>
new Export(export.Definition, () => GetValue(export)));
}
private object GetValue(Export innerExport)
{
var value = innerExport.Value;
var context = value as IContextAware;
if (context != null)
{
context.InjectContext(_context);
}
return value;
}
private object _context;
public void SetContextToInject(object context)
{
_context = context;
}
#region IDisposable Members
public void Dispose()
{
_exportProvider.Dispose();
}
#endregion
}
}
特别引起兴趣的区域是SetContextToInject(object context)
方法以及GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
和GetValue(Export innerExport)
方法。Glenn的博客解释了GetExportsCore(..)
方法,但SetContextToInject(object context)
呢?它为我们做了什么,它是如何工作的?
好的,这是发生的情况。在MefedMVVMResolver.GetViewModelByContract(string vmContractName, object contextToInject)
(我们之前讨论过)中,有一段代码,我们调用了一个名为SetContextToExportProvider(object contextToInject)
的方法,该方法用于通过SetContextToInject(object context)
方法将MEFedMVVMExportProvider
注入,并传入当前视图,然后再尝试从CompositionContainer
创建ViewModel。
MEFedMVVMResolver代码摘录
SetContextToExportProvider(contextToInject);
var vmExports = Container.GetExports(definition);
SetContextToExportProvider(null);
...
...
internal void SetContextToExportProvider(object contextToInject)
{
if (Container.Providers != null && Container.Providers.Count >= 1)
{
//try to find the MEFedMVVMExportProvider
foreach (var item in Container.Providers)
{
var mefedProvider = item as MEFedMVVMExportProvider;
if (mefedProvider != null)
mefedProvider.SetContextToInject(contextToInject);
}
}
}
现在我们在MEFedMVVMExportProvider
中拥有了一些上下文(实际的视图),因此当我们尝试通过CompositionContainer
获取Export时,我们将把上下文(刚刚添加到MEFedMVVMExportProvider
中的视图)注入到服务中,然后再创建用于满足正在创建的ViewModel的Import的服务。这只对实现IContextAware
的Export进行。
再看看MEFedMVVMExportProvider
的这一部分。现在可能更清楚了。基本上,它归结为这一点:在创建ViewModel Export之前,一个View被注入到MEFedMVVMExportProvider
中。然后,对于任何实现IContextAware
的ViewModel服务,可用的上下文对象(View)都会在创建服务Export以满足ViewModel创建的Import之前注入到服务中。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using MEFedMVVM.Services.Contracts;
namespace MEFedMVVM.ViewModelLocator
{
public class MEFedMVVMExportProvider : ExportProvider, IDisposable
{
.......
.......
.......
.......
protected override System.Collections.Generic.IEnumerable<Export> GetExportsCore(
ImportDefinition definition, AtomicComposition atomicComposition)
{
var exports = _exportProvider.GetExports(definition, atomicComposition);
return exports.Select(export =>
new Export(export.Definition, () => GetValue(export)));
}
private object GetValue(Export innerExport)
{
var value = innerExport.Value;
var context = value as IContextAware;
if (context != null)
{
context.InjectContext(_context);
}
return value;
}
private object _context;
public void SetContextToInject(object context)
{
_context = context;
}
.......
.......
.......
.......
}
}
暂时就到这里
总之,现在就到这里。希望你喜欢,但还会有更多,更多……但现在,我将休假两周,这是我应得的,我计划在这期间把自己喝到昏迷,吃掉三群水牛。当我精力充沛地回来时,我会写完剩下的文章,但如果你觉得实在等不及了,想探索Cinch V2的代码库,请随意。我应该再次提一下,如果你有任何关于MEF的深入问题,你应该直接咨询Marlon Grech,可以通过他的博客C# Disciples,或者使用MefedMVVM CodePlex站点。任何其他Cinch V2的问题将在下一篇Cinch V2文章中得到解答。