Skip to main content

Getting Started

  • 5 minutes to read

A Service is a special Behavior that implements an interface. Although Services are defined in Xaml, the Service interfaces can be accessed from the View Model layer.

Implement Services in Your Application

Follow the steps below to use Services.

  • Set the View’s DataContext to your View Model.
  • Define a service in XAML.
  • Get access to the service’s interface from your View Model via the GetService<T> method.

Example

Assume that you need to show a message box from a View Model. The easiest way to accomplish this task is to use the MessageBox.Show method directly from the View Model. However, this approach breaks the main MVVM rule: the View Model layer should not refer to the View layer. Thus, this way makes it impossible to write unit tests for this View Model, because there is no one who can click the MessageBox button during a unit test. For solving such tasks in MVVM, use the Services mechanism.

Let’s discuss how to solve the posed task with Services. We have the following View Model…

public class DocumentViewModel : ViewModelBase {
    public ICommand CloseDocumentCommand { get; private set; }
    public DocumentViewModel() {
        CloseDocumentCommand = new DelegateCommand(CloseDocument);
    }
    void CloseDocument() {
        MessageBoxResult canCloseDocument;
        //canCloseDocument = 
            //    MessageBox.Show("Want to save your changes?", " Document", MessageBoxButton.YesNoCancel);
        if(canCloseDocument == MessageBoxResult.Yes) {
            //...
        }
    }
}

… and the following View:

<UserControl x:Class="Example.View.DocumentView"
    xmlns:ViewModel="clr-namespace:Example.ViewModel" ...>
    <UserControl.DataContext>
        <ViewModel:DocumentViewModel/>
    </UserControl.DataContext>
    ...
        <Button Content="Close Document" Command="{Binding CloseDocumentCommand}" .../>
    ...
</UserControl>

The DevExpress.Xpf.Mvvm library provides the IMessageBoxService interface. Implementation of this interface is contained in the DevExpress.Xpf.Core library – the DXMessageBoxService class. To add this service to our View (DocumentView), add it to the Interaction.Behaviors collection as follows.

<UserControl x:Class="Example.View.DocumentView"
    xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
    xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
    xmlns:ViewModel="clr-namespace:Example.ViewModel" ...>
    <UserControl.DataContext>
        <ViewModel:DocumentViewModel/>
    </UserControl.DataContext>
    <dxmvvm:Interaction.Behaviors>
        <dx:DXMessageBoxService/>
    </dxmvvm:Interaction.Behaviors>
    ...
        <Button Content="Close Document" Command="{Binding CloseDocumentCommand}" .../>
    ...
</UserControl>

Services are automatically injected to View Models, so they are available from there via an interface that is provided by a certain service.

As you may have noticed, our View Model (DocumentViewModel) is inherited from the ViewModelBase class. So, the DocumentViewModel supports the GetService<T> method that returns an interface used to access the DXMessageBoxService.

public class DocumentViewModel : ViewModelBase {
    public ICommand CloseDocumentCommand { get; private set; }
    public IMessageBoxService MessageBoxService { get { return GetService<IMessageBoxService>(); } }
    ...
    void CloseDocument() {
        MessageBoxResult canCloseDocument = MessageBoxService.Show(
            messageBoxText: "Want to save your changes?", 
            caption: "Document", 
            button: MessageBoxButton.YesNoCancel);
        if(canCloseDocument == MessageBoxResult.Yes) {
            //...
        }
    }
}

Create Unit Tests

The use of Services makes it easy to create unit-tests for your View Models. Let’s write a test for the above-mentioned DocumentViewModel (Moq Library is used).

[TestFixture]
public class DocumentViewModelTests {
    [Test]
    public void Test() {
        bool serviceIsCalled = false;
        var viewModel = new DocumentViewModel();
        var service = new Mock<IMessageBoxService>(MockBehavior.Strict);
        service.
           Setup(foo => foo.Show(
               "Want to save your changes?", "Document", MessageBoxButton.YesNoCancel, 
                MessageBoxImage.None, MessageBoxResult.None)).
           Returns((string text, string caption, MessageBoxButton button,
                MessageBoxImage image, MessageBoxResult none) => {
               serviceIsCalled = true;
               return MessageBoxResult.OK;
           });
        ((ISupportServices)viewModel).ServiceContainer.RegisterService(service.Object);
        viewModel.CloseDocumentCommand.Execute(null);
        Assert.IsTrue(serviceIsCalled);
    }
}

