服務(wù)
考慮一些瑣碎的任務(wù),比如顯示來自ViewModel的通知(例如,消息框)。作為可視元素,任何消息框?qū)嶋H上都是視圖的一部分。因此如果直接從ViewModel中顯示消息框(定義一個(gè)調(diào)用MessageBox.Show()方法的命令),這段簡(jiǎn)單的代碼將破壞主要的MVVM概念——ViewModels不能引用Views——并且使為ViewModel編寫單元測(cè)試變得不可能。為了解決這個(gè)困難,DevExpress MVVM框架實(shí)現(xiàn)了Services。
服務(wù)是一種IOC模式,它刪除ViewModel和View層之間的任何引用。在代碼中,服務(wù)是ViewModel代碼中使用的接口,不需要假設(shè)該接口“何時(shí)”和“如何”實(shí)現(xiàn)。
您可以實(shí)現(xiàn)自己的自定義服務(wù),也可以使用DevExpress服務(wù),不管使用的是什么服務(wù),通用的工作流程都是一樣的:
- 在代碼中定義一個(gè)服務(wù)(如果正在使用DevExpress已經(jīng)實(shí)現(xiàn)的服務(wù),則跳過)。
- 注冊(cè)到一個(gè)特定的視圖中。
- 在ViewModel中檢索服務(wù)并使用其方法。
DevExpress服務(wù)
提示:
下面的文本在DevExpress的MVVM Best Practices Demo 中有一個(gè)相關(guān)的例子。
Group:API代碼示例
Module:服務(wù)
Example:除“自定義服務(wù)”外的所有示例
23.1 Demo Center:?jiǎn)?dòng)演示
DevExpress MVVM框架已經(jīng)為大多數(shù)常見任務(wù)提供了現(xiàn)成的服務(wù)——顯示消息、彈出窗口、對(duì)話框、添加應(yīng)用程序UI管理器文檔等。例如,下面的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方法不是線程安全的,不應(yīng)該從后臺(tái)線程調(diào)用。
對(duì)于POCO ViewModels,您可以使用以下故障安全語法,它將自動(dòng)使用this.GetService方法或在出現(xiàn)錯(cuò)誤時(shí)引發(fā)異常。
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
在服務(wù)被檢索之后,您可以在ViewModel中使用它的方法:
C#:
public void SayHello() { MessageBoxService.Show("Hello!"); }
VB.NET:
Public Sub SayHello() MessageBoxService.Show("Hello!") End Sub
最后,在視圖中注冊(cè)您的服務(wù)。服務(wù)可以注冊(cè)在本地容器中,以便在單個(gè)視圖中使用(本地服務(wù)),也可以注冊(cè)在全局靜態(tài)(單例)服務(wù)容器中,允許您在整個(gè)應(yīng)用程序中使用注冊(cè)的服務(wù)(全局服務(wù))。
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))
服務(wù)也可以在運(yùn)行時(shí)(即創(chuàng)建ViewModel時(shí))注冊(cè)到服務(wù)容器中。
C#:
this.ServiceContainer.RegisterService(new Services.AnotherService());
VB.NET:
this.ServiceContainer.RegisterService(new Services.AnotherService());
最后,您可以通過在ViewModel層次結(jié)構(gòu)的任何級(jí)別提供自定義服務(wù)實(shí)現(xiàn)來覆蓋parent’s service的實(shí)現(xiàn)。
C#:
serviceContainer.RegisterService(new NotImplementedCustomService(ModuleType.MyMainView));
VB.NET:
serviceContainer.RegisterService(new NotImplementedCustomService(ModuleType.MyMainView));
使用MvvmContext組件,您不需要記住這個(gè)底層服務(wù)容器機(jī)制,組件的API提供了易于使用的方法來在全局和本地級(jí)別注冊(cè)服務(wù)。
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())
許多現(xiàn)成的服務(wù)已經(jīng)在全局靜態(tài)容器中注冊(cè)了,因此甚至都不需要手動(dòng)注冊(cè)它們。在MessageBox服務(wù)演示中刪除RegisterMessageBoxService方法調(diào)用,您會(huì)發(fā)現(xiàn)該服務(wù)仍然可以工作。
如果需要,您可以重新定義這些服務(wù)注冊(cè),為此使用MVVMContext類的相應(yīng)靜態(tài)Register…方法。例如,XtraMessageBox Service和FlyoutMessageBox Service示例的ViewModel都與第一個(gè)示例的ViewModel相同,所有三個(gè)viewmodel都檢索實(shí)現(xiàn)IMessageBoxService的服務(wù),然而使用不同的靜態(tài)Register…方法會(huì)強(qiáng)制使用不同的服務(wù)。
同樣的方法允許Dialog Services組中的示例顯示不同的對(duì)話框,盡管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è)了不同的服務(wù),因此調(diào)用了不同的對(duì)話框。
C#:
//XtraDialog service MVVMContext.RegisterXtraDialogService(); //FlyoutDialog service MVVMContext.RegisterFlyoutDialogService();
VB.NET:
//XtraDialog service MVVMContext.RegisterXtraDialogService(); //FlyoutDialog service MVVMContext.RegisterFlyoutDialogService();
自定義服務(wù)
下面的文本在DevExpress的MVVM Best Practices Demo 中有一個(gè)相關(guān)的例子。
Group:API代碼示例
Module: 指揮
Example:自定義服務(wù)
23.1 Demo Center:?jiǎn)?dòng)演示程序
對(duì)于自定義服務(wù),首先需要在一個(gè)單獨(dú)的類中實(shí)現(xiàn)該服務(wù)。例如,應(yīng)用程序具有帶有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
然后,實(shí)現(xiàn)此接口的自定義服務(wù)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
作為一個(gè)變體,創(chuàng)建另一個(gè)實(shí)現(xiàn)相同接口的服務(wù),但使用不同的方法重載。
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代碼中檢索自定義服務(wù)的屬性如下所示。
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方法,它可以綁定到一個(gè)UI元素(例如,一個(gè)按鈕),它將顯示具有相同文本的兩條消息。
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
最后,在視圖中注冊(cè)您的自定義服務(wù)。由于這些是自定義服務(wù),因此不存在用于注冊(cè)這些服務(wù)的預(yù)定義靜態(tài)MVVMContext方法,相反調(diào)用本地MvvmContext實(shí)例的RegisterService方法。
C#:
//View mvvmContext1.RegisterService(new CustomService1()); mvvmContext1.RegisterService(new CustomService2());
VB.NET:
'View mvvmContext1.RegisterService(New CustomService1()) mvvmContext1.RegisterService(New CustomService2())
因此,您已經(jīng)通過了所有必要的步驟:實(shí)現(xiàn)自定義服務(wù)、使用ViewModel檢索它們、使用它們的方法并在視圖中注冊(cè)它們。但是如果在運(yùn)行時(shí)嘗試應(yīng)用程序,則兩個(gè)消息框都將具有' Service2 '標(biāo)題,這意味著調(diào)用的兩個(gè)Notify方法都是CustomService2類的方法。
注冊(cè)后,服務(wù)在分層樹中占據(jù)特定位置。每當(dāng)框架需要服務(wù)時(shí),它就會(huì)開始從樹的底部向上查找,直到找到合適的服務(wù)。前面提到過,很多現(xiàn)成可用的服務(wù)已經(jīng)注冊(cè)在靜態(tài)容器中了,這些服務(wù)位于層次結(jié)構(gòu)的最頂層,如果框架在較低級(jí)別上沒有找到任何自定義服務(wù),則使用這些服務(wù),如果兩個(gè)默認(rèn)服務(wù)都不存在,則會(huì)發(fā)生異常。在此示例中,兩個(gè)自定義服務(wù)都在同一層次結(jié)構(gòu)級(jí)別上注冊(cè),由于這兩個(gè)服務(wù)實(shí)現(xiàn)相同的IMy通知服務(wù)服務(wù),因此當(dāng)任一服務(wù)的Notify方法或者調(diào)用另一個(gè)服務(wù)對(duì)象,但Custom Service2是最后注冊(cè)的,因此它更靠近層次結(jié)構(gòu)底部,并且始終首先被框架“找到”。您可以欺騙此機(jī)制并使用“注冊(cè)默認(rèn)服務(wù)”方法來注冊(cè)自定義服務(wù)2 ,這會(huì)將Custom Service2注冊(cè)到層次結(jié)構(gòu)頂部的靜態(tài)容器中,并使Custom Serive1成為最底層的服務(wù),之后,框架將始終選擇Custom Service1。
要解決這個(gè)問題,可以定義服務(wù)密鑰,密鑰是標(biāo)記特定服務(wù)的字符串標(biāo)識(shí)符。對(duì)于POCO ViewModels,您可以將服務(wù)鍵設(shè)置為[ServiceProperty]屬性的參數(shù)。
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
對(duì)于非poco視圖模型,可以將服務(wù)鍵設(shè)置為GetService擴(kuò)展方法的參數(shù)。
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
現(xiàn)在,您必須使用這些唯一的密鑰注冊(cè)自定義服務(wù)。所有Register方法都有相應(yīng)的重載來執(zhí)行此操作。
C#:
mvvmContext1.RegisterService("Service1", new CustomService1()); mvvmContext1.RegisterService("Service2", new CustomService2());
VB.NET:
mvvmContext1.RegisterService("Service1", New CustomService1()) mvvmContext1.RegisterService("Service2", New CustomService2())
因此,當(dāng)您調(diào)用Notify方法時(shí),框架不會(huì)對(duì)應(yīng)該使用哪個(gè)IMyNotificationService服務(wù)實(shí)現(xiàn)感到困惑,相反它將使用自定義密鑰標(biāo)記的確切服務(wù)。例如,AnotherService屬性將查找標(biāo)有“Service2”鍵的服務(wù),并查找已注冊(cè)的CustomService2服務(wù),Service屬性也是如此,它將使用CustomService1服務(wù),因?yàn)樗粯?biāo)記為' Service1 '鍵。
如果現(xiàn)在測(cè)試應(yīng)用程序,您將看到兩個(gè)消息框現(xiàn)在顯示不同的標(biāo)題,因?yàn)樗鼈兪怯刹煌?wù)的方法顯示的。