In my experience working with Silverlight applications, probably one of the most underused features I've witnessed is the ability to abstract application-level functionality using the IApplicationService
and IApplicationLifetimeAware
interfaces. This, in turn, results in the over-use (and abuse) of the Startup
and Exit
events in the Application
object. Before you get angry with me shaking a finger, I'll admit I've done this quite a bit myself.
IApplicationService
The IApplicationService
interface allows you to define a class that is part of the overall application lifecycle. It provides a mechanism to create a service that lasts the duration of a Silverlight application. It is called once when the application starts with an ApplicationServiceContext
, and called again when the application ends.
One of the most useful things you can do with this service class is use it to intelligently parse parameters passed to the Silverlight application. How many times have you found yourself hooking into the application startup and then parsing these out? What's worse, in larger, complex applications, many different portions of the application share responsibility for handling these parameters. It breaks the concept of loose coupling to have a single front-end parsing it out and then stuffing it into an object to dole it out to the consumers that need it.
By implementing IApplicationService
, you can create a service for your functionality that handles its own initialization parameters. There is no "central" place that has to understand everything. Silverlight supports using multiple application services, so you can have a logger service that looks at the logging parameter and a file upload service that looks at the file storage location parameter, so on and so forth.
Here's what a service that handles writing to the debugger might look like:
public class LoggerService : IApplicationService { const string TRACE_LEVEL_KEY = "TraceLevel"; public LoggerService() { _traceLevel = TraceLevel.Warning; // default } private TraceLevel _traceLevel; public ILogger Logger { get; private set; } public static LoggerService Current { get; private set; } public void StartService(ApplicationServiceContext context) { Current = this; if (context.ApplicationInitParams.ContainsKey(TRACE_LEVEL_KEY)) { _traceLevel = (TraceLevel)Enum.Parse(typeof (TraceLevel), context.ApplicationInitParams[TRACE_LEVEL_KEY], true); } Logger = new CustomLogger(TraceLevel); Logger.WriteLine(TraceLevel.Information, "Logger service started."); } public void StopService() { Logger.WriteLine(TraceLevel.Information, "Logger service stopped."); } }
This service now allows me to configure the logging level in the web page using initParams
, like this:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%"> <param name="source" value="ClientBin/MySilverlightApp.xap"/> <param name="onError" value="onSilverlightError" /> <param name="initParams" value="TraceLevel=Warning" /> <param name="background" value="white" /> <param name="minRuntimeVersion" value="4.0.50401.0" /> <param name="autoUpgrade" value="true" /> </object>
What's more, if it is in a project by itself, adding the service to a new application is so simple it's hard to believe more people don't take advantage of this. After referencing the project, I won't have to mangle my App.xaml.cs
and hook into any events in order to use the new service, parse out parameters, etc. Nope, I just add a simple reference to it in the App.xaml
that looks like this:
<Application.ApplicationLifetimeObjects> <MySilverlightApp:LoggerService/> </Application.ApplicationLifetimeObjects>
IApplicationLifetimeAware
Now, if you really want to do some cool things, you can extend the class even further by implementing IApplicationLifetimeAware
. This will give you four additional methods: Starting
, Started
, Exiting
, and Exiting
.
Use these to perform set up and clean up actions.
For example, in the Sterling Windows Phone 7 Example I wrote (Sterling is an open source object-oriented database for Silverlight 4.0 and Windows Phone 7) I created a database service to make it easy to set up and tear down the database engine. The service looks like this:
public class DatabaseService : IApplicationService, IApplicationLifetimeAware { public static DatabaseService Current { get; private set; } public ISterlingDatabaseInstance Database { get; private set; } private SterlingEngine _engine; public void StartService(ApplicationServiceContext context) { Current = this; _engine = new SterlingEngine(); } public void StopService() { _engine = null; } public void Starting() { _engine.Activate(); Database = _engine.SterlingDatabase.RegisterDatabase<PhoneDatabase>(); } public void Started() { return; } public void Exiting() { _engine.Dispose(); } public void Exited() { return; } }
As you can see, I am managing everything that would normally get hooked into the application startup and exit events. Instead of adding to that code-behind, however, I can abstract these hooks in a re-usable component that is easily dropped into or out of the App.xaml
as needed. Just like the previous example, the only thing needed to hook into the database in a Windows Phone 7 application is now just this:
<Application.ApplicationLifetimeObjects> <SterlingExample:SterlingService/> </Application.ApplicationLifetimeObjects>
The Silverlight framework guarantees my service is called to start only once, so I take advantage of that call to assign a static property. That way, anywhere in the application I need to reference the service, I can use ServiceName.Current (for example, SterlingService.Current).
Don't limit yourself to just that, however. One common task in composite Silverlight applications is "bootstrapping" objects. If you are using the Managed Extensibility Framework (MEF), you may need to configure some catalogs and then call the CompositionInitializer
. You can do this in an application service as well. In fact, many of my applications have nothing in the App.xaml.cs
code behind except a call to InitializeComponent
. Why? Because I can set the root visual in a service, like this:
[Import] public Shell MainShell { get; set; } public void StartService(ApplicationServiceContext context) { _myCatalog = new AggregateCatalog(new DeploymentCatalog()); var container = new CompositionContainer(_mainCatalog); CompositionHost.Initialize(container); CompositionInitializer.SatisfyImports(this); } public void Starting() { Application.Current.RootVisual = MainShell; }
When called, the service sets up the catalogs/containers/etc. and satisfies imports to generate the shell. If the shell has any dependencies, these are also handled by MEF. Then, in the starting method, we wire the shell to the root visual because this is called just before the full visual tree is loaded.
I hope to see this powerful feature used more often to add some quality customer service to Silverlight applications.