Thursday, July 1, 2010

Customer IApplicationService for Silverlight Applications

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.

Jeremy Likness

8 comments:

  1. I didn't realize that silverlight has those features. Is it possible to access objects in ApplicationLifetimeObjects tag? would be very helpful if you can. Anyways thanks for the great post :)

    ReplyDelete
  2. Yes, you can ... but it is indexed as a collection, so a more common practice is to either export them using MEF or have a singleton property pointing to the initialized instance.

    ReplyDelete
  3. Hi,

    I'vew written about AES in an earlier post ( http://ajdotnet.wordpress.com/2009/11/08/silverlight-bitspieces-part-7-application-permissions/ ) to implement application permissions. Afterwards I went ahead and turned AES into more general service providers (see http://ajdotnet.wordpress.com/2009/11/14/silverlight-bitspieces-part-8-application-extensions/ and http://ajdotnet.wordpress.com/2009/11/28/silverlight-bitspieces-part-9-a-messagebox-replacement/ ).

    I'd be interested to hear what you think about the service provider approach.

    Regards,
    AJ.NET

    ReplyDelete
  4. Hi,
    I am looking for an elegent solution to the old "no session end" event firing when someone closes the web browser (silverlight app).

    Using your excellent post, I created a SessionManager service that is registered as you showed in the App.xaml.

    In the Starting() method, I call a DomainService invoke operation which sets an increment in Global.asax, and in the Ending() method I call DomainService invoke operation to decrement a counter in Global.asax.

    The DomainService Invoke is is called correctly on Startup(), but the DomainService Invoke to decrement is never called from the Exiting() method.

    Stepping through inside VS2010, it does go into the Exiting() method, but never actually invokes the DomainService call to decrement.

    Am I missing something about the LifeTimeService class with regards to invoking DomainService calls?
    i.e. is it not possible to make a Domain Service call from the Exiting() event?

    Many thanks,
    Chris

    ReplyDelete
  5. Take a look here, let me know if that addresses your issue:

    http://www.codeproject.com/Articles/43157/Calling-Web-Services-from-Silverlight-after-the-Br.aspx

    ReplyDelete
  6. Thank you for the fast response Jeremy.

    I was hoping that Microsoft had provided a more native silverlight client-side solution by now, instead of using Javascript - seems they haven't yet :)

    I have successfully implemented the solution you linked above, however I will point out that anyone trying to make it work for Silverlight 4/Framework 4 will need to install the latest AJAX Control Toolkit for .Net 4.

    I had some horrible errors and lots of google hunting for these errors until I discovered what the problem was (asp:ScriptManager was the problem)

    At the time of writing, the current toolkit can be found at
    http://ajaxcontroltoolkit.codeplex.com/releases/view/63654#DownloadId=223346


    Thanks again for your help!

    ReplyDelete
  7. Thanks great post! I have a SL application that makes call to a REST service on Exit I need to call my clean up that makes a call to the REST service to clean up some resources. I am using the HttpWebRequest class and when I try to get the stream from EndGetRequestStream I am getting a thread abort exception. Any Ideas on getting around this without using javascript.

    ReplyDelete
  8. Hi alan,
    I think your only chance is to do final cleanup using the Javascript method described in Jemery's link. It seems like the only reliable way to handle cleanup tasks on browser close.

    The Javascript code is only to invoke the cleanup web service, so it's not too bad (i.e. you don't have to write the cleanup function itself in Javascript).

    Good luck with it.
    Chris

    ReplyDelete