The Publish and Subscribe pattern (which uses the Observer Pattern in .NET - more info here) is one of those patterns we use all the time while thinking nothing of it. In .NET, we get the concrete implementation via events and delegates.
By abstracting it out just a little and applying concepts from other bus patterns, we gain a ton of flexibility and can use it for more specialized messaging than button click events. In Silverlight applications, in the client code, I tend to use it for two things:
- Providing a means by which different parts of the application can notify each other of changes without requiring tight coupling
- Providing a centralized mechanism for controlling navigation. This is really a variation on the first, but I break it out as I usually do when implementing. I'll post about this topic in the follow-up post
The first one is usually handled by a class named EventBus or similar. (EventBus is a well-known name used by technologies in the java world, biztalk etc., so I recommend that. I was encouraged to see a colleague at another firm used the same name in an application we did for a demo, so it's likely a pretty common name and pattern.)
Here's a simple example from my current project. This part is used for notifying various parts of the application that data has been loaded from async calls to web services:
public static class EventBus
{
public static event EventHandler ConfigurationLoaded;
public static event EventHandler MediaPageContentLoaded;
public static event EventHandler EventPageContentLoaded;
public static event EventHandler GeneralPageContentLoaded;
public static event EventHandler UserInformationLoaded;
#region Event Raising
public static void OnConfigurationLoaded(object sender, EventArgs e)
{
if (ConfigurationLoaded != null)
ConfigurationLoaded(sender, e);
}
public static void OnUserInformationLoaded(object sender, EventArgs e)
{
if (UserInformationLoaded != null)
UserInformationLoaded(sender, e);
}
public static void OnMediaPageContentLoaded(object sender, EventArgs e)
{
if (MediaPageContentLoaded != null)
MediaPageContentLoaded(sender, e);
}
public static void OnEventPageContentLoaded(object sender, EventArgs e)
{
if (EventPageContentLoaded != null)
EventPageContentLoaded(sender, e);
}
public static void OnGeneralPageContentLoaded(object sender, EventArgs e)
{
if (GeneralPageContentLoaded != null)
GeneralPageContentLoaded(sender, e);
}
#endregion
...
}
I store all the loaded data in a singleton ApplicationData class which serves both as the local data cache and the interface to the data web service. Here's the code in that class:
public void LoadUserInformation(string platformSpecificSessionKey)
{
ConfigurationServiceClient client = new ConfigurationServiceClient();
client.GetUserInformationCompleted +=
new EventHandler<GetUserInformationCompletedEventArgs>(client_GetUserInformationCompleted);
client.GetUserInformationAsync(platformSpecificSessionKey);
}
void client_GetUserInformationCompleted(object sender, GetUserInformationCompletedEventArgs e)
{
_userInformation = e.Result;
((ConfigurationServiceClient)sender).CloseAsync();
EventBus.OnUserInformationLoaded(this, new EventArgs());
}
(In woodworking magazines, they often write "safety guards removed for clarity" in articles where someone is doing something dangerous. I'll say the same here, just don't call me on it :)
"Exception Handling Removed for Clarity") If you're struggling with a way to chain your async service calls and want to do the right thing by not using WaitOne or otherwise blocking the UI thread, this is one way that can help you out. Have your client-side service interface class be both a publisher and a subscriber and chain your web service calls via event handlers. You could, of course, just chain inside the same functions, but that gets ugly once you have more than a couple levels.
Here's the code that chains the remaining data calls that are dependent on the call to load the user information. The three calls in here all need the information from the user data web service before they can proceed.
EventBus.UserInformationLoaded += new EventHandler(EventBus_UserInformationLoaded);
...
void EventBus_UserInformationLoaded(object sender, EventArgs e)
{
// we can only get the other information once we have the user
// information available
ApplicationData.Current.LoadEventsPageData();
ApplicationData.Current.LoadMediaPageData();
ApplicationData.Current.LoadGeneralPageData();
}
Note that I don't use this architecture for everything - just information the rest of the application is interested in. I also make sure to translate the events into something meaningful before publishing. For example, the rest of the application is probably not interested in the click event of the "ApplyOptions" button, but it very likely is interested in an OptionsChanged event.
Now, there are lots of other things you may want to notify the rest of the application about. One is the example above where options are changed by the user and you need to do something like change screen colors or font size. Another is when you want to navigate from one page to another. I'll cover using this pattern for navigation in the next post.
One last note: this is by no means specific to Silverlight. I've used this on Windows Forms and other client technologies in the past. It just happens that Silverlight is my newest toy :)