This part of the tutorial assumes some knowledge of C#, MEF and Silverlight as well as the concepts of Prism modules and regions which can be learned from parts 1 and 2 of this tutorial.

As we learned in Part 1: Prism Modules, Prism modules are independently deployable software units (.dll files in WPF and .xap files in Silverlight). The main module which is used to assemble other modules is called "application".

Communication between different modules is a little bit of a challenge since most modules do not reference each other (the independence condition) and thus cannot access each other's functionality directly. The modules, however, can reference some other projects that can provide a way for them to access common communication data and communication interfaces.

There are three ways for Prism modules to communicate between each other:

Via a Prism service: A common MEF-able service is defined in a project referenced by all the modules that use it for communications.

Via a Prism region context: Data can be transmitted from a control containing a region to the module loaded into the region.

Via Prism's Event Aggregator: It is the most powerful and simple method of communication between the modules - unlike Prism service, it does not require any extra services built and unlike region context method, it can be used for communicating between any two modules, not only the modules within region hierarchy.

Inter-Module Communications Overview and Samples

This tutorial contains 3 samples - each demonstrating one of the ways in which modules can communicate between each other, described above. In all these samples, a string from one module is copied into another module and displayed there.

Inter-Module Communications via a Service

The source code for this project can be found under "CommunicationsViaAService.sln" solution.

Two modules: Module1 and Module2 are loaded into the application (Main Module) by the bootstrapper. The application and the two modules are dependent on a very thin project called "Common" containing an interface for the inter-module communication service:

Module2View gets a reference to the "String Copy" service via its importing constructor (this is necessary since we want to make sure that we have an initialized service reference within the constructor). It registers an event handler to the service's CopyStringEvent event to catch the string copy event. Within the event handler, we assign the copied string to a text block within Module2View view:

Inter-Module Communications via a Service with Weak Event Handlers

Note: Reader "stooboo" noticed that the code above might cause a memory leak in case when the views that handle the service's event (e.g. IStringCopyService.CopyStringEvent in the previous example) are created and removed throughout the lifetime of the application.

There are several ways to deal with such situation. The simplest but, the least safe one would be to force removal of the event handler when the view is removed from the application. In terms of the previous sample, that would mean calling...

...at every place in the code in which the view is removed from the application.

Obviously, this is not a very good approach as the onus is placed on the developers who use the service and who can very easily forget inserting the clean up code at every place they need it.

The right solution would be to create a weak event, so that adding a handler to it would not prevent the corresponding view from being garbage collected when it is no longer referenced by other parts of the application.

Here I use my own "poor man's" implementation, specific to StringCopyService just enough to show general ideas of how it can be achieved. This implementation, by the way, matches Prism's WeakDelegatesManager functionality (for some reason, WeakDelegatesManager class is internal to Prism, so I could not use it directly).

The code for this sample is very similar to the previous one. The only difference is that StringCopyServiceImpl class has additional functionality that turns CopyStringEvent into a weak event. Also Module2View now has a button and the functionality that can be used to add or remove another view (DynamicView) to its "DynamicViewRegion" region:

Prism's DelegateReference class is employed to create a weak delegate referencing the event handler. It is added to the list of such event handlers. When copy operation is called, we iterate over all DelegateReference objects within the list calling the corresponding delegate. One can see that we are not removing the DelegateReference objects from the list, so there is still a little bit of a memory leak, but, the views that they are referring to are no longer hard referenced and will be removed once other references to them are removed. If necessary, we can do the Delegate list clean up every time we add a new event handler (this will prevent any memory leak).

This is how the application looks after copying was triggered by the "Copy" button on the left hand side:

If one presses "Remove Dynamic View" button on the right, the "Dynamic View" disappears and one should see the following text "DynamicView Destructor Called" within Module2View area:

Note: For some reason, the destructor is not called every time we null the references to the view. I think it is related to some kinks with Silverlight's garbage collector. But this has nothing to do with the event handler within the view - the same happens when I disconnect the event handler.

Inter-Module Communications Via Region Context

As we learned in Parts 1 and 2 of this tutorial, one module can define a region over a ContentControl, ListBox or some other elements, while other modules can plug its views into that region. It turned out that we can define some data that can be passed between the module that defines a region and the module which plugs its view into that region.

The region context sample is located under "CommunicationsViaRegionContext.sln" solution. Its application (main module) uses Region Context functionality to pass data to its Module1 module.

ContentControl that defines "MyRegion1" is located in Shell.xaml file within the application (main module):

