Another feature of Silverlight 3 that has flown under the radar since the product's release is application extension services, or application services for short. Application services are client-side services that start when the application starts and end when the application ends. In other words, their lifetime parallels that of the application itself. An application service is an excellent deployment vehicle for features that in Silverlight 2 might have been added to the Application-derived class in App.xaml.cs.
Writing an application service is a simple matter of building a class that implements Silverlight's IApplicationService interface. That interface has just two methods: StartService and StopService, which are called when the service starts and when it ends. In practice, StartService is called immediately before the Application.Startup event fires, and StopService is called right after the Application.Exit event fires. A skeletal service implementation appears below. Note the static property named Current, which provides a simple means for application code to acquire a reference to a running service instance:
public class SimpleService : IApplicationService
{
private static SimpleService _current = null;
public static SimpleService Current
{
get { return _current; }
}
// TODO: Implement other class members here
public void StartService(ApplicationServiceContext context)
{
_current = this;
}
public void StopService()
{
_current = null;
}
}
Although not shown in the example above, a service can optionally implement the IApplicationLifetimeAware interface, too. This interface adds four methods named Starting, Started, Exiting, and Exited. Calls to these methods bracket calls to StartService and StopService and provide an application service with additional opportunities to perform any necessary initialization or cleanup chores.
Application services are loaded by declaring instances of them in App.xaml:
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SimpleServiceDemo.App"
xmlns:local="clr-namespace:SimpleServiceDemo"
>
<Application.Resources>
</Application.Resources>
<Application.ApplicationLifetimeObjects>
<local:SimpleService />
</Application.ApplicationLifetimeObjects>
</Application>
ApplicationLifetimeObjects is a new property added to the Application class in Silverlight 3. You can use it to register application services as shown above, and also to enumerate loaded services at run-time.
So, the big question: why would you ever write one of these things? To demonstrate, I built an application service named AssemblyLoaderService that provides a useful service to Silverlight applications that rely on external assemblies: asynchronous assembly loading. Rather than swell the size of your XAP file by packing it with extra assemblies, you can register those assemblies with the assembly loader service when the application starts up and allow the service to download them in the background and load them into the app domain. It's an easy way to deploy applications that utilize multiple assemblies without increasing the size of the XAP and delaying the startup of the entire application. Done properly, it also allows you to avoid jumping through some of the hoops I described in an earlier blog post on dynamic assembly loading in Silverlight.
You can download the source code for AssemblyLoaderService (and a sample app that uses it) from Wintellect's Web site. When you run the application, you'll initially see a blank page. After a short time (very short since you're running against a local Web server), a button will appear. Clicking the button removes the button from the page and replaces it with a dynamically created Calendar control.
It's what happens on the inside that's interesting. The Calendar class lives in System.Windows.Controls.dll, which isn't part of the core Silverlight run-time. To create a Calendar control, you must include a reference to System.Windows.Controls.dll in your project. Normally, that increases the size of your XAP file. Here it doesn't, because after adding the assembly reference to the project, I changed the assembly's Copy Local setting from true to false. This satisfies the compiler but prevents the assembly from being packaged in the XAP file.
The following statements in MainPage.xaml.cs initiate an asynchronous download of System.Windows.Controls.dll from the server's ClientBin folder:
AssemblyLoaderService als = AssemblyLoaderService.Current;
if (als != null) // Just to be sure
{
als.AssemblyLoaded +=
new EventHandler<AssemblyLoadedEventArgs>(OnAssemblyLoaded);
als.AssemblyLoadedError +=
new EventHandler<AssemblyLoadedErrorEventArgs>(OnAssemblyLoadedError);
als.LoadAssemblyAsync(new Uri("System.Windows.Controls.dll", UriKind.Relative));
}
Before calling the service's LoadAssemblyAsync method to start the download, MainPage registers handlers for the service's AssemblyLoaded and AssemblyLoadedError events, which notify interested parties when an assembly is successfully loaded, or when an error prevents an assembly from being loaded. MainPage's AssemblyLoaded event handler displays the "Create Calendar Control" button to the user, so there's no chance that the application will attempt to create a Calendar control before the required assembly has been loaded into the app domain.
The service is implemented in the AssemblyLoaderService class. Look through the source code and you'll see that it sometimes uses a DispatcherSynchronizationContext to marshal to the application's UI thread. That's because AssemblyPart.Load throws an exception if called from a background thread, and also because I wanted all events fired by the service to execute on the UI thread. I'll describe all this and more next week in Berlin when I do my "Biggest Little-Known Features in Silverlight" talk at TechEd, so if you're attending the conference, please come by and join the fun!
On Nov 6 2009 4:04 AMBy jprosise
This post was mentioned on Twitter by jeremylikness: Jeff Prosise: Silverlight 3's new Application Extension Services #silverlight #sl3 http://tinyurl.com/ydfudwc
So what kind of Services are permitted as far as trust goes? Are SQL Compact or SQLite scenarios permittied (maybe csharp-sqllite)?
An application service runs in the same sandbox as apps do.
Jeff,
Thanks for shining a light on this hidden feature. I wasn't aware of it.
These services look like Singletons to me. How do they differ from Singletons? And what benefit do you get over the classic Singleton pattern?
Thanks,
Brian
Great post Jeff,
I wasn't aware of this. I've done this with Singletons, just like Brian, however especially in combination with IApplicationLifetimeAware this could prove valuable in scenario's I wasn't able to solve correctly in Silverlight earlier.
Keep up the good work.
Jonathan
Wonderful post, Jeff! I always look forward to your posts. I really like the simplicity of this dynamic assembly loader.
There is one thing that has been puzzling me for sometime. When I set a breakpoint in your AssemblyLoaderService.cs the breakpoints don't get hit. This happens on other apps too. But, if I switch the "startup project" from the web app to the SL app then the breakpoints "are" hit. Unfortunately, it won't run that way. Do you have any idea why the breakpoints don't get hit???
Best regards,
Chris
Hi,
I used your 'dynamic assembly loading' bits but i tried to check whether it was possible to load multiple versions of the same assembly in the app domain and after strong signing a sample assembly and having made 2 versions (1.0.0.0 and 1.0.0.1) here is what I observe:
a) if i copy the 1.0.0.0 assembly in the web app ClientBin folder and load the assembly, it works. If i replace the assembly in the ClientBin folder (i do not close the application browser) with the 1.0.0.1 version, the line part.Load(e.Result) returns the assembly 1.0.0.0.
b) if i copy the 1.0.0.1 assembly in the web app ClientBin folder and load the assembly, it works. If i replace the assembly in the ClientBin folder (i do not close the application browser) with the 1.0.0.0 version, the line part.Load(e.Result) returns null.
I was just trying to figure out how to dynamically reload an assembly or update an assembly that might have already been loaded in the app domain. I understand that we can just wait for a client session to end and have new clients reload the new assembly of an assembly needs to be updated/corrected.
I am interested in trying to re-use this 'push' technology to update dlls on a fat client application.
Let me know if i am missing somehting or if this is the right approach.
Thank you.