Note

Refer to the following topic for a tutorial and a downloadable example: DXMessageBoxService.

Services become available only after loading the Views to which services are attached. To obtain services to perform preliminary actions, handle the View’s Loaded event (you can use EventToCommand) and access the required service there. Below is a code snippet that shows how to perform preliminary navigation with the FrameNavigationService.

<UserControl x:Class="DXSample.View.MainView" 
    ... 
    DataContext="{dxmvvm:ViewModelSource Type={x:Type ViewModel:MainViewModel}}">
    <Grid>
        <dxwui:NavigationFrame AnimationType="SlideHorizontal">
            <dxmvvm:Interaction.Behaviors>
                <dxmvvm:EventToCommand EventName="Loaded" Command="{Binding OnViewLoadedCommand}" />
                <dxwuin:FrameNavigationService />
            </dxmvvm:Interaction.Behaviors>
        </dxwui:NavigationFrame>
    </Grid>
</UserControl>
public class MainViewModel {
    private INavigationService NavigationService { get { return this.GetService<INavigationService>(); } }

    public MainViewModel() {  }

    public void OnViewLoaded() {
        NavigationService.Navigate("HomeView", null, this);
    }
}

Use Services with Dependency Injection

The recommended technique to use DevExpress services with Dependency Injection varies depending on whether the service has an associated visual element.

  • If the service is attached to a specific visual element, add the following custom AttachServiceBehavior to register it:

    public class AttachServiceBehavior : Behavior<DependencyObject> {
        public static readonly DependencyProperty AtachableServiceProperty =
            DependencyProperty.Register(nameof(AtachableService), typeof(ServiceBase),
            typeof(AttachServiceBehavior), new PropertyMetadata(null, OnAtachableServiceChanged));
    
        static void OnAtachableServiceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            (e.OldValue as ServiceBase)?.Detach();
            ((AttachServiceBehavior)d).AttachService();
        }
        public ServiceBase AtachableService {
            get => (ServiceBase)GetValue(AtachableServiceProperty);
            set => SetValue(AtachableServiceProperty, value);
        }
    
        protected override void OnAttached() {
            base.OnAttached();
            AttachService();
        }
        protected override void OnDetaching() {
            base.OnDetaching();
            AtachableService?.Detach();
        }
    
        void AttachService() {
            if(AtachableService == null || AssociatedObject == null)
                return;
            if(AtachableService.IsAttached)
                AtachableService.Detach();
            AtachableService.Attach(AssociatedObject);
        }
    }
    

    Add a public property that corresponds to the service to the View Model:

    public class MainViewModel {
        public INavigationService NavigationService { get; }
    
        public MainViewModel(INavigationService navigationService) =>
            NavigationService = navigationService;
    }
    

    Use the AttachServiceBehavior to attach the service to a visual element:

    <dxwui:NavigationFrame>
        <dxmvvm:Interaction.Behaviors>
            <common:AttachServiceBehavior Service="{Binding NavigationService}"/>
        </dxmvvm:Interaction.Behaviors>
    </dxwui:NavigationFrame>
    

    View Example

  • If the service does not need to be attached to a specific visual element (such as Message Box Services), you can use the following technique instead:

    1. Register the service in the Dependency Injection container:

      container.RegisterSingleton(typeof(IMessageBoxService), typeof(DXMessageBoxService));
      
    2. Specify the corresponding View Model property:

      public class MainViewModel {
          IMessageBoxService messageBoxService;
          public MainViewModel(IMessageBoxService dialogService) {
              this.messageBoxService = messageBoxService;
          }
      }
      

Tip

If you configure the service to work with a specific View, Dependency Injection is not recommended. Use the technique described in the Implement Services in Your Application section.

Register Services at App.xaml

Use the DevExpress.Mvvm.ServiceContainer.Default property to access application services at the View level.

ServiceContainer.Default.GetService<IDispatcherService>();

You can register services that are not associated with a specific control (for example, NotificationService or DispatcherService) at the App.xaml.

<Application.Resources>
    <dx:DXMessageBoxService x:Key="MessageBoxService"/>
</Application.Resources>

You can access services registered at the App.xaml only from the UI thread.