1. Problem
You want to package
application functionality into reusable services and make those services
available within a Silverlight project so that they are available for
the lifetime of the application.
2. Solution
Create a class that implements the IApplicationService interface and possibly the IApplicationLifetimeAware interface. Add the class to the Application.ApplicationLifetimeObjects collection for the Silverlight application.
3. How It Works
Silverlight 3 and later includes support for services that are created by the Application object and added to the ApplicationLifetimeObjects collection on the Application object. The services are created before the MainPage UserControl and can hook into various events associated with application lifetime.
A class that implements IApplicationService interface implements the following methods:
IApplicationService.StartService(ApplicationServiceContext context)
IApplicationService.StopService()
The StartService method fires before UserControl_Loaded to allow the application service to initialize itself. Likewise, StopService fires after the MainPage UserControl
is unloaded. This allows for service setup and teardown as necessary,
though you need to take extra steps to ensure setup completes (detailed
below in the code section).
Notice on the StartService method, there is a parameter passed in named context. The context parameter provides access to the initialization parameters via its ApplicationInitParams property that can be configured on an HTML <param> tag within the <object>
tag that creates the Silverlight plug-in. Developers can provide
information to the application service via the configured parameters on
the plug-in.
Implementing the IApplicationService
interface is the minimum requirement to create an application service.
For more fine-grained control or interaction between the service and the
application, developers can also implement the IApplicationLifetimeAware interface, which adds these additional methods to the application service:
IApplicationLifetimeAware.Exited()
IApplicationLifetimeAware.Exiting()
IApplicationLifetimeAware.Started()
IApplicationLifetimeAware.Starting()
The above events fire on the application service with respect to state of the application. For example, the Starting event fires before the Application_Startup event, while the Started event fires after the Application_Startup event. Likewise, the Exiting event fires before the Application_Exit event, while the Exited event fires after the Application_Exit event.
All of the IApplicationLifetimeAware interface events are bracketed by the two IApplicationService events, meaning that IApplicationService.StartService fires before IApplicationLifetimeAware.Starting and IApplicationService.StopService fires after IApplicationLifetimeAware.Exited event.
4. The Code
The example for this recipe
performs application functions related to configuration. The first
function stores a copy of plug-in initialization parameters as a public
property on the application service instance. The second function is a
service that retrieves an XML file from the server to obtain
configuration settings.
The application service is implemented in a code file named ConfigurationSettingsService.cs, which implements both the IApplicationService and IApplicationLifetimeAware interfaces. You modify the default Application_Startup event in the App.xaml.cs class file so that it handles initialization correctly.
You also modify the MainPage_Loaded event in MainPage.xaml.cs file so that it data binds to the ConfigSettings Dictionary object on the service. Finally, you add a TextBlock and ListBox to MainPage.xaml to display the configuration settings. Listings 1 through 4 show the contents of these files. Listing 5 shows App.xaml for the recipe, which is where the application service is declared.
Listing 1. Recipe 1's ConfigurationSettingsService.cs File
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Windows;
using System.Xml.Linq;
namespace Ch02_ProgrammingModel.Recipe2_12.Services
{
public class ConfigurationSettingsService : IApplicationService,
IApplicationLifetimeAware
{
//Event to allow the Application object know it is safe
//to create the MainPage UI
//i.e. the ConfigurationSettingsService is fully populated
public event EventHandler ConfigurationSettingsLoaded;
#region IApplicationService Members
void IApplicationService.StartService(ApplicationServiceContext context)
{
InitParams = context.ApplicationInitParams;
LoadConfigSettings();
}
private void LoadConfigSettings()
{
if (InitParams["configUrl"] != "")
{
WebClient wc = new WebClient();
wc.OpenReadCompleted += wc_OpenReadCompleted;
wc.OpenReadAsync(new Uri(InitParams["configUrl"]));
}
}
void IApplicationService.StopService()
{
}
#endregion
#region IApplicationLifetimeAware Members
public void Exited()
{
}
public void Exiting()
{
}
public void Started()
{
}
public void Starting()
{
}
#endregion
private void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error != null)
{
return;
}
using (Stream s = e.Result)
{
XDocument xDoc = XDocument.Load(s);
ConfigSettings =
(from setting in xDoc.Descendants("setting")
select setting).ToDictionary(n => n.Element("key").Value, n =>
n.Element("value").Value);
//Check to see if the event has any handler's attached
//Fire event if that is the case
if (ConfigurationSettingsLoaded != null)
ConfigurationSettingsLoaded(this, EventArgs.Empty);
}
}
//Store initialization parameters from <object> tag
public Dictionary<string, string> InitParams { get; set; }
//Stores configuraiton settings retrieved from web server
public Dictionary<string, string> ConfigSettings { get; set; }
}
}
|
Listing 2. Recipe 1's App.xaml.cs (partial) File
private void Application_Startup(object sender, StartupEventArgs e)
{
ConfigurationSettingsService service =
App.Current.ApplicationLifetimeObjects[0]
as ConfigurationSettingsService;
//Wire up an anonymouse event handler that is fired when the
//ConfigurationService is fully populated
//This ensures that we can access the ConfigSettings properties
//in MainPage_Loaded
service.ConfigurationSettingsLoaded +=
new EventHandler((s, args) =>
{
this.RootVisual = new MainPage();
});
}
|
Listing 3. Recipe 1's MainPage.xaml.cs (partial) File
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ConfigurationSettingsService service =
App.Current.ApplicationLifetimeObjects[0]
as ConfigurationSettingsService;
//Simple data bind to the ConfigSettings Dictionary
SettingsList.ItemsSource = service.ConfigSettings;
}
|
Listing 4. Recipe 1's MainPage.xaml (partial) File
<UserControl x:Class="Ch02_ProgrammingModel.Recipe2_16.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<StackPanel>
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Top"
Text="Configuration Settings" TextWrapping="Wrap" Margin="6"/>
<ListBox x:Name="SettingsList" Height="100" Margin="6,6,0,6"/>
</StackPanel>
</Grid>
</UserControl>
|
Listing 5. Recipe 1's App.xaml File
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Ch02_ProgrammingModel.Recipe2_12.App"
xmlns:MyServices="clr-namespace:
Ch02_ProgrammingModel.Recipe2_12.Services">
<Application.Resources>
</Application.Resources>
<Application.ApplicationLifetimeObjects>
<MyServices:ConfigurationSettingsService x:Name="ConfigService"/>
</Application.ApplicationLifetimeObjects>
</Application>
|
The URL used to retrieve
the configuration file is configured on the plug-in control via
initialization parameters so that it is not hard-coded into the
Silverlight application itself.
The MainPage_Loaded event on the UserControl fires after the IApplicationService.StartService event. However, in testing the application, the ConfigSettings collection was not populated in MainPage_Loaded
as expected. If you think about it, this makes sense, because you need
to make an asynchronous web request to retrieve settings from a URL. In
this example, the webClient.OpenReadCompleted event was firing after MainPage_Loaded executed, making it impossible to access configuration settings at load time within the application itself.
The application pattern you
utilize to maintain the proper event ordering is to add an event to the
application service class, which in this example is declared in ConfigurationSettingsService.cs like so:
public event EventHandler ConfigurationSettingsLoaded;
In the WebClient.OpenReadCompleted event handler that fires after the web request call succeeds, you fire the event as long as there is an event subscriber:
if (ConfigurationSettingsLoaded != null)
ConfigurationSettingsLoaded(this, EventArgs.Empty);
In this application pattern, there an event subscriber created as shown in Listing 2-23. It is an anonymous event handler show here:
service.ConfigurationSettingsLoaded += new EventHandler((s, args) =>
{
this.RootVisual = new MainPage();
});
Using this pattern ensures that the application service is fully configured before the MainPage is instantiated, permitting the application to function as expected. Figure 1 shows the settings displayed in the basic UI.