MULTIMEDIA

Silverlight Recipes : Creating Application Services

6/25/2012 5:39:52 PM

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.

Figure 1. Recipe 1 The Configuration Settings UI
Other  
  •  Silverlight Recipes : Creating Silverlight Using Ruby, Python, or JScript
  •  Olympus Launches OM-D E-M5
  •  Nikon Launched The D800 And D800E
  •  Hands-On Preview: Pentax K-01- Stylish new compact system camera from Pentax
  •  Birds Of Prey (Part 3) - Behind the scenes
  •  Birds Of Prey (Part 2) - Owl in motion, Prey, Little owl, Expression, Goshawk, Perch
  •  Birds Of Prey (Part 1) - Flight, Gaze, Eagle silhouette
  •  Serif PagePlus X6
  •  Oloneo HDRengine
  •  Magix Music Maker MX
  •  Arcsoft Perfect365
  •  Displacement Maps
  •  Astrophotography Alternative : Canon EOS 60Da, Lumix GF5
  •  CyberLink PowerDirector 10 Ultra
  •  Viewsonic VP2365-LED – Go LED, Go Green
  •  Philips Brilliance 241P4QPYES – Not a cheap TN
  •  Corsair Vengeance 1300 Analog Gaming Headset
  •  Where Is My Hard Drive Capacity? (Part 2) - Beat the browser
  •  Where Is My Hard Drive Capacity? (Part 1) - ITunes culprit
  •  Viewsonic V3D231
  •  
    Video
    Top 10
    Tim Cook: “Of course, I'm going to change things.”
    All About Solid-State Drives (SSD)
    Samsung 830
    OCZ Agility 3
    Kingston HyperX 3K
    Crucial M4
    Corsair Nova 2
    BlackBerry Java Application Development : Networking - Debugging with the MDS simulator
    BlackBerry Java Application Development : Networking - The transport-so many possibilities
    IIS 7.0 : Managing Administration Extensions
    Most View
    Programming the Mobile Web : Widgets and Offline Webapps - Platforms (part 3) - webOS & Android
    Fallen IT Giants
    Programming the Mobile Web : Geolocation and Maps - Showing a Map
    The Benefits of Facebook Marketing
    Windows Home Server Installation and Configuration
    SQL Server 2008 : Transact-SQL Programming - The max Specifier
    Microsoft XNA Game Studio 3.0 : Getting Player Input - Adding Vibration
    Safeguarding Confidential Data in SharePoint 2010 : Examining Supported Topologies
    Registry in Windows Vista
    Silverlight Recipes : Managing Embedded Resources
    SQL Server 2008 : Working with Multiple-Source Queries - OpenQuery, OpenRowSet, and OpenDataSource Explained
    Microsoft XNA Game Studio 3.0 : Displaying Images - Using Resources in a Game (part 2) - Positioning Your Game Sprite on the Screen
    The tiny miracle of microSD cards (Part 2)
    Windows 7 : Windows Driver Foundation Architecture (part 1)
    iPhone 3D Programming : Blending and Augmented Reality - Rendering Anti-Aliased Lines with Textures
    Macs no longer safe from virus attacks
    SQL Server 2008 : T-SQL Tips and Tricks (part 2) - Using CONTEXT_INFO & Working with Outer Joins
    iPhone Application Development : Creating User Interfaces
    Create virtual desktop with nSpaces (Part 1)
    Buying Guide: CPU Cooling Equipment (Part 5) - Antec KUHLER H2O 620,Arctic Cooling Freezer i30,Cooler Master Hyper 612 PWM