In this post, I’ll give a tutorial on how to apply dependency injection in DevExpress WPF application. This post assumes that you already know about dependency injection, WPF application, and also MVVM. DevExpress MVVM is one of the freely available MVVM frameworks we can use to develop a desktop application using WPF.
Background
A well-designed application can use dependency injection package to resolve object dependencies between modules. If one object depends on other objects, the required objects are injected to requiring object via some sort of ways. So, instead of explicitly creating objects, the required objects are created, injected, and managed by dependency injection package (third-party package) and then, ‘injected’ to the requiring object. The lifetime of the required objects is also managed by this package. Dependency Injection package is also called an IoC Container. IoC is an abbreviation of Inversion of Control.
Benefit
The main benefit of using dependency injection is loose-coupling between modules. By loose-coupling, we can have modules developed separately by different developers. It also implies that testing can be done easier because we can inject mock-up objects to the module being tested, replacing the real objects. Loose-coupling also implies that we can change or replace the implementation of required objects with other ones while maintaining minimal impact or changes on the application.
Another benefit is the automatic life-cycle management of object created by IoC Container. We don’t have to know when to create and destroy required objects. The IoC Container manages the creation, scopes, and also, the destruction of objects.
Dependencies in MVVM
In an MVVM application framework such as DevExpress-MVVM, the module dependency pattern can be seen from the following figure:
From the figure above, we can see that the views depend on view models, then view models depend on services. Dependency injection package can be used to resolve view models required by views. It can also resolve services objects required by view models.
Code Sample
To show how dependency injection works in DevExpress-MVVM WPF application, I’ll create a demo project here. To create this demo project, I use Visual Studio 2017, but you can use Visual Studio 2015 or earlier edition since WPF is a platform that already existed since .NET framework 3.5.
I’ll make a simple scenario here. In this scenario, I have one simple data entry view called: CustomerView. This view will access view model called CustomerViewModel. The view model will call CustomerRepository that acts as a service to persist data to a storage. To make this example as simple as possible, I will just make a dummy repository that does not really store data. The main emphasis of this sample is the implementation of Ioc Container to apply dependency injection.
DevExpress MVVM framework can be used freely as a separate package downloadable from NuGet package gallery. The IoC Container that I’ll use in this sample is UnityContainer, also can be freely downloaded from NuGet package gallery.
Create a WPF Project in Visual Studio
Open Visual Studio, and then create a WPF desktop project by choosing an existing template from template list. I name my project WPFDependencyInjection. This name is automatically used by the containing solution.
Download Required Packages
To download required packages from NuGet package gallery, make sure we are connected to the internet.
Inside the Visual Studio project, we created earlier, open Package Manager Console, and type the following commands to download required packages,
- Install-Package DevExpressMvvm
- Install-Package Unity
Create CustomerView View
In Visual Studio project, create a new item of type User Control (WPF) and name it CustomerView.xaml. Then, in XAML markup of CustomerView, we’ll create a label, a text box, and a button. The view accepts a text box input that binds to the Name property of the view model. It also provides a button for the user to interact with the view for submitting data. This button is bound to SaveCommand of the view model. We also use DevExpress MVVM MessageBoxService to display the message. The final markup of CustomerView.xaml can be seen from the following code
- <UserControl x:Class="WpfDependencyInjection.CustomerView"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
- xmlns:local="clr-namespace:WpfDependencyInjection"
- mc:Ignorable="d"
- d:DesignHeight="300" d:DesignWidth="300">
- <dxmvvm:Interaction.Behaviors>
- <dxmvvm:MessageBoxService />
- </dxmvvm:Interaction.Behaviors>
- <StackPanel Orientation="Vertical" Margin="10">
- <TextBlock>This is customer view.</TextBlock>
- <TextBlock>Customer name: <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Width="200" /></TextBlock>
- <Button Content="Save" Command="{Binding SaveCommand}" Width="50" />
- </StackPanel>
- </UserControl>
We have set up the view, but we haven’t set the data context for this view yet. We will do it later.
Create CustomerViewModel View Model
In Visual Studio project, create a new class called CustomerViewModel, make it public, and make it a descendant of DevExpress.Mvvm.ViewModelBase. This view model has one single public bindable property called Name, and a command called SaveCommand. The source code of CustomerViewModel can be seen from the following code
- public class CustomerViewModel : ViewModelBase
- {
- public string Name
- {
- get => GetProperty(() => Name);
- set => SetProperty(() => Name, value);
- }
-
- public ICommand SaveCommand { get; private set; }
- public IMessageBoxService MessageBoxService => GetService<IMessageBoxService>();
-
- public CustomerViewModel()
- {
- SaveCommand = new DelegateCommand(Save, () => !string.IsNullOrWhiteSpace(Name), true);
- }
-
- private void Save()
- {
- }
- }
Now the Save command is doing nothing. We will add the functionality later after we add the dependency injection mechanism to this view model.
Create CustomerRepository
CustomerRepository acts as the persistence service that view models depend on. In a real-world scenario, the repository performs CRUD agent against the data store. But in this sample, we’ll just create a simple dummy repository. We also create an ICustomerRepository interface to be used by IoC container as a contract to resolve CustomerRepository instance. So, the view models do not directly depend on the CustomerRepository class, but it depends on its interface ICustomerRepository.
ICustomerRepository only provides one Save method that accepts an arbitrary object and returns a boolean value. See the following code for ICustomerRepository,
- public interface ICustomerRepository
- {
- bool Save(object entity);
- }
And here is the implementation of ICustomerRepository. The Save method simply returns true value,
- public class CustomerRepository : ICustomerRepository
- {
- public bool Save(object entity)
- {
- return true;
- }
- }
Edit the MainWindow
The MainWindow only displays the CustomerView directly. Here is the markup for MainWindow.xaml,
- <Window x:Class="WpfDependencyInjection.MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:local="clr-namespace:WpfDependencyInjection"
- mc:Ignorable="d"
- Title="MainWindow" Height="350" Width="525">
- <Grid>
- <local:CustomerView />
- </Grid>
- </Window>
Try It, Debugging
Before we add the IoC container to apply dependency injection, try the project by debugging it. This step makes sure there is no compile-time and run-time error so far. The following simple form should be displayed while debugging this project:
Add IoC Container on MainWindow
An IoC container needs to be created at some point in application lifecycle. Usually, it is created inside OnStartup method of the application object. But in this case, for simplicity, we create the UnityContainer inside the code-behind of MainWindow object,
- public MainWindow()
- {
- InitializeComponent();
-
- IUnityContainer container = new UnityContainer();
- container.RegisterType<ICustomerRepository, CustomerRepository>();
-
- var customerView = container.Resolve<CustomerView>();
- content.Children.Add(customerView);
- }
The first thing the IoC container does is mapping ICustomerRepository interface to CustomerRepository class. Whenever an application requires an object of type ICustomerRepository, it will create an instance of a CustomerRepository object. CustomerView is also created dynamically using IoC container by calling its Resolve method. Then, the view will be injected into the content area. The main window XAML mark up now changes to the following code.
- <Window x:Class="WpfDependencyInjection.MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:local="clr-namespace:WpfDependencyInjection"
- mc:Ignorable="d"
- Title="MainWindow" Height="350" Width="525">
- <Grid x:Name="content">
- </Grid>
- </Window>
The way we fill the view inside the content area in main window is simplified. In real world application, we should use view-injection services like Prism to do the task.
Inject CustomerRepository to View Model
Change the view model we created earlier by injecting CustomerRepository via constructor injection. Then, we can call the repository’s Save method inside the Save command handler. The following code shows the updated CustomerViewModel,
- public class CustomerViewModel : ViewModelBase
- {
- public string Name
- {
- get => GetProperty(() => Name);
- set => SetProperty(() => Name, value);
- }
-
- public ICommand SaveCommand { get; private set; }
- public IMessageBoxService MessageBoxService => GetService<IMessageBoxService>();
-
- private ICustomerRepository customerRepository;
- public CustomerViewModel(ICustomerRepository customerRepository)
- {
- this.customerRepository = customerRepository;
- SaveCommand = new DelegateCommand(Save, () => !string.IsNullOrWhiteSpace(Name), true);
- }
-
- private void Save()
- {
- if (customerRepository.Save(new { Name = this.Name }))
- MessageBoxService.ShowMessage("Success!");
- }
-
- }
Test It, Debugging
Now test the application by debugging it. When the view loaded, Save button should be disabled because the Name textbox is empty. After we fill the Name text box with arbitrary text, the Save button should be enabled. When we click the Save button, a success message should be displayed. The following figure shows the application after displaying the success message.
Source Code
Source code for the sample can be downloaded from GitHub.
Conclusion
In this post, I have shown an example of how to use dependency injection or IoC container within WPF Desktop application that uses DevExpress MVVM framework.