<!-- this content control defines the location for MyRegion1 region--><ContentControlx:Name="TheRegionControl"HorizontalAlignment="Center"VerticalAlignment="Center"prism:RegionManager.RegionName="MyRegion1"/>

One can see from the code above that we get the region context by using static function RegionContext.GetObservableContext of the RegionContext class. Then we set its Value property to the data we want to transmit.

On the receiving side Module1View, within its constructor, registers an event handler with the region context's PropertyChanged event to detect when its Value property changes. Within the event handler, the Value property's value is extracted from the region context and assigned to the text block within the module:

Inter-Module Communications via the Event Aggregator

Event aggregator functionality allows different modules to publish and subscribe (to send and receive) any data objects between any modules. Unlike communication methods described above, it does not require a service creation and it does not impose any restrictions on the modules involved.

The sample code is located under "CommunicationsViaEventAggregator.sln" solution. Two modules are loaded into the application: Module1 and Module2. They both reference "Common" library project which defines a class for communication data:

Here is what you'll see once you run the project, enter text within the text box on the left and press the "Copy" button:

If we follow the publish/subscribe functionality described above, we won't be able to distinguish between events that pass data of the same type (there is nothing within the functionality that would allow us to subscribe to only some of the events of MyCopyData type). To fix this problem, Prism introduces a concept called event filtering. There are several Subscribe(...) methods provided by Prism (of which we used the simplest one). The most complex of them has the following signature:

The last of its arguments "filter" is a delegate that takes a data object and returns a Boolean indicator specifying whether the "action" delegate should fire the event or not. Different filtering strategies can be employed: e.g., a subscription delegate can fire only if the data string has some pattern to it. Or, alternatively, we can add "EventName" property to our data class MyCopyData and subscribe only to events of certain name.

The "threadOption" argument to Subscribe function specifies the thread in which the event will be passed from one module to another:

BackgroundThread value will use a thread from the thread pool.

PublishedThread will handle the event in the same thread in which the event was published (best for debugging). This is the default option.

UIThread will perform event handling in the UI thread of the application

The 3rd argument "keepSubscriberReferenceAlive" is "false" by default. Setting it to "true" can make subscription event handler invoked faster, but will require calling Unsubscribe method for the event object to be garbage collected.

Exercise: Create a similar demo using subscription filtering functionality.

Comparison of Inter-Module Communication Methods

This section has been added due to an exchange with reader "stooboo". A big hat tip to him for noticing the potential memory leak and stimulating the discussion about comparing different inter-module communication methods.

Event aggregator is definitely the most powerful and most widely used communication method. It enables communicating between any modules (not only those within the same region hierarchy) and without almost any extra functionality (one does not have to build a service for it). It also can create weak delegate connections to the views so that one does not have to unsubscribe in order for the views to be removed.

As was mentioned above, however, one of the weak points of Prism's event aggregator is the fact that it does event subscription by type. Suppose we need to pass a string argument. To be sure, we can do filtering, but we still need to introduce some complex type containing e.g. EventName property, in order to be able to get only events that we need. So, if you create a 3rd party module, e.g. a window displaying log strings, I would recommend you to create it as a service with the corresponding API that would allow the user to pass strings as arguments and not to use the event aggregator.

Region context is the simplest to use for communication between modules within the same Region hierarchy.

Acknowledgment

I would like to thank my dear almost 9 year old daughter who was nagging me, telling me stories, asking me to test her multiplication table knowledge, threatening me (daddy, if you publish this acknowledgement, I am going to smack you in the face) while I was working on this article, thus proving that I can write Prism articles under adverse conditions.

History

February 20, 2011 - Published the article

February 22, 2011 - Added a section about using weak events in Services and another section comparing different communication methods. Both were added due to a discussion with reader "stooboo".

Share

About the Author

I have 15 years of experience developing enterprise software, starting from C++ and Java on UNIX and moving towards C# on Windows platforms.
I am fascinated by the new .NET technologies especially WPF, Silverlight and LINQ.
Recently I decided to make a move and start my own contracting consulting and mentoring company AWebPros.
I can be contacted via my web site awebpros.com or through my blog at nickssoftwareblog.com

Thanks Rabbil!
after Microsoft's abandoning Silverlight I switched to work primarily in WPF (which is very similar to Silverlight in terms of its development model).
I am actually working on an article describing how to architecture a WPF application. It also talks about inter-widget communications via the View Models.
I hope, I'll be able to publish it within a month.