Skip navigation

Introduction to Using Prism with Silverlight 3

Break your monolithic applications into manageable chunks

Downloads
124942Code.zip

Microsoft's patterns and practices group designed Prism, formally known as the Composite Application Guidance for WPF and Silverlight, to help you build modular Windows Presentation Foundation (WPF) and Silverlight client applications more easily.

Prism offers several key benefits. It promotes modularity through the various classes and services it provides in the Composite Application Library (CAL). It gives the ability to compose the overall UI using independent views. It offers extensibility through the various interfaces and base classes provided, and it lets you adopt as little or as much of it as you need in your application without having to include the entire CAL. Prism's chief goal is to offer guidance for building composite applications. You can download it from Microsoft.

Composite Applications


If you've ever worked on an application that was hard to extend, maintain, test, and deploy, and was so tightly coupled that correcting one defect introduced several new ones, you know all too well the horrors of a monolithic application. Sadly, most applications in the wild suffer from exactly this malady. It's so prevalent that you may wonder if there's an alternative to deliver you from this nightmare. This is where the idea of a composite application was born.

Composite applications seek to address the evils of monolithic applications by introducing modularity and the use of shared services. In a composite application, developers create loosely coupled and independently evolvable modules that work together in the overall application. Each module in a composite application is designed to be independently created, maintained, tested, and deployed, often by separate teams. These modules communicate with one another and the main application shell using loosely coupled events and interfaces aimed at reducing the concrete references between modules.

It's important to understand that some level of dependency between modules in an application is a necessity. However, compile-time dependencies where one component directly depends on another component's type or implementation aren't. In a composite application, you seek to reduce these necessary dependencies between modules. One of the most common ways to provide this decoupling is to offload the management of these dependencies to another, intermediary object. Before I introduce this object and its important role in a Prism application, I'll need to introduce the concept of dependency injection.

Dependency Injection


In a composite application, the necessary dependencies between modules are often mitigated by the use of interfaces at compile-time and through the resolution (or injection) of the concrete references at run-time. This run-time injection of dependencies is appropriately named Dependency Injection (DI) and is a specialized version of the Inversion of Control (IoC) design pattern. (DI and IOC are described in detail on Martin Fowler's website. The task of managing and injecting these dependencies in an application that uses DI is handled by what Fowler calls an assembler object, but which is more commonly referred to as a DI container. DI containers play a large role in a composite application, so it's important to spend some time them before you start creating a composite application with Prism.

The DI container is the glue used to compose an application into a composite application. It manages and injects the concrete dependencies and provides a collection of shared services (often singletons) and creates and injects these where needed. There are a couple of standard ways that these dependencies can be injected, including constructor and setter injection.

In constructor injection, an object's constructor includes the required dependency as one of its parameters, usually as an interface, like so:

// constructor with injected dependency
public ZipEntryViewModel(IForecastService forecastService)

In the above code, the concrete type that implements IForecastService isn't known at compile time. At run time, the concrete object that implements IForecastService is injected by the DI container into the ZipEntryViewModel class's Constructor. The DI container knows which concrete class to inject based on the type previously registered with the DI container, as in

container.RegisterType();

This RegisterType method essentially says that any time an IForecastService is requested, it should be resolved to a concrete MyForecastService object. This lends itself well to being substituted with a mock object in unit testing scenarios, because you could replace MyForecastService with a mock object that implements IForecastService.

The other type of injection is setter injection, which involves creating a property on the object that needs the dependency and marking it with a DependencyAttribute to make the DI container aware that it needs to inject the dependency. For example:

\\[Dependency()\\]
public IForecastService ForecastService \\{ get; set; \\}

In addition to injecting dependencies, the DI container can also act as a service locator, allowing components to register types, instances, and services (as shown above). Then, when a component needs a concrete reference at run time, it can request it from the DI container and the previously registered type will be created and supplied. In this example, the resolve method will return the concrete type that was previously registered to handle all IForecastService requests:

IForecastService forecastService = container.Resolve();

There are numerous DI containers available for integration into your composite application, including Microsoft Unity, Spring.NET, and Castle Windsor. Prism uses Unity as its default DI container and even provides a UnityBootstrapper base class that takes care of wiring your Prism application with Unity. If you choose to use one of the other DI containers, you'll need to implement this wiring code yourself.

Introducing Prism


Now that you have some understanding of the problems that Prism tries to address and some of the patterns that it uses, let's take a look at exactly what Prism brings to the table. Prism includes the CAL, documentation, quick starts, hands-on labs for various targeted concepts, and an example application called Stock Trader Reference Implementation that shows all of the moving parts of Prism in an application that targets both WPF and Silverlight.

As with any guidance, Prism isn't prescribed for every application. Fortunately, there's some excellent information in the provided documentation that will help you with your decision. The CAL itself is constructed in a composite manner and is divided across multiple namespaces and assemblies, allowing you to selectively use the pieces that meet your application's needs.

A few of the high points from the CAL include modularization via its IModule interface, built-in integration with the Unity DI container via the UnityBootstrapper Class, and the ability to multi-target your code base to WPF and Silverlight via the Project Linker. It provides a set of shared services for subscribing and publishing events and an ICommand implementation (which is noticeably absent in Silverlight 3). You can also use the CAL to compose the user interface of your application. It doesn't implement a specific separated presentation pattern for this UI composition, but it very nicely lends itself to using most of the popular flavors of the presentation model design pattern, including Model View Controller (MVC), Model View Presenter (MVP), and Model View ViewModel (MVVM).

As I walk through the creation of a small Prism application that implements the MVVM pattern in Silverlight in this article, I'll assume a basic level of understanding of events, commands, XAML, and databinding. Note that I'll be spending most of the time highlighting the concepts and patterns provided by the CAL, but I encourage you to check out the other parts of Prism, because they're all really top-notch. A thorough review of the documentation will go a long way toward helping you understand how to best use the various pieces of the CAL.

Shell and Regions


Generally, the first item of business in creating a new Prism application is to create the shell. This is the main window of your composite application and will contain the various regions that you will use to serve as the placeholders for the views that you'll create to compose your interface (see Figure 1). If you've done any ASP.NET programming, you can think of the shell as analogous to the master page. As a developer, your main responsibility for the shell is to define where these regions appear and how they get filled.

Figure 1: Illustration of container relationships

 

See Figure 2 for an example of a simple Shell.xaml with two regions defined. You'll notice that I've included a new namespace that includes the RegionManager. The RegionManager has an attached property called RegionName that you'll use to identify this control as a region. This RegionName property is the key to accessing this region when you are ready to connect the region with the view or views that will fill it. These regions can be of type ContentControl, ItemsControl, TabControl, or any classes derived from them. If you need a region of a different type, you can create your own using the RegionAdapterBase class included in the CAL. You can find many fine examples of custom RegionAdapters using your favorite search engine.

Figure 2: Example Shell.xaml with two regions defined

Modules


A Module in Prism is a set of related concerns in your application and can contain views, ViewModels, services, or more. In the sample project, I've implemented a SearchModule that contains the functionality for searching for a weather forecast based on a zip code. I've also created a FutureForecastModule that displays the five day forecast. By convention, these modules are each created in their own project within the application solution. Each module implements the IModule interface, which has a single method called Initialize that's used to register the module's views and services with the RegionManager and DI container respectively.

The modules are designed to be discovered and loaded at run time and Prism offers a lot of flexibility here as to where to define and how to load these modules. You can load them programmatically in your code, from a config file, from another XAP file, or even from a database. This flexibility can often allow you to extend, test, and deploy modules without ever having to touch your main application's compiled code. The modules are loaded into a ModuleCatalog that's created by the bootstrapper.

Bootstrapper


The bootstrapper is a simple class derived from the abstract UnityBootstrapper class that takes cares of the initialization of an application. There are a couple of important methods that you'll want to override in your bootstrapper. The first one is GetModuleCatalog, which takes care of creating and loading the modules. A simple implementation of GetModuleCatalog that programmatically loads the module catalog looks like this:

protected override IModuleCatalog GetModuleCatalog()
\\{
ModuleCatalog catalog = new ModuleCatalog();
catalog.AddModule(typeof(SearchModule.SearchModule));
catalog.AddModule(typeof(FutureForecastModule.FutureForecastModule));
return catalog;
\\}

In a more real-world application, you could have conditional code that loaded the modules based on certain conditions. In fact, there are several overloads of the AddModule method that let you specify InitializationMode and even specify dependencies between the modules, so that they'll be loaded in the appropriate order and at the appropriate time.

The other method you'll need to override in your bootstrapper is the CreateShell method. In a typical Silverlight application, the RootVisual of the application is set in the Application_Startup method of the App.Xaml.cs. However, in a Prism application, the CreateShell method takes care of creating your shell and assigning it as the RootVisual of the application. Having this assignment happen in the bootstrapper gives you a lot of flexibility to make programmatic decisions at run-time, such as selecting between different shell files to use as the RootVisual based on some criteria. A simple implementation of this method in your Bootstrapper would look like this:

protected override DependencyObject CreateShell()
\\{
Shell shell = new Shell();
Application.Current.RootVisual = shell;
return shell;
\\}

In addition to creating and loading the ModuleCatalog and creating the shell, the bootstrapper class takes care of configuring the RegionAdapters, configuring the DI container, and initializing each of the modules by calling their initialize methods. See Figure 3 for an illustration of the bootstrapper's duties.

Figure 3: Illustration of the bootstrapper's duties

Views


The view is the main unit of UI construction in a composite application. The views are implemented in the modules and are usually implemented as a UserControl or a DataTemplate. If you recall from the discussion of regions, the views are what gets loaded into the regions at run time, as illustrated in Figure 4.

Figure 4: Illustration of views loaded into regions at run time

The views can be loaded into the regions in one of two different ways: view discovery or view injection. Using a view discovery approach, the views are registered by their modules against a named region. When that region needs to be filled at run time, the shell (or the parent view) asks the RegionManager if there are any views that have registered to appear in that region. The views are then automatically created and displayed within the proper region. The module's only interaction in view discovery is this registration process.

View discovery is the simpler of the two methods. The alternate approach, view injection, is more complex but allows more explicit control, because the modules actually add or remove their views from the regions instead of simply registering for the named region. In view injection, the module accesses the RegionManager to look up a region, and then can inject or remove views from the region based on various programmatic decisions.

Views can contain child regions, and they're often implemented using a separated presentation pattern such as MVVM.

Model View ViewModel


The MVVM pattern is a specialized version of the presentation model pattern. (For a full explanation of the presentation model pattern, see Martin Fowler's website. The MVVM pattern has become popular for use in Silverlight and WPF applications because it really takes advantage of the extensive databinding capabilities of these platforms. This built in databinding can be used to attain a clean separation between the view and its underlying data model. Using MVVM helps reduce the coupling between the logic and the presentation by having the view bind its DataContext to the ViewModel. This decoupling allows the UI to be independently developed and tested by substituting mock objects in your unit tests. There are many great resources and many opinions about the exact minutiae of implementing the MVVM pattern, but I'll give you a quick overview of each of the pieces as I've implemented them in the sample project.

The model is the underlying representation of your domain objects. The model communicates with the ViewModel, but never with the view. In my example, the Forecast and ForecastDay classes are the models. The view is the actual visual UI and communicates only with its ViewModel. There's generally a one-to-one correspondence between the views and their ViewModels. The view gets both its data and its command actions by databinding its DataContext to its accompanying ViewModel. In your views, you should strive to have little or no code in the code-behind files. UI Designers can independently build and modify the views.

ViewModels sit between the view and the model. ViewModels expose their data and various actions to the view using bindable properties. ViewModels (or a ViewModel base class) will typically implement the INotifyPropertyChanged Interface to notify the UI when the data has changed. Collections in a ViewModel will often be of type ObservableCollection since this collection already implements the INotifyCollectionChanged interface, as illustrated in Figure 5.

Figure 5: Illustration of implementation of the INotifyCollectionChanged interface

Services


I mentioned that modules can contain both services and views. These services are non-visual objects that exist to provide some centralized functionality to a module, multiple modules, or an entire application. When the module is initialized by the bootstrapper, it registers its services with the DI container. The module gets a reference to the DI container by specifying the container as a parameter of its constructor (the constructor injection). Here's an example of an injected container and the module registering its service in the initialize method:

private readonly IUnityContainer _container;

public SearchModule(IUnityContainer container)
\\{
_container = container;
\\}

public void Initialize()
\\{
_container.RegisterType();
\\}

In the sample application, I've created RandomForecastService, which implements the IForecastService interface. In the above code, I'm registering this service with the DI container to let it know that if any code asks for an IForecastService, the container should create and supply an instance of the RandomForecastService. In the ZipEntryViewModel, the constructor requests that an IForecastService be injected via constructor injection and then uses the IForecastService to fetch the forecast when the user enters a zip code and initiates a search. By not creating an actual reference in the ViewModel to the RandomForecastService, I've allowed the ViewModel to be tested using a mock implementation of IForecastService, or to have the actual implementation of IForecastService vary at run-time.

private readonly IForecastService _forecastService;

public ZipEntryViewModel(IForecastService forecastService)
\\{
_forecastService = forecastService;
\\}

void OnSearch(string zip)
\\{
Forecast forecast = _forecastService.GetForecastByZip(zip);
\\}

There are also several core shared services that are supplied by the CAL including: IEventAggregator, IRegionManager, and IUnityContainer.

Commanding


Now that there's an IForecastService implementation available to provide the forecast for a given zip code, I need a way for the view to let the ViewModel know that the user has entered a zip code and wants to initiate a search. If you haven't used a separation presentation pattern in the past, your first impulse is likely to be creating an event handler in your view's code-behind. Doing this violates the intended separation of the logic from the presentation, however, and makes it very difficult to place a unit test around the functionality of the user's action of initiating a search.

Commands are a way to loosely couple an action to the UI. As of Silverlight 3, there's no out-of-the-box implementation for the ICommand interface. The CAL, however, provides both the DelegateCommand and the CompositeCommand implementations. CompositeCommand is a command that has multiple child commands and is useful for "save all" type scenarios where more than one module is interested in the command. The DelegateCommand simply allows for delegating the command logic to an element that isn't in the logical tree (in our case the ViewModel). The command is exposed as a bindable property in the ViewModel, as you can see from this sample implementation:

private ICommand _searchCommand;
public ICommand SearchCommand
\\{
get
\\{
return _searchCommand;
\\}
set
\\{
_searchCommand = value;
OnPropertyChanged("SearchCommand");
\\}
\\}

public ZipEntryViewModel()
\\{
SearchCommand = new DelegateCommand(OnSearch, OnCanSearch);
\\}

 

When you create the DelegateCommand, you provide a few key pieces of information. The type parameter is the type that the handler methods are expecting as their payload (in the case above, a string). The first parameter to the constructor is the method that should be called when the command is executed (OnSearch) and the second parameter is the method that should be used to determine if the command can be executed (OnCanSearch). This CanExecuteMethod can be used to control the enabling or disabling of the UI element that is bound to the Command automatically. In the sample application, the search button is bound to the SearchCommand and the OnCanSearch method simply tests whether the entered value is a valid zip code. If OnCanSearch returns true, the command takes care of automatically enabling the search button. All of this is handled in a loosely coupled manner, allowing you to independently change and test the ViewModel's functionality. The Commands are specified in the XAML file using an attached property provided by the CAL.

  

Notice how the Click.Command property is data-bound to the SearchCommand property in the ViewModel. The Click.CommandParameter allows you to specify the parameter. In my case, I'm taking advantage of the element-to-element databinding that was introduced in Silverlight 3 to simply pass the text property of my ZipText text box. Back in the ViewModel, the OnSearch and OnCanSearch methods will receive the entered zip code as their payload.

void OnSearch(string zip)
\\{
Forecast forecast = _forecastService.GetForecastByZip(zip);
\\}

bool OnCanSearch(string zip)
\\{
return (zip.IsValidZipCodeFormat());
\\}

Eventing


At this point in the SearchModule, I have a service that can fetch our forecast based on a zip code and a command in the ViewModel that's loosely bound to the View and that can call this service to get the forecast. What's missing is a way to notify the module that's responsible for displaying the five-day forecast (FutureForecastModule) that the forecast has been retrieved. This is where the concept of an event comes into play.

In a typical .NET event scenario, one or more objects will subscribe to another object. At some point, this publisher object will fire the event and all subscriber objects will be notified, and they can then take whatever action they desire. However, this requires that the subscriber have a direct reference to the publisher. In the context of the composite application, you want to try and decouple this dependency. You do this by using the IEventAggregator Service provided by the CAL.

The IEventAggregator serves as a container for events. At run time, the IEventAggregator can be injected into an object that will use it to get a reference to a specified event, and can then publish or subscribe to it. This removed dependency allows the subscribers and publishers to evolve independently.

The other key piece of the eventing architecture in the CAL is the CompositePresentationEvent Class. This is the base class for all custom Events and is the only implementation of the EventBase that is provided by the CAL. The CompositePresentationEvent is the real workhorse here, because it has the job of maintaining the list of subscribers and then notifying them when the event is published. An added benefit of the CompositePresentationEvent is the type safety provided by it being a generic. The type parameter is the type of the payload expected by the subscriber and publisher's methods. Defining a CompositePresentationEvent is dead simple and is, by convention, defined in an infrastructure or common project. Here's the ForecastChangedEvent from the sample project. Notice that the payload is an instance of the Forecast class.

using Microsoft.Practices.Composite.Presentation.Events;
using PrismWeather.Infrastructure.Models;

namespace PrismWeather.Infrastructure
\\{
public class ForecastChangedEvent : CompositePresentationEvent
\\{

\\}
\\}

Now that ForecastChangedEvent is defined, I want to have the FutureForecastModule subscribe to be notified when the forecast has changed. To do this, I first need access to the IEventAggregator. I'll get access by having it injected into the constructor of our FutureForecastViewModel and then using it to subscribe to the ForecastChangedEvent, like so:

public FutureForecastViewModel(IEventAggregator eventAggregator)
\\{
eventAggregator.GetEvent().Subscribe(OnSearch); \\}

The parameter that's being passed to the Subscribe method is the callback method that I want to be executed when the event is fired. The Subscribe method has various overloads that allow you to specify which thread the callback method should be called on (PublisherThread, BackgroundThread, or UIThread). The default is PublisherThread. To facilitate the cases where a subscriber isn't interested in every instance of a published event, the Subscribe method will also allow you to provide a filtering delegate that will filter the events before any registered callbacks are executed.

The last order of business in setting up ForecastChangedEvent is to have SearchModule publish the event. Here again, I need a reference to the IEventAggregator, and I'll get it using constructor injection into ViewModel. Once I have that, I'll use it to get the event and publish the event when SearchCommand is triggered by the Search button in the view.

private readonly IEventAggregator _eventAggregator;
public ZipEntryViewModel(IEventAggregator eventAggregator)
\\{
_eventAggregator = eventAggregator;
\\}

void OnSearch(string zip)
\\{
Forecast forecast = _forecastService.GetForecastByZip(zip);

_eventAggregator.GetEvent().Publish(forecast);
\\}

In the Publish method, you'll note that I pass the type that I defined in the type parameter of CompositePresentationEvent. There's also the Unsubscribe method available on the CompositePresentationEvent for unsubscribing from an event.

Go Forth and Compose


I've attempted to shine some light on the evils of monolithic applications and I've offered the alternative of using Prism and the MVVM pattern to create a composite application that will be easier to maintain, test, and deploy over the lifetime of the application. I've walked through the various building blocks of the CAL and shown this in the context of a small sample application. If you haven't had an opportunity to play around with Prism, I encourage you to download the bits and give it a spin.

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish