Calcium: A Modular Application Toolset Leveraging PRISM – Part 2

Calcium provides much of what one needs to rapidly build a multifaceted and sophisticated modular application. Includes a host of modules and services, and an infrastructure that is ready to use in your next application.

Contents

Introduction

Calcium is a WPF composite application toolset that leverages the Composite Application Library. It provides much of what one needs to rapidly build a multifaceted and sophisticated modular application.

In part one of this series, we explored some of Calcium’s core infrastructure, including module management, region adaptation, and the Bootstrapper. Now, we will examine the messaging system, and take a look at two other modules, namely the WebBrowser module and the Output module.

Figure: Calcium with the Web browser and Output modules visible.

In the first article, we saw that Calcium consists of a client application and server based WCF services, which allow interaction and communication between clients. Out of the box, Calcium comes with a host of modules and services, and an infrastructure that is ready to use in your next application.

We've got a lot of ground to cover. For that reason, I've decided to break this article up into a series of three, maybe four articles.

Some of the contents in this series of articles are not at an advanced level, and will suit even those that are new to Prism. While others, such as the messaging system, will suit more advanced readers. Hopefully, there are things here to learn for everyone.

These series of articles are, in some respects, an introduction to some areas of Prism. Although, if you are completely new to Prism, you may find yourself somewhat overloaded at times, and I would recommend taking a look at some beginner Prism articles before you tackle Calcium, such as Jammer's Introduction or the Technical Concepts on MSDN.

Location Agnostic Message Service

When developing an application, clearly it’s prudent to have uniformity in the manner certain tasks are carried out. An example of such a task is displaying common dialog boxes. But wait, if you think this section is just going to be about an abstracted dialog box system, it isn't. That would be far too boring. While Calcium does provide a common dialog system, it also allows us to display a dialog to the user from the server during any WCF call! Moreover, it allows us to consume the same API on the client and the server. We don't need to worry about interpreting the result of a WCF call in the client. This means we are able to interact with the user directly from anywhere, without having to know where our business logic is executing, i.e., client or server.

Out of the box, Calcium comes with a number of IMessageService implementations. There is a client implementation for WPF, another client-side implementation for command line driven applications, and a server-side implementation that sends messages back to the client via a callback and leverages the client-side IMessageService implementation.

Firstly, I want to provide you with an overview of the client-side message service, and then we will examine how it is leveraged from the server-side to provide the location agnosticism.

Client-side Message Service Implementation

Obviously, it’s unwise for each member of a development team to be creating his or her own dialogs for simple tasks such as asking the user a closed ended question (a Yes/No question box). We would end up with lots of duplication, and that degrades maintainability. If we decide to change the caption in the dialogs across the board, it is rather more difficult if dialogs are scattered throughout the project. Likewise, if we wish to port the application from WPF to Silverlight, or even to a command line interface (think Powershell or mobile applications), it’s great to be able to swap out the implementation for any given scenario. Clearly, an abstracted layer is in order.

The Message Service has various overloads which allow errors, captions, and messages to be specified.

Let’s take a look at the IMessageService interface and client-side implementations.

Figure: IMessageService allows us to interact with the user in a UI agnostic manner.

The Message Service allows for a Message Importance level to be specified. This allows for a threshold noise level to be specified by the user. If the MessageImportance is lower than the user’s preference, then the user won't be bothered with the message. In a later version of Calcium, we shall see a Preferences Service for specifying the preferred level.

Figure: MessageService and CommandLineMessageService both override the ShowCustomDialog method of MessageServiceBase to cater for their particular environments.

By applying variation through merely overriding the ShowCustomDialog method, it also makes it very easy to mock the MessageServiceBase class for testing.

Our client-side WPF implementation channels all messaging requests through the ShowCustomDialog method as shown in the following excerpt:

I have created various extension methods for translating between the native WPF enums MessageBoxButton, MessageBoxImage, and MessageBoxResult. Why go to all of this trouble? At first glance, it resembles an antipattern. The reason is, in fact, that there are differences in these enums in WPF and Silverlight, and this allows us to cater for both without duplication.

