Practical .NET

Integrating Services into a WPF Composable Application

There are two strategies that you can follow in pulling together the modules that make up your WPF composable application: Central Control and Distributed Control. Here’s how to implement both of them.

In two previous columns, I've showed how to build a composable application in WPF with Prism and Unity. A composable application consists of the main Shell (a WPF application with a XAML Window) and a set of Modules, each containing one part of the application's functionality.

My first column in this series on creating self-assembling, composable WPF applications showed how to create the Shell (main application) and a Module that defined part of the UI. The second column showed how to add in a Module that provided business functionality. But just because the application "knows" about a module that provides the functionality, it doesn't mean the application will use it. The next step is to decide where you're going to apply that business functionality (or "service," in Prism-speak).

Some understanding of the WPF+Prism+Unity processing cycle is required. In the Shell application, you specify a folder as your Module Catalog to hold all the Modules -- class library project -- that make up your application (I used a folder and then had all my other Module projects copy their DLLs to that folder).

When the application starts up, it runs through the Modules in the catalog and runs their initializer class (a class that implements the IModule interface). Code in the initializer registers all the other classes in the project with the application by specifying three things: the interface that the class implements, a class itself, and a name for the relationship between the class and the interface.

In my previous column, I showed the code that registered a class -- MockCustomerFactory -- that returned a list of mock customer entities to use for testing. Then I tied the ICustomerFactory interface to the class under the name MockDefaultNWCustomerFactory.

After registering all the Modules, WPF starts pulling together the application's components (i.e. "composing the application"). All classes from all modules registered with Unity in their initializer class are available through Unity when it comes time for the application to decide which class (or classes) to use.

There are two ways to retrieve registered classes. The first is through the Unity container, which is passed to the initializer classes in the Modules, and which they can pass to the classes they create. The second method is through the ServiceLocator, which is available in the Shell application.

Pulling From the Unity Container
You can use the Unity container's Resolve method, passing the interface you want and the name of the relationship to retrieve a specific class. For instance, to retrieve my mock object implementation of the ICustomerFactory interface, I'd use code like this (where ucont represents the Unity container):
ICustomerFactory cf = ucont.Resolve(
  typeof(ICustomerFactory),"MockNWCustomerFactory");

The Unity container will find the class registered with that interface and name, instantiate it, and return it to you. You can then call the methods defined in the interface and implemented in the class. For my ICustomerFactory interface there's only a single method -- GetCustomer -- which I want to use set the Customers property on the ViewModel class that drives my WPF form. (My WPF form has an ItemsList bound to that property, so as soon as I set the Customers property to a list of Customers, the WPF form will display the customers).

The probem is that getting to the Unity container can be awkward. In my sample application,  I created GetCustomersCommand class that's bound to the Click event of a the "Get Customers" Button on my WPF form -- when the user clicks the Button, it runs an Execute method in my GetCustomersCommand class.

I could pass the Unity container to that Execute method and use it to set the Customers property. To do that, the constructor for my GetCustomersCommand class would have to accept a reference to a Unity container so I could use it in the Execute method to retrieve the class.

In addition, testing my GetCustomersCommand method would be awkward because I'd have to create test cases that passed a configured Unity container to my GetCustomersCommand class. So instead, I'll have the GetCustomersCommand's constructor accept anything that implements the ICustomerFactory method. My Execute method will use that class that's passed into the constructor:

private readonly ICustListVM clvm;
private readonly ICustomerFactory cf;
            
public GetCustomersCommand(ICustListVM CustListVM, ICustomerFactory CustFactory)
{
  clvm = CustListVM;
  cf = CustFactory;
}
public void Execute(object parameter)
{
 clvm.Customers = cf.GetAllCustomers();
}

In this design, the call to the Resolve method to retrieve the CustomerFactory class goes in my ViewModel class. My ViewModel is already being passed the application's Unity container in its constructor, so it saves passing the Unity container one level further on. The code that retrieves the mock implementation of my CustomerFactory and passes it to the GetCustomersCommand object looks like this:

ICustomerFactory nfc = (ICustomerFactory)cont.Resolve(
                              typeof(ICustomerFactory),
                               "MockNWCustomerFactory");
cmdGetCustomers = new GetCustomersCommand(this, nfc);

Effectively, then, I've let the Module decide which class from the Unity container it will use to retrieve customers. I call this the Distributed Control strategy.

Using the Selection Service
Alternatively, the Resolve code can go in the application's Shell, which I call the Central Control strategy: The application decides which class will be used in the Modules. To make that happen, in the Shell application's BootStrapper class that starts the application, I retrieve the class and pass it to whatever other class needs it. Much of the work to implement the central control strategy is already done. In the case study, my BootStrapper class was already retrieving the View to be used, for instance.

The code in my previous columns also has already set that View's DataContext property to its ViewModel (which the Shell is also retrieving).

So with the View and ViewModel in place, all I have left to do is retrieve the CustomerFactory class I want, and use its GetAllCustomers method to set the Customers property on the ViewModel. Here I use the ServiceLocator object to request an object that implements the ICustomerFactory interface, and that's been assigned the MockNWCustomerFactory name:

ICustomerFactory cf = Microsoft.Practices.ServiceLocation.ServiceLocator.
          Current.GetInstance<ICustomerFactory>("MockNWCustomerFactory");
((ICustListVM)CustOrderWindow.DataContext).Customers = cf.GetAllCustomers();

With my application now assembling itself and using my mock class, I can test that my application behaves correctly (at least with the dummy data returned by my mock class). Once I know that's working, I'll create the real class in the same project with my mock class. In that project's initializer class, I'll also add the code to register the real CustomerFactory class (in this case under the name DefaultNWCustomerFactory):

Microsoft.Practices.Unity.TransientLifetimeManager tlm2 = 
   new Microsoft.Practices.Unity.TransientLifetimeManager();
cont.RegisterType(typeof(ICustomerFactory),
                typeof(CustomerFactory), "DefaultNWCustomerFactory", tlm2);

Now that I've registered my real class, I change my Resolve code to ask for it:

ICustomerFactory cf = cont.Resolve(
   typeof(ICustomerFactory)," DefaultNWCustomerFactory");

ICustomerFactory cf = Microsoft.Practices.ServiceLocation.ServiceLocator.
          Current.GetInstance<ICustomerFactory>("DefaultNWCustomerFactory");

And, of course, there's nothing stopping you from using the ServiceLocator in your Modules. Revisiting my Distributed Control example, rather than using the Unity container in the class that instantiates the GetCustomersCommand object and passing in the CustomerFactory class, I could just use the ServiceLocator in the GetCustomersCommand's Execute method, like this:

public void Execute(object parameter)
{
  ICustomerFactory cf = Microsoft.Practices.ServiceLocation.
     ServiceLocator.Current.GetInstance<ICustomerFactory>
("MockNWCustomerFactory");
  clvm.Customers = cf.GetAllCustomers();
}

For me, the real issue in using the ServiceLocator outside of the Shell is in testing my modules. The "lower down" in my application's structure I use the ServiceLocator, the more times I have to dummy up some sort of context for those modules so that I can test them independently of each other. Passing a Unity container or -- even better -- retrieving the service object higher in the structure and passing it to the class that needs it, makes testing those lower classes much easier: I just need to create an instance of the service class to test my Module.

Now, to switch my code between my mock and real classes (or any other classes that I create), I just need to change the name used in calls to the Resolve method. The best solution for handling these names is probably to store the name in a configuration file and read that in at runtime. With that change in place, I can switch from test to production to whatever other composition I need just by changing the entry in my config file.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube