I’ve recently been playing around with the composition root concept as the “next step” beyond dependency injection1. The basic idea is you register, via dependency injection, all the objects in your app. This relates them to each other so they can be created automagically by the DI framework. The composition root object is the one from which everything else can be created. Running the app then becomes a matter of creating an instance of that root object and executing it.
This concept is further extended by Net5’s IHost system, which introduces the concept of a host object which choreographs all the activity related to a particular activity. Registering hosts with the dependency injection system simplifies executing them as as separate activities. In this WPF app there’s only one host, but the command line version of the app2 uses two because it needs to be able to provide two different kinds of functionality, depending upon the command line keys it’s invoked with.
A view model locator is an instance of a composition root used in a WPF app. As part of its ability to create related objects it serves as a single point from which objects needed to configure any UI element can be obtained.
Why View Models?
Before going any further there’s a basic question people new to this way of writing WPF apps may have. Why go through all the trouble to create a view model locator? In fact, why use view models and dependency injection?
I’m not an expert on WPF. But the value of view models — which separates the stuff which defines what the UI looks like from the stuff that holds the actual data and does something with it — lies in simplifying the otherwise repetitive boilerplate code need to make things in the UI automagically update3. The view model layer focuses on how to keep the UI updated as things change while keeping the underlying data layer in sync with changes made in the UI. But it doesn’t do anything with the data, although it may tell the data layer to do something with its contents.
There’s nothing to say that couldn’t be handled all in one place. But the emphasis of each layer — UI, viewmodel and (data) model — are very different. Separating them simplifies writing code in the long run.
Declaring a Composition Root/View Model Locator in a WPF App
In a WPF app the view model locator is generally defined in the app’s master resource dictionary (details omitted for clarity):
<Application x:Class="J4JSoftware.GeoProcessor.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:J4JSoftware.GeoProcessor" Startup="Application_Startup" Exit="Application_Exit"> <Application.Resources> <ResourceDictionary> <local:CompositionRoot x:Key="ViewModelLocator" /> </ResourceDictionary> </Application.Resources> </Application>
The x:Key for the composition root instance can be anything but traditionally it’s ViewModelLocator. As with any WPF app, line 4 is important because otherwise the WPF framework wouldn’t be able to find the definition of CompositionRoot
. It specifies the XAML prefix local is referring to a particular namespace, namely, the app’s namespace.
Modifying the App’s Startup and Exit
If you’re familiar with WPF apps you may have noticed lines 5 and 6 are different than usual. By defining them this way we change how the app initializes itself, giving us access to the IHost framework:
public partial class App : Application { private async void Application_Startup( object sender, StartupEventArgs e ) { var compRoot = TryFindResource( "ViewModelLocator" ) as CompositionRoot; if( compRoot?.Host == null ) throw new NullReferenceException( "Couldn't find ViewModelLocator resource" ); await compRoot.Host.StartAsync(); var mainWindow = compRoot.Host.Services.GetRequiredService<MainWindow>(); mainWindow.Show(); } private async void Application_Exit( object sender, ExitEventArgs e ) { var compRoot = (CompositionRoot) TryFindResource( "ViewModelLocator" ); using( compRoot.Host! ) { await compRoot.Host!.StopAsync(); } } }
This isn’t terribly important in this app. But it would be useful in an app which manages multiple activities, each defined by its own IHost.
If you’re not using dependency injection, you should. I recommend and use Autofac. ↩
which you can read about on the GeoProcessor github page ↩
Personally, I also find the “raw” WPF code pretty counter-intuitive to use. ↩