服务
考虑一些琐碎的任务,比如显示来自ViewModel的通知(例如,消息框)。作为可视元素,任何消息框实际上都是视图的一部分。因此如果直接从ViewModel中显示消息框(定义一个调用MessageBox.Show()方法的命令),这段简单的代码将破坏主要的MVVM概念——ViewModels不能引用Views——并且使为ViewModel编写单元测试变得不可能。为了解决这个困难,DevExpress MVVM框架实现了Services。
服务是一种IOC模式,它删除ViewModel和View层之间的任何引用。在代码中,服务是ViewModel代码中使用的接口,不需要假设该接口“何时”和“如何”实现。
您可以实现自己的自定义服务,也可以使用DevExpress服务,不管使用的是什么服务,通用的工作流程都是一样的:
- 在代码中定义一个服务(如果正在使用DevExpress已经实现的服务,则跳过)。
- 注册到一个特定的视图中。
- 在ViewModel中检索服务并使用其方法。
DevExpress服务
提示:
下面的文本在DevExpress的MVVM Best Practices Demo 中有一个相关的例子。
Group:API代码示例
Module:服务
Example:除“自定义服务”外的所有示例
23.1 Demo Center:启动演示
DevExpress MVVM框架已经为大多数常见任务提供了现成的服务——显示消息、弹出窗口、对话框、添加文档等。例如,下面的ViewModel代码通过定义IMessageBoxService类型的属性来检索XtraMessageBoxService。
C#:
//ViewModel public class MyViewModel { protected IMessageBoxService MessageBoxService { get { return this.GetService<IMessageBoxService>(); } } }
VB.NET:
'ViewModel Public Class MyViewModel Protected ReadOnly Property MessageBoxService() As IMessageBoxService Get Return Me.GetService(Of IMessageBoxService)() End Get End Property
注意:GetService方法不是线程安全的,不应该从后台线程调用。
对于POCO ViewModels,您可以使用以下故障安全语法,它将自动使用this.GetService方法或在出现错误时引发异常。
C#:
//POCO ViewModel protected virtual IMessageBoxService MessageBoxService { get { throw new System.NotImplementedException(); } }
VB.NET:
//POCO ViewModel Protected Overridable ReadOnly Property MessageBoxService() As IMessageBoxService Get Throw New System.NotImplementedException() End Get End Property
在服务被检索之后,您可以在ViewModel中使用它的方法:
C#:
public void SayHello() { MessageBoxService.Show("Hello!"); }
VB.NET:
Public Sub SayHello() MessageBoxService.Show("Hello!") End Sub
最后,在视图中注册您的服务。服务可以注册在本地容器中,以便在单个视图中使用(本地服务),也可以注册在全局静态(单例)服务容器中,允许您在整个应用程序中使用注册的服务(全局服务)。
C#:
//Global service DevExpress.Mvvm.ServiceContainer.Default.RegisterService(new SomeService()); //Local service serviceContainer.RegisterService(new SomeFilterService(ModuleType.MyCustomFilter));
VB.NET:
'Global service DevExpress.Mvvm.ServiceContainer.Default.RegisterService(New SomeService()) 'Local service serviceContainer.RegisterService(New SomeFilterService(ModuleType.MyCustomFilter))
服务也可以在运行时(即创建ViewModel时)注册到服务容器中。
C#:
this.ServiceContainer.RegisterService(new Services.AnotherService());
VB.NET:
this.ServiceContainer.RegisterService(new Services.AnotherService());
最后,您可以通过在ViewModel层次结构的任何级别提供自定义服务实现来覆盖parent’s service的实现。
C#:
serviceContainer.RegisterService(new NotImplementedCustomService(ModuleType.MyMainView));
VB.NET:
serviceContainer.RegisterService(new NotImplementedCustomService(ModuleType.MyMainView));
使用MvvmContext组件,您不需要记住这个底层服务容器机制,组件的API提供了易于使用的方法来在全局和本地级别注册服务。
C#:
//Static method that registers the global DevExpress XtraDialogService MVVMContext.RegisterXtraDialogService(); //Registers the Service1 service in the default service container (global service) mvvmContext1.RegisterDefaultService(new Service1()); //Registers the local Service1 for use within the current View only mvvmContext1.RegisterService(new Service2());
VB.NET:
'Static method that registers the global DevExpress XtraDialogService MVVMContext.RegisterXtraDialogService() 'Registers the Service1 service in the default service container (global service) mvvmContext1.RegisterDefaultService(New Service1()) 'Registers the local Service1 for use within the current View only mvvmContext1.RegisterService(New Service2())
许多现成的服务已经在全局静态容器中注册了,因此甚至都不需要手动注册它们。在MessageBox服务演示中删除RegisterMessageBoxService方法调用,您会发现该服务仍然可以工作。
如果需要,您可以重新定义这些服务注册,为此使用MVVMContext类的相应静态Register…方法。例如,XtraMessageBox Service和FlyoutMessageBox Service示例的ViewModel都与第一个示例的ViewModel相同,所有三个viewmodel都检索实现IMessageBoxService的服务,然而使用不同的静态Register…方法会强制使用不同的服务。
同样的方法允许Dialog Services组中的示例显示不同的对话框,尽管ViewModel代码是相同的。
C#:
protected IDialogService DialogService { get { return this.GetService<IDialogService>(); } }
VB.NET:
Protected ReadOnly Property DialogService() As IDialogService Get Return Me.GetService(Of IDialogService)() End Get End Property
由于View代码注册了不同的服务,因此调用了不同的对话框。
C#:
//XtraDialog service MVVMContext.RegisterXtraDialogService(); //FlyoutDialog service MVVMContext.RegisterFlyoutDialogService();
VB.NET:
//XtraDialog service MVVMContext.RegisterXtraDialogService(); //FlyoutDialog service MVVMContext.RegisterFlyoutDialogService();
自定义服务
下面的文本在DevExpress的MVVM Best Practices Demo 中有一个相关的例子。
Group:API代码示例
Module: 指挥
Example:自定义服务
23.1 Demo Center:启动演示程序
对于自定义服务,首先需要在一个单独的类中实现该服务。例如,应用程序具有带有Notify方法的自定义接口IMyNotificationService。
C#:
//View public interface IMyNotificationService { void Notify(string message); }
VB.NET:
'View Public Interface IMyNotificationService Sub Notify(ByVal message As String) End Interface
然后,实现此接口的自定义服务CustomService1将如下所示。
C#:
//Service1.cs class CustomService1 : IMyNotificationService { void IMyNotificationService.Notify(string message) { System.Windows.Forms.MessageBox.Show(message, "Service1"); } }
VB.NET:
'Service1.vb Friend Class CustomService1 Implements IMyNotificationService Private Sub IMyNotificationService_Notify(ByVal message As String) Implements IMyNotificationService.Notify System.Windows.Forms.MessageBox.Show(message, "Service1") End Sub End Class
作为一个变体,创建另一个实现相同接口的服务,但使用不同的方法重载。
C#:
//Service2.cs class CustomService2 : IMyNotificationService { void IMyNotificationService.Notify(string message) { System.Windows.Forms.MessageBox.Show(message, "Service2"); } }
VB.NET:
'Service2.vb Friend Class CustomService2 Implements IMyNotificationService Private Sub IMyNotificationService_Notify(ByVal message As String) Implements IMyNotificationService.Notify System.Windows.Forms.MessageBox.Show(message, "Service2") End Sub End Class
在ViewModel代码中检索自定义服务的属性如下所示。
C#:
//ViewModel public virtual IMyNotificationService Service { get { throw new NotImplementedException(); } } public virtual IMyNotificationService AnotherService { get { throw new NotImplementedException(); } }
VB.NET:
'ViewModel Public Overridable ReadOnly Property Service() As IMyNotificationService Get Throw New NotImplementedException() End Get End Property Public Overridable ReadOnly Property AnotherService() As IMyNotificationService Get Throw New NotImplementedException() End Get End Property
这是DoSomething方法,它可以绑定到一个UI元素(例如,一个按钮),它将显示具有相同文本的两条消息。
C#:
//ViewModel public void DoSomething() { Service.Notify("Hello"); AnotherService.Notify("Hello"); }
VB.NET:
'ViewModel Public Sub DoSomething() Service.Notify("Hello") AnotherService.Notify("Hello") End Sub
最后,在视图中注册您的自定义服务。由于这些是自定义服务,因此不存在用于注册这些服务的预定义静态MVVMContext方法,相反调用本地MvvmContext实例的RegisterService方法。
C#:
//View mvvmContext1.RegisterService(new CustomService1()); mvvmContext1.RegisterService(new CustomService2());
VB.NET:
'View mvvmContext1.RegisterService(New CustomService1()) mvvmContext1.RegisterService(New CustomService2())
因此,您已经通过了所有必要的步骤:实现自定义服务、使用ViewModel检索它们、使用它们的方法并在视图中注册它们。但是如果在运行时尝试应用程序,则两个消息框都将具有' Service2 '标题,这意味着调用的两个Notify方法都是CustomService2类的方法。
注册后,服务在分层树中占据特定位置。每当框架需要服务时,它就会开始从树的底部向上查找,直到找到合适的服务。前面提到过,很多现成可用的服务已经注册在静态容器中了,这些服务位于层次结构的最顶层,如果框架在较低级别上没有找到任何自定义服务,则使用这些服务,如果两个默认服务都不存在,则会发生异常。在此示例中,两个自定义服务都在同一层次结构级别上注册,由于这两个服务实现相同的IMy通知服务服务,因此当任一服务的Notify方法或者调用另一个服务对象,但Custom Service2是最后注册的,因此它更靠近层次结构底部,并且始终首先被框架“找到”。您可以欺骗此机制并使用“注册默认服务”方法来注册自定义服务2 ,这会将Custom Service2注册到层次结构顶部的静态容器中,并使Custom Serive1成为最底层的服务,之后,框架将始终选择Custom Service1。
要解决这个问题,可以定义服务密钥,密钥是标记特定服务的字符串标识符。对于POCO ViewModels,您可以将服务键设置为[ServiceProperty]属性的参数。
C#:
[ServiceProperty(Key="Service1")] public virtual IMyNotificationService Service { get { throw new NotImplementedException(); } } [ServiceProperty(Key = "Service2")] public virtual IMyNotificationService AnotherService { get { throw new NotImplementedException(); } }
VB.NET:
<ServiceProperty(Key:="Service1")> Public Overridable ReadOnly Property Service() As IMyNotificationService Get Throw New NotImplementedException() End Get End Property <ServiceProperty(Key := "Service2")> Public Overridable ReadOnly Property AnotherService() As IMyNotificationService Get Throw New NotImplementedException() End Get End Property
对于非poco视图模型,可以将服务键设置为GetService扩展方法的参数。
C#:
public IServiceInterface MyService { get { return this.GetService<IServiceInterface >("MyServiceKey"); } }
VB.NET:
Public ReadOnly Property MyService() As IServiceInterface Get Return Me.GetService(Of IServiceInterface )("MyServiceKey") End Get End Property
现在,您必须使用这些唯一的密钥注册自定义服务。所有Register方法都有相应的重载来执行此操作。
C#:
mvvmContext1.RegisterService("Service1", new CustomService1()); mvvmContext1.RegisterService("Service2", new CustomService2());
VB.NET:
mvvmContext1.RegisterService("Service1", New CustomService1()) mvvmContext1.RegisterService("Service2", New CustomService2())
因此,当您调用Notify方法时,框架不会对应该使用哪个IMyNotificationService服务实现感到困惑,相反它将使用自定义密钥标记的确切服务。例如,AnotherService属性将查找标有“Service2”键的服务,并查找已注册的CustomService2服务,Service属性也是如此,它将使用CustomService1服务,因为它被标记为' Service1 '键。
如果现在测试应用程序,您将看到两个消息框现在显示不同的标题,因为它们是由不同服务的方法显示的。