I am considering expanding the service to support message details, and perhaps reducing the number of overloads with a reference type Message parameter. Another improvement would be to implement a Don't show again checkbox system. I leave that for a future incarnation. I hope to use Karl Shifflet’s excellent Common TaskDialog project WPFTaskDialogVistaAndXP.aspx (with Karl's permission).

Server-side Message Service

We've looked at the basic client-side implementation of IMessageService, but now things are going to get a little more interesting. With Calcium, we have the capability to consume the IMessageService in the same manner on both client and server. To accomplish this, we use a WCF custom header and a duplex service callback.

The demonstration application contains a module called the MessageServiceDemoModule. The view for this module contains a single button that becomes enabled when the CommunicationService has connectivity with the server.

The following excerpt shows code from a WCF service, which is executed asynchronously. The user is presented with dialogs on the client-side. Remember, this code executes server-side!

In the above excerpt, we retrieve the IMessageService instance from the Unity container. This is done in the same manner as we would on the client! Being location agnostic allows us to move business logic far more easily. I have placed the Message Service calls here inside a QueueUserWorkItem delegate just to demonstrate the independence of the Message Service. The WCF call returns almost immediately, yet a child thread will continue to work in the background and will still retain the capability to communicate with the user. Without using a child thread to communicate with the user, we may experience a timeout if the user fails to respond fast enough. Please note that we are required to retrieve the Message Service from the Unity container before the service call completes. Not doing so will raise an exception, as the OperationContext for the service call will no longer be present.

Figure: Server causes dialog to be presented client-side.

Figure: Question asked from server.

Figure: Response received and echoed back to user.

How it all Works

When we need a service channel, we use our IChannelManager instance to retrieve an instance. I've discussed the IChannelManager in other articles, in particular here and here.

In WCF, each WCF service has an independent session state. In other words, WCF services don't share a global session. So, in order to identify the client application instance from any WCF service call, we place a custom header into every service channel we create, as shown in the following excerpt from the ServiceManagerSingleton and InstanceIdHeader classes:

We cache the channel until it is closed or faults. But as soon as we create a new channel, we add the header to be consumed on the server. We also have duplex channel support, which works in much the same way.

We must talk to the ICommunicationService in order to create a callback, before we attempt to consume the Message Service on the server-side. This is done automatically by the CommunicationModule. In fact, the CommunicationModule polls the server periodically to let it know that it is still alive. It will also detect network connectivity, or lack thereof, and disable or enable the polling accordingly.

As an aside, the CommunicationModule itself has no UI. Its purpose is to interact with the CommunicationService, and to provide notifications to the client when various server-side events take place. One such CompositeEvent is the ConnectionEvent, which can be seen in the previous excerpt.

I plan on expanding the Message Service system to allow text input, drop down list selection, and maybe even custom dialogs.

WebBrowser Module

The WebBrowser module consists of the module itself, a view, and a viewmodel. The view, a class called WebBrowserView, plays host to a WPF WebBrowser control, whose Url property is bound to the ViewModel, as shown in the following excerpt:

The WebBrowser toolbar has a button whose Command is the WebBrowserViewModel.NavigateCommand and a TextBox. The NavigateCommand is actually injected into the shell’s CommandBinding collection so that it may be invoked when the current view implements the content interface IWebBrowserView.

This can be seen in action in the WebBrowserModule, where we associate the WebBrowserViewModel.NavigateCommand with the content type WebBrowserViewModel, as shown in the following excerpt:

/* When a WebBrowserViewModel is the ViewModel of the active item in the shell,
* the NavigateCommand becomes active. */
commandService.AddCommandBindingForContentType<WebBrowserViewModel>(
WebBrowserViewModel.NavigateCommand,
(arg, commandParameter) => arg.Navigate(commandParameter),
(arg, commandParameter) => arg.CanNavigate(commandParameter));

Here, the shell accepts an ICommand, and when a WebBrowserViewModel becomes active, it will enable the command according to the CanNavigate handler.

The following excerpt is from the ICommandService interface, in which we see the various signatures for associating commands.

///<summary>/// Adds a <seealsocref="CommandBinding"/> for the shell,
/// that is associated with the active content.
/// When the command binding's canExecute handler is called,
/// we get the active content <strong>ß</strong> in the shell.
/// If the active content is of the specified <code>TContent</code>/// type then the specified <code>canExecuteHandler(ß)</code> is called.
/// When the command binding's execute handler is called,
/// we get the active content <strong>ß</strong> in the shell.
/// If the active content is of the specified <code>TContent</code>/// type then the specified <code>executeHandler(ß)</code> is called.
///</summary>///<typeparamname="TContent">The type of the content
/// that must be active in the workspace.</typeparam>///<paramname="command">The command to register.</param>///<paramname="executeHandler">The execute handler.
/// Must return <code>true</code> if the command
/// is to be marked as handled.</param>///<paramname="canExecuteHandler">The can execute handler.
/// If the handler returns <code>true</code> the command is executable
/// (e.CanExecute is set to <code>true</code>), otherwise the command
/// will be not executable (e.CanExecute is set to <code>false</code>.</param>void AddCommandBindingForContentType<TContent>
(ICommand command,
Func<TContent, object, bool> executeHandler,
Func<TContent, object, bool> canExecuteHandler)
where TContent : class;
//… overloads omitted for brevity.
void AddCommandBinding(ICommand command,
Func<bool> executeHandler, Func<bool> canExecuteHandler);
void AddCommandBinding(ICommand command,
Func<bool> executeHandler, Func<bool> canExecuteHandler, KeyGesture keyGesture);
void RegisterKeyGester(KeyGesture keyGesture, ICommand command);
void RemoveCommandBinding(ICommand command);

The WPF routed command infrastructure is useful in scenarios where we have a command target located within the visual tree. Yet with an interface that combines tool views and document views, it can be difficult triggering command handlers, and some controls may behave not as expected. This can be demonstrated when e.g., toolbar buttons are not enabled when the current workspace view is not focused.

In order to address this challenge, we use an ICommandService method to associate a known content type with a command. The following excerpt shows the AddCommandBindingForContentType interface definition.

The AddCommandBindingForContentType has been implemented in the DesktopShell.Commanding.cs as the following excerpt demonstrates:

This all comes together in the WebBrowserModule class, which takes the Workspace region from the RegionManager and populates it with a WebBrowserView. We then create a new instance of the WebBrowserToolBar and place it in the StandardToolBarTray region of the shell.

In order to hide and show the toolbar according to content present in the interface, we use the IViewService, which will hide the toolbar when the current content does not implement IWebBrowser view, and show it when it does. We will examine View Service in the next article in this series.

The final thing we do in the WebBrowserModule is to add the command binding to the shell.

The reader may notice that throughout my code I generally avoid using constructor injection. What is constructor injection? Constructor injection is where a Dependency Injection (DI) container, in this case Unity, is used to automatically call a non default constructor and supply it with instances of known types. I choose to avoid it because I have found it to be troublesome. When resolution failures occur, and one has a number of cascading types that are resolved during constructor injection, one may find oneself digging through reams of stacktrace looking for the root cause of the failure. Thus, I normally retrieve the DI container via a singleton. I can't see the value in injecting the container either. To me, it’s just bloat.

Output Module

The Output Module is used to display messages received via the OutputPostedEvent, which is a Prism CompositePresentationEvent. CompositePresentationEvents are used, along with the IEventAggregator, to allow modules to subscribe and publish events in a decoupled manner. If you are new to CompositePresentationEvents (formally CompositeEvent), more information can be found here[^].

Figure: Routed events cause messages to be displayed in OuputView.

This event can be published from anywhere client-side. The OutputViewModel subscribes to this event, and when a message is received, it adds it to an ObservableCollection, as shown in the following excerpt:

We first retrieve the IEventAggregator instance from the DI container. Then the event sync is retrieved using the GetEvent method. We then publish, or raise, the event, which then causes the handler OnOutputPosted to be called in the OutputViewModel.

Conclusion

In this article, we have seen how Calcium can be used to provide a location agnostic messaging system, which allows a set of common dialogs to be displayed to the user from anywhere, be it server-side or client-side. We can consume the same API on the client and the server, allowing us to interact with the user directly from anywhere, without having to know where our business logic is executing. We also explored the implementation of a web browser module, an output module, and we examined how it is possible to inject RoutedEvent handlers into Calcium’s shell.

In the next article, we shall be taking a look at the File Service, which automatically handles common IO errors, and which has a rather flexible API that I really like. We will also look at the User Affinity Module for providing feedback about other users of the application, and finally the Text Editor Module, which will tie in many of the other features we have been exploring.

We still have a lot to cover, and I hope you will join me for our next installment.

I hope you find this project useful. If so, then I'd appreciate it if you would rate it and/or leave feedback below. This will help me to make my next article better.

History

July 2009

Initial publication

License

This article, along with any associated source code and files, is licensed under The BSD License

Share

About the Author

Daniel Vaughan is a Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company dedicated to creating best-of-breed user experiences and leading-edge back-end solutions, using the Microsoft stack of technologies--in particular WPF, WinRT, Windows Phone, and also Xamarin.Forms.

Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.

Daniel is the developer behind several acclaimed Windows Phone apps including Surfy, Intellicam, and Splashbox; and is the creator of a number of popular open-source projects including Calcium SDK, and Clog.

Would you like Daniel to bring value to your organisation? Please contact

Comments and Discussions

I just used SoapBox (http://soapboxautomation.com/) for my diploma thesis project. Now i just look around for something similar like SoapBox and i found Calcium. It looks perfect for the first look. I'm impressed by WCF MessageService. I need to check internals a little bit, but Perfect.

Few questions>
Is it hard to implement something like "Module Workflows"? User can use only module A. He fills data in moudle A. After he fills data there, He can use and fill data in Modules B and C. After that he can "use and fill" module D...

Or is Calcium transparent around module usage?
If Module B depends on module A and I disable module A. Is module B disabled also?

Daniel,
I'm very impressed with your Calcium application.
I've been reviewing WPF and CAL for the first time in the last few days, comming from CAB and SCSF background over the last few years. Your app has provided me with a one-stop-shop of all the questions I was having.
Wow!
I'd love to know how much time it took you to get to your understanding and this coding.
I'm ploughing through the 305 page CAL document, but you appear to have interpreted it into a real-world app.

What's your goals for Calcium? Just a learning experience, or to you have plans to use in the course of your work?

And it compiles and runs straight out the box, where so much stuff we download never does!!

Thanks very much, and I've learnt a lot in just 2 hrs of browsing your code.

It took me a few weeks of development in my spare time, during the weekends, to put it all together. But it has been a refinement of a lot of the day to day work I've been doing recently. One of the nice things about having a decent infrastructure in place is that you can forget about it; it becomes transparent. That's what I've found has happened with Calcium. It's now also a nice base for myself to use when starting on a new app. For me, I know that if I want to whip up an app quickly, I won't have to spend a long time building the infrastructure.
CAL itself is not overly complicated, and I'm sure you will have a handle on that in no time.

In the future I hope to support Silverlight, and make some general improvements to projects structure for reusability sake. I would like to do some refactoring when I get some time. I also intend to come up with some VS project/item templates for creating modules.

I’d like to assist you. Could you give me some more information? Did this occur when you were installing the clickonce app? I assume so. Sometimes there are differences in the way systems are set up that can throw a spanner in the works. I expect that you would have more luck with the sample download.

Anyway, this project has taken many weeks of my time to develop. Please be aware that by rating it a one, it is enormously discouraging for me.

Voted you 5.Its a good article. I do agree it takes week to develop. Do not be discouarged by the 1. Sometimes people just give 1 to bring their articles up. I just went to that guys profile its recently created and he has posted only one message. So can be fake.

... really is great stuff chap. Thanks for the link back to my article as well! Part 2 is on the way!

I love the messaging service, big time. Very cool indeed. I've also been thinking about your novel use of Unity. I really hadn't thought about registering the MainWindow or the Dispatcher in the container until I saw you do this. Very interesting.