MVVM Services
- 9 minutes to read
Consider trivial task like showing a notification (for example, a message box) from a ViewModel. As a visual element, any message box is in fact, part of a View. If you show your message box directly from a ViewModel (define a command that calls the MessageBox.Show() method), this simple code will break the main MVVM concept - ViewModels must not refer to Views - and make it impossible to write unit-tests for your ViewModel. To get around this difficulty, the DevExpress MVVM Framework implements Services.
A service is a kind of IOC pattern that removes any references between a ViewModel and View layers. In code, a service is an interface used within the ViewModel code without any assumption of “when” and “how” this interface is implemented.
You can implement your own custom services as well as use DevExpress Services. Whatever service you are using, the common workflow remains the same:
- define a service in code (skipped if you’re using the service already implemented by DevExpress);
- register it in a specific View;
- retrieve the service in a ViewModel and use its methods.
DevExpress Services
Note
MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.
Group: API Code Examples
Module: Services
Example: All examples except for the ‘Custom Services’
24.2 Demo Center: Launch the demo
The DevExpress MVVM Framework already has ready-to-use services for most common tasks - displaying messages, flyouts, dialogs, adding Application UI Manager documents etc. For instance, the following ViewModel code retrieves the XtraMessageBoxService by defining a property of the IMessageBoxService type.
//ViewModel
public class MyViewModel {
protected IMessageBoxService MessageBoxService {
get { return this.GetService<IMessageBoxService>(); }
}
}
Important
The GetService method is not thread safe and should not be invoked from a background thread.
For POCO ViewModels you can use the following fail-safe syntax, that will automatically use the this.GetService method or raise an exception if something goes wrong.
//POCO ViewModel
protected virtual IMessageBoxService MessageBoxService {
get { throw new System.NotImplementedException(); }
}
After the service is retrieved, you can use its methods in your ViewModel:
Lastly, register your service within the View. Services are registered in either a local container for use within the individual View (local services), or in a global static (singleton) service container that allows you to use registered services across the entire application (global services).
//Global service
DevExpress.Mvvm.ServiceContainer.Default.RegisterService(new SomeService());
//Local service
serviceContainer.RegisterService(new SomeFilterService(ModuleType.MyCustomFilter));
Services can also be registered in service containers at runtime, when a ViewModel is created.
Finally, you can override the parent’s service implementation at any level of the ViewModel’s hierarchy by providing a custom service implementation at this level.
With the MvvmContext component you do not need to remember this underlying service container mechanic. The component’s API provides easy-to-use methods to register services on both global and local levels.
//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());
Many ready-to-use services are already registered within the global static container, so you do not even need to register them manually. Remove the RegisterMessageBoxService method call within the MessageBox Service demo and you will notice that the service is still working.
You can re-define these service registrations, if needed. To do so, use the corresponding static Register… methods of the MVVMContext class. For example, ViewModels of both XtraMessageBox Service and FlyoutMessageBox Service examples are same as the first example’s ViewModel. All three ViewModels retrieve a service that implements the IMessageBoxService. However, using different static Register… methods forces different services to be used.
The same approach allows examples from the Dialog Services group to display different dialogs, although the ViewModel code is identical.
protected IDialogService DialogService {
get { return this.GetService<IDialogService>(); }
}
Different dialogs are called due to the View code that registers different services.
//XtraDialog service
MVVMContext.RegisterXtraDialogService();
//FlyoutDialog service
MVVMContext.RegisterFlyoutDialogService();
Custom Services
Note
MVVM Best Practices Demo The text below has a related example in the DevExpress ‘MVVM Best Practices’ demo.
Group: API Code Examples
Module: Commanding
Example: Custom Services
24.2 Demo Center: Launch the demo
For custom services, you first need to implement this service somewhere in a separate class. For instance, the application has the custom interface IMyNotificationService with the Notify method.
Then, a custom service CustomService1 that implements this interface will look like the following.
//Service1.cs
class CustomService1 : IMyNotificationService {
void IMyNotificationService.Notify(string message) {
System.Windows.Forms.MessageBox.Show(message, "Service1");
}
}
As a variation, create another service that implements the same interface, but uses the different method overload.
//Service2.cs
class CustomService2 : IMyNotificationService {
void IMyNotificationService.Notify(string message) {
System.Windows.Forms.MessageBox.Show(message, "Service2");
}
}
Properties that retrieve your custom services in the ViewModel code will look like the following.
//ViewModel
public virtual IMyNotificationService Service {
get { throw new NotImplementedException(); }
}
public virtual IMyNotificationService AnotherService {
get { throw new NotImplementedException(); }
}
The DoSomething
method that can be bound to a UI element (for example, a button). It will display two messages with the same text.
//ViewModel
public void DoSomething() {
Service.Notify("Hello");
AnotherService.Notify("Hello");
}
Finally, register your custom services in a View. Since these are your own custom services, no pre-defined static MVVMContext methods to register these services exist. Instead, call the local MvvmContext instance’s RegisterService method.
//View
mvvmContext1.RegisterService(new CustomService1());
mvvmContext1.RegisterService(new CustomService2());
You have passed all the necessary steps: implemented your custom services, used the ViewModel to retrieve them, used their methods and registered them within your View. However, if you try your application at runtime, both message boxes will have the ‘Service2’ caption. This means both Notify methods called are methods of the CustomService2 class.
Tip
Once registered, services take a specific place in a hierarchical tree. Whenever the framework needs a service, it starts seeking from the bottom of the tree, moving up until an appropriate service is found. It was mentioned earlier that many ready-to-use services are already registered in the static container. These services sit on the very top of the hierarchy and are used if the framework has not found any custom services on lower levels. If neither default service exists, an exception occurs. In this example, both custom services are registered on the same hierarchy level. Since both services implement the same IMyNotificationService service, they are both considered as appropriate services when the Notify method of either Service or AnotherService objects is called. But the CustomService2 was the last to be registered, so it sits closer to the hierarchy bottom and is always ‘found’ first by the framework. You can trick this mechanism and register the CustomService2 using the RegisterDefaultService method instead. This will register your CustomService2 in the static container at the top of the hierarchy and make the CustomSerive1 the lowermost service. After that, the framework will always pick the CustomService1.
To avoid this issue, you can define service keys. A key is a string identifier that marks specific services. For POCO ViewModels you can set a service key as a parameter of the [ServiceProperty] attribute.
[ServiceProperty(Key="Service1")]
public virtual IMyNotificationService Service {
get { throw new NotImplementedException(); }
}
[ServiceProperty(Key = "Service2")]
public virtual IMyNotificationService AnotherService {
get { throw new NotImplementedException(); }
}
For non-POCO ViewModels, a service key can be set as a parameter of the GetService extension method.
public IServiceInterface MyService {
get { return this.GetService<IServiceInterface >("MyServiceKey"); }
}
Now, you have to register your custom services with these unique keys. All Register methods have corresponding overloads to do that.
mvvmContext1.RegisterService("Service1", new CustomService1());
mvvmContext1.RegisterService("Service2", new CustomService2());
As a result, when you call the Notify methods, the framework will not be confused about which IMyNotificationService service implementation should be used. Instead, it will take the exact service marked with your custom key. For instance, the AnotherService property will seek for the service marked with the ‘Service2’ key and will find the registered CustomService2 service. The same with the Service property, which will use the CustomService1 service because it was marked with the ‘Service1’ key.
If you test the application now, you will see that both message boxes now display different captions, since they are shown by methods of different services.