This is part 3 in the series Working with Prism 4.
Introduction
In the last article, I showed how to structure your Prism application to use the MVVM pattern and use DelegateCommands to communicate between the view and view model. Additionally, I showed how to pull some data in using WCF RIA Services and display it in the view, as well as using Prism Regions and the ability to add and activate different views in a region to accomplish simple navigation for the user (view switching).
In this article, I’ll extend that sample application a little farther and show you how to leverage two other loosely coupled communication features of Prism 4: CompositeCommands and Prism CompositePresentationEvents (aka pub/sub events). The code presented in this article builds on what was the completed code from the last article.
You can download the complete code from the last article that acts as the starting point here. You can download the completed code for this article here.
Note: Now that Silverlight 5 has released, the code for this article uses an updated version of the Prism 4 code recompiled to Silverlight 5 as a target. That code can be downloaded from prism.codeplex.com and is included in the completed code sample for this article.
CompositeCommands
CompositeCommands are another implementation of the ICommand interface defined by Silverlight and WPF. There are several differences about the CompositeCommand implementation and the DelegateCommand implementation. The first is that the CompositeCommand is an aggregation of other commands – a list of ICommand references internally. It allows you to hook up multiple command targets to a single root command that itself can be hooked up to a command source such as a button or menu item. Figure 1 shows this relationship. The CompositeCommand can hold references to any ICommand object, but typically you will use it in conjunction with DelegateCommands. When the CompositeCommand.Execute method is invoked, it will invoke the Execute method on each of the child commands. When CompositeCommand.CanExecute is called to determine whether the command is enabled, it polls its child commands for their result from CanExecute.
Figure 1: CompositeCommands contain other commands
Child commands register themselves with the composite command and can also unregister if appropriate. In this way, it is very similar to a subscribe/unsubscribe of an event, but with the additional functionality of commands to enable and disable the commands based on custom logic.
Another thing different about CompositeCommands is that they can be hooked up to a source control ahead of time, before any child commands have registered. DelegateCommands have to be pointed to their target methods through a delegate at the point where they are constructed. As a result, CompositeCommands allow an extra layer of separation between the source (i.e. toolbar button or menu item) and the target (handling method) – decoupled in lifetime. Because they can target multiple child commands, they also work well for distributed logic such as a Save All command that needs to be dispatched to multiple open documents, each of which has their own handling logic in their view model.
You will see both of these aspects at work in the sample code for this article – separated hook up of the command, and multiple handlers for command execution.
Pub/Sub Events
Another form of loosely coupled communications offered by Prism are pub/sub events. Normal events in .NET are fairly tightly coupled – both the publisher and subscriber objects have to be alive at the same time, the subscriber needs explicit type information and a reference to the publisher to hook up their event handler, and once subscribed, the publisher maintains a reference back to the subscriber through the delegate.
Pub/sub events are designed to break that tight coupling. The idea is that you really want publishers and subscribers to not need either type information or coupled lifetimes. They should be able to come and go independently, and different types of subscribers and publishers should be able to participate in the same event scenario. The event is what is important, not the specific parties who raise or handle the events. To achieve this, you need a middleman or mediator between the publishers and subscribers. The Event Aggregator pattern is a means of achieving this. Prism provides an implementation of the Event Aggregator pattern that is easy to use and that provides several options including strong or weak references for subscribers, thread dispatching, and event filtering.
Figure 2 shows the basic architecture of using the EventAggregator service in Prism. Publishers and Subscribers make calls directly against the EventAggregator to obtain references to event class instances. The EventAggregator is really nothing more than a Registry for event types. Publishers then use those event classes to publish an event with a strongly typed payload and subscribers hook up a listener through a delegate to a method that will accept the payload and handle the event for that subscriber. The event classes are derived from CompositePresentationEvent and do not need any implementation themselves, the class declaration is really just a way to tie together a base class reference with the strongly typed payload through the generic type definition. The CompositePresentationEvent base class provides all the infrastructure to maintain the list of subscribers, dispatch the calls on the appropriate thread, maintain weak or strong references, and filter the calls if desired.
Figure 2: Event Aggregator Architecture
Step 1: Add the Prism Libraries and Recompile for Silverlight 5
The first step for this article is that I downloaded a recently added source code release of Prism 4 from the CodePlex site and added the projects to the solution, as well as changing their compilation target to Silverlight 5. This is because there were a few incompatibilities discovered in using the Silverlight 4 versions of the libraries with Silverlight 5. And I actually stumbled upon one of those incompatibilities in putting together the sample for this article.
Step 2: Modify the application to present multiple edit views
To demonstrate the use of CompositeCommands, I wanted to have a reasonably realistic scenario of where you might use them. As mentioned earlier, CompositeCommands let you separate the hook up of the invoker from the hookup of the receiver in terms of the Command pattern. They also let you have multiple handlers for a given command, to address a Save All kind of scenario, which is what I will be putting together in this article.
To do this, I need more than one thing open at a time to save. I added a TabControl to the MainPage shell view and made it a Prism Region so that multiple CustomerEditView instances could be opened in tabs there instead of swapping out the MainContent region view as was done in the last article.
1: <sdk:TabControl prism:RegionManager.RegionName="SecondaryContent"
2: Grid.Row="2">
3: <prism:TabControlRegionAdapter.ItemContainerStyle>
4: <Style TargetType="sdk:TabItem">
5: <Setter Property="HeaderTemplate">
6: <Setter.Value>
7: <DataTemplate>
8: <StackPanel Orientation="Horizontal">
9: <TextBlock VerticalAlignment="Center"
10: Margin="3"
11: Text="{Binding Title}" />
12: <Button VerticalAlignment="Center"
13: Margin="3"
14: Content="X"
15: Command="{Binding CloseCommand}" />
16: </StackPanel>
17: </DataTemplate>
18: </Setter.Value>
19: </Setter>
20: </Style>
21: </prism:TabControlRegionAdapter.ItemContainerStyle>
22: </sdk:TabControl>
Note the attached property to indicate that this is a Prism Region as discussed in the last two articles, as well as a Style to modify the HeaderTemplate of the TabItems in the TabControl to contain a TextBlock and an X Button to be able to close them. This style gets attached through a custom attached property provided by Prism called TabControlRegionAdapter.ItemContainerStyle. This is needed because the TabControlRegionAdapter is the thing in the Prism toolkit that generates the TabItems as views are added to the region. Also note that the template expects the DataContext to have a Title property for the text in the tab header, as well as a CloseCommand for the button Command. Those will be present on the view models for the individual views which are set as the DataContext based on the MVVM pattern.
To populate the tabs, the EditCommand handling code in the CustomerListViewModel changed to the following:
1: private void OnEditCustomer()
2: {
3: IRegion secondaryContentRegion = RegionManager.Regions["SecondaryContent"];
4: bool alreadyExists = false;
5: foreach (var view in secondaryContentRegion.Views)
6: {
7: var custView = view as CustomerEditView;
8: var custViewModel = custView.DataContext as CustomerEditViewModel;
9: if (custViewModel.Customer == SelectedCustomer)
10: {
11: secondaryContentRegion.Activate(view);
12: alreadyExists = true;
13: }
14: }
15: if (!alreadyExists)
16: {
17: CustomerEditView editView = new CustomerEditView();
18: CustomerEditViewModel viewModel = new CustomerEditViewModel { Customer = SelectedCustomer };
19: editView.DataContext = viewModel;
20: secondaryContentRegion.Add(editView);
21: secondaryContentRegion.Activate(editView);
22: }
23: }
This code is very similar to what was discussed in the last article – it determines if there is a view already being presented within the “SecondaryContent” region for the selected customer. If so, it activates it. If not, it adds one. The main difference here is that the code is now targeting the SecondaryContent region, which is the tab control, and it allows more than one view to be created at a time.
The structure of the CustomerEditView itself did not change at all, but the CustomerEditViewModel had to change quite a bit. First, it needed to have the Title and CloseCommand properties expected by the tab item headers.
1: public string Title { get { return _Customer != null ? _Customer.CustomerID : "Empty"; } }
2:
3: public DelegateCommand CloseCommand { get; private set; }
Second, the CloseCommand handling needs to simply remove its view from the region, not swap views as the SaveCommand handling did from the last article:
1: private void OnClose()
2: {
3: IRegion secondaryContentRegion = RegionManager.Regions["SecondaryContent"];
4: foreach (var view in secondaryContentRegion.Views)
5: {
6: if (view is CustomerEditView && ((FrameworkElement)view).DataContext == this)
7: {
8: secondaryContentRegion.Remove(view);
9: break;
10: }
11: }
12: }
Third, it needed the SaveCommand logic to not swap views but to just manage the “saved” state of the view. In this case, for demo purposes, that meant just modifying the view model to support an IsDirty flag. That flag gets set when the customer is modified (one of its properties change). The flag gets cleared when the SaveCommand fires. Additionally, the SaveCommand was modified to have a CanExecute handler that checks that IsDirty flag. If the view’s state is not dirty, there is no reason to invoke the SaveCommand so its invoker should be disabled.
1: Customer _Customer;
2: bool _IsDirty = false;
3:
4: public CustomerEditViewModel()
5: {
6: SaveCommand = new DelegateCommand(OnSave, CanSave);
7: ...
8: }
9:
10: public DelegateCommand SaveCommand { get; private set; }
11:
12: public Customer Customer
13: {
14: get { return _Customer; }
15: set
16: {
17: if (_Customer != value)
18: {
19: if (_Customer != null) _Customer.PropertyChanged -= OnCustomerChanged;
20: _Customer = value;
21: if (_Customer != null) _Customer.PropertyChanged += OnCustomerChanged;
22: RaisePropertyChanged(() => Customer);
23: }
24: }
25: }
26:
27: public bool IsDirty
28: {
29: get
30: {
31: return _IsDirty;
32: }
33: set
34: {
35: _IsDirty = value;
36: SaveCommand.RaiseCanExecuteChanged();
37: }
38: }
39:
40: private bool CanSave()
41: {
42: return IsDirty;
43: }
44:
45: private void OnSave()
46: {
47: IsDirty = false;
48: }
49:
50: private void OnCustomerChanged(object sender, PropertyChangedEventArgs e)
51: {
52: IsDirty = true;
53: }
Notice that the CanSave handler just returns the flag. But since the enablement of the command depends on that flag, that means whenever that flag changes, the command should raise the CanExecuteChanged event. The best way to do that is to encapsulate the call to the DelegateCommand.RaiseCanExecuteChanged method in the setter for the IsDirty property and use that property internally throughout the view model instead of the member variable.
At this point I have the setup to be able to edit multiple customer views at the same time in individual tabs. Next I want to be able to add a “Save All” command invoker at a shell level and have it invoke the SaveCommand on the individual open edit views as shown in Figure 3.
Figure 3: Multiple edit views active in the tab region
Step 3: Add a CompositeCommand for the Save All command
A common way to define a CompositeCommand is similar to how WPF defines the built-in routed commands – as a public static readonly singleton instance of the command. In order for the invoker to be in one module and the handlers to be in different modules, they all need to be able to get to that definition of that command to hook up to it. So you will need to have a common or shared assembly that all the parts of your solution can reference to hold shared types like CompositeCommands and Pub/Sub events.
The sample solution has a Silverlight Class Library project added with a Commands class to contain the CompositeCommand instances you want to define – in this case the SaveAllCommand.
1: public class Commands
2: {
3: public static readonly CompositeCommand SaveAllCommand = new CompositeCommand();
4: }
Step 4: Hook up the CompositeCommand Invoker
The invoker is simply a button in the MainPage.xaml:
1: <Button Content="Save All"
2: Command="{Binding SaveAllCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
If you are using CompositeCommands in WPF, you can refer directly to the static instance with the {x:Static} markup extension. Because Silverlight does not have that markup extension in the framework, you will have to bind to a property exposed to your XAML. In this case to keep it simple, I expose that property from the code behind of the MainPage view itself and use a RelativeSource binding to get to the root element that corresponds to the code behind class. Then in the code behind I expose the static command variable via the property that the XAML is binding to. Alternatively I could have just hooked up the command in the code behind with a reference to the button.
1: public ICommand SaveAllCommand { get { return Commands.SaveAllCommand; } }
At this point the invoker is ready to invoke the command, but the button will be disabled because the CompositeCommand, as a container for command instances, is empty. The default logic of CompositeCommand is to be disabled unless all of its child commands are enabled. If there are no child commands, it is disabled because there is nothing to invoke. If you find you want different logic, such as enabling the command if any one of its child commands is enabled, all you need to do is derive a class from CompositeCommand and override the CanExecute method.
Step 5: Hook up the child command instances
As mentioned earlier, CompositeCommand exposes a Register/Unregister API for adding and removing child commands. To add a child command, simply call Register, passing an ICommand reference. Because the CustomerEditViewModel already has a SaveCommand that I want invoked when Save All is invoked, I just need to pass a reference to that in the constructor for the view model:
1: Commands.SaveAllCommand.RegisterCommand(SaveCommand);
Likewise, when the view is going away (in the CloseCommand handler), you should unregister the command, otherwise the CompositeCommand will keep it, and be indirection, your view model, alive.
1: private void OnClose()
2: {
3: IRegion secondaryContentRegion = RegionManager.Regions["SecondaryContent"];
4: foreach (var view in secondaryContentRegion.Views)
5: {
6: if (view is CustomerEditView && ((FrameworkElement)view).DataContext == this)
7: {
8: secondaryContentRegion.Remove(view);
9: Commands.SaveAllCommand.UnregisterCommand(SaveCommand);
10: break;
11: }
12: }
13: }
At this point you should be able to run the application and open several edit views as shown in Figure 3. Initially all of the Save buttons in the edit views as well as the Save All button will be disabled because none of the views are dirty. Go make an edit to each one of the views, and you will see their individual Save button become enabled. With the default CanExecute logic of the CompositeCommand, not until all of the child commands becomes enabled is the CompositeCommand itself enabled. So at the point where you have each of the child views Save enabled through an edit, the Save All button will become enabled. Clicking it invokes the SaveCommand handler in each instance of the CustomerEditViewModel, setting its IsDirty flag to false and raising the CanExecuteChanged handler for that command, which the CompositeCommand will monitor to refresh its own command enabled state.
Step 6: Add an Orders Module
A common usage of pub/sub events is to communicate between loosely coupled module components, particularly from one view model to another, especially if those view models are defined in separate modules. To demonstrate this, I want to add the capability to display the last 10 products ordered by a customer in a side panel whenever a customer is selected. I want this functionality to be decoupled from the customer listing and editing capabilities in the Core module, possibly developed by a separate team or added as a separate pluggable feature of the application.
To do this, I added a new Orders module to the solution following the procedures outlined in the first article for defining a Prism module:
- Add a Silverlight Application project
- Delete MainPage.xaml and App.xaml
- Set the Startup Object in the project settings to Not Set.
- Add Prism references, setting the Copy Local property on the references to false so that you don’t get multiple copies of the Prism libraries loaded when the module loads.
- Defined an OrdersModule class with a ModuleExport attribute and an implementation of the IModule interface.
- Added the Orders module to the Silverlight hosting settings of the Web project so that it is available for download by the module manager in Prism.
- Added module information to the ModuleCatalog.xaml in the shell project.
Additionally, since now both the Core module and the Orders module will need to use WCF RIA Services to retrieve data from the back end, it makes sense to move the client WCF RIA Services code out to a shared library so that there is just one definition of the entity types and the DomainContext that lets the client code talk to the server. As a result, I also did the following in the solution:
- Removed the RIA Services link from all the client projects.
- Added a NorthwindRIAClientLibrary Silverlight Class Library project with the RIA Services link in the project properties set to the hosting web project where the domain services live.
- Added a reference to NorthwindRIAClientLibrary to each of the projects, with them all set to Copy Local = false except the reference in the shell application.
Step 7: Add another region for the view to plug into
In MainPage.xaml in the shell application, I added another region named SidePanel to the right of the listing of customers for the order summary to plug into.
1: <Grid Grid.Row="1">
2: <Grid.ColumnDefinitions>
3: <ColumnDefinition Width="*" />
4: <ColumnDefinition Width="Auto" />
5: </Grid.ColumnDefinitions>
6: <ContentControl prism:RegionManager.RegionName="MainContent"
7: Grid.Column="0"
8: Margin="5" />
9: <ContentControl prism:RegionManager.RegionName="SidePanel"
10: Grid.Column="1"
11: Margin="5" />
12: </Grid>
Step 8: Add a View and ViewModel to present the order summary
The view simply contains a DataGrid with three columns: Order date, product name, and quantity. The view model uses WCF RIA Services to retrieve the order information for a given customer and populate a collection of OrderItem data structures to populate the DataGrid with the three columns.
1: public class OrdersViewModel : INotifyPropertyChanged
2: {
3: CustomersDomainContext _Context;
4: public OrdersViewModel()
5: {
6: if (!DesignerProperties.IsInDesignTool)
7: {
8: _Context = new CustomersDomainContext();
9: }
10: }
11:
12: ObservableCollection<OrderItem> _Orders = new ObservableCollection<OrderItem>();
13: public ObservableCollection<OrderItem> Orders
14: {
15: get { return _Orders; }
16: set
17: {
18: if (_Orders != value)
19: {
20: _Orders = value;
21: PropertyChanged(this, new PropertyChangedEventArgs("Orders"));
22: }
23: }
24: }
25:
26: public void OnCustomerSelected(Customer cust)
27: {
28: Orders = new ObservableCollection<OrderItem>();
29: if (_Context == null || cust == null)
30: {
31: return;
32: }
33:
34: EntityQuery<Order_Detail> detailQuery = _Context.GetOrder_DetailsQuery();
35: _Context.Load(detailQuery.Where(
36: det => det.Order.Customer.CustomerID == cust.CustomerID).OrderByDescending(
37: det => det.Order.OrderDate).Take(10), OnDetailsLoaded, null);
38:
39: }
40:
41: private void OnDetailsLoaded(LoadOperation<Order_Detail> loadOp)
42: {
43: var details = loadOp.Entities;
44: if (details != null)
45: {
46: var detailsList = details.ToList();
47: detailsList.ForEach(det => Orders.Add(
48: new OrderItem
49: {
50: ProductName = det.Product.ProductName,
51: Quantity = det.Quantity,
52: OrderDate = det.Order.OrderDate.Value
53: }));
54: }
55: }
56: }
Now the only thing left to do is call the OnCustomerSelected method whenever a Customer is selected in the main listing. But that code lives in a totally separate module that you are trying to keep decoupled. Prism events to the rescue.
Step 9: Add a Prism Event
When working with Prism events, the first thing to do is to declare the event type. You do this by deriving a type from CompositePresentationEvent as described earlier, and indicating through the generic type argument what the payload type will be:
1: public class CustomerSelectedEvent : CompositePresentationEvent<Customer> { }
Like the CompositeCommand, this type will need to be referenced by both sides of the communication, even though those two sides need to be decoupled from each other. So this is another one of those types you will want to declare in a shared library that any modules in the solution can reference.
Step 10: Hook up the subscriber
To hook up a subscriber, the subscribing code first needs access to the EventAggregator service in Prism. This is a singleton service like the RegionManager that you can simply obtain by using dependency injection and importing it through the container. Add the following code to the OrdersViewModel:
1: public OrdersViewModel()
2: {
3: if (!DesignerProperties.IsInDesignTool)
4: {
5: CompositionInitializer.SatisfyImports(this);
6: EventAggregator.GetEvent<CustomerSelectedEvent>().Subscribe(OnCustomerSelected);
7: _Context = new CustomersDomainContext();
8: }
9: }
10:
11: [Import]
12: public IEventAggregator EventAggregator { get; set; }
Because the view model will be constructed in the XAML of the view, the container will not be involved in its construction. To get the container to satisfy the imports in the class, which in this case includes the event aggregator, the CompositionInitializer class can be used. It is put in a guard condition as discussed in a previous article so that the designer does not break since it should not execute that code in the designer.
After the SatisfyImports call, the Import property will be set by the container. So the next line of code can then use that IEventAggregator reference to subscribe. That involves calling the GetEvent<T>() method to get a reference to the event, then calling subscribe on the returned event, which can be done in a single line of code as shown. The Subscribe method just takes an Action<T> delegate, where T is the payload type defined by the event class. The OnCustomerSelected method was shown earlier.
By default, Prism events maintain weak references to the subscribing class method. That means that if all other references to the object go away, the event reference will not keep it alive. This solves a lot of memory leak issues where you either forget or it is difficult to know where in the code to do the Unsubscribe call. If you want the event subscription to keep the object alive, there is an overload to the Subscribe method with a keepSubscriberReferenceAlive bool parameter.
Additionally, by default the event publication will happen synchronously using the publisher’s thread. If you want control over what thread is used to call the target method pointed to by the Subscribe call, you can use another overload that takes a ThreadOption with three choices: use the Publisher’s thread (the default), use the UI thread, or use a background thread from the thread pool.
Finally, there is an overload that also allows you to pass a Predicate<T> delegate for filtering purposes. This allows you to pass a lambda expression or point to a method that returns a boolean. The method or lambda will be passed the payload object of the event. It can use that to decide whether to return true or false. True means to call the subscription method, false means don’t.
Step 11: Publish the event
To publish a Prism event, it is just as simple as subscribing. you first need a reference to the EventAggregator service, which you get through dependency injection as shown earlier. Then you publish at the appropriate point in your code calling the Publish method on the event returned from the same GetEvent<T>() method shown earlier:
1: EventAggregator.GetEvent<CustomerSelectedEvent>().Publish(_SelectedCustomer);
Summary
In this article, you saw how to define multiple modules that can communicate with each other through a combination of CompositeCommands and Prism events. CompositeCommands let you have handlers registered or unregistered in a loosely coupled fashion, and allows you to have multiple handlers (child commands) that will be used by the CompositeCommand. Prism events are for situations that are not necessarily an action->reaction kind of set up where enablement and disablement is needed. You simply define the event type with its strongly typed payload and then subscribe or publish by obtaining the event reference through the EventAggregator. Both of these mechanisms give you a really powerful combination for having loosely coupled communications between composite parts of your application.
You can download the finished code from this article here.
About the Author
Brian Noyes is Chief Architect of IDesign, a Microsoft Regional Director, and Silverlight MVP. He is a frequent top rated speaker at conferences worldwide including Microsoft TechEd, DevConnections, VSLive!, DevTeach, and others. Brian worked directly on the Prism team with Microsoft patterns and practices and co-authored the book Developers Guide to Microsoft Prism 4. He is also the author of Developing Applications with Windows Workflow Foundation, Smart Client Deployment with ClickOnce, and Data Binding in Windows Forms 2.0. Brian got started programming as a hobby while flying F-14 Tomcats in the U.S. Navy, later turning his passion for code into his current career. You can contact Brian through his blog at http://briannoyes.net/ or on Twitter @briannoyes.