|
Embedding Silverlight in a Windows Service
|
|
Thu Jan 7th 2010
|
by
Brett
|
|
|
|
|
I had an interesting idea a few weeks ago. Wouldn’t it be interesting to see if I could host a Silverlight application inside a WCF service. The reason the idea came to mind was that my company has an application that has three components – a windows service (that hosts several WCF services), and WPF Management Application, and a Windows Forms application. In an effort to reduce the installation steps I thought it would be nice if instead of having to setup the management application on administrator’s desktops – it would be much easier to exercise Silverlight’s local install option. The whole upgrade process would also be made easier from that point forward because updates to the server would then go hand in hand with the management application.
Obviously – this would be easy to do in IIS, but from my application’s perspective I didn’t want to put a dependency on IIS because of the complexity of configuration. Few things are more time consuming than walking a customer through IIS configuration over an email conversation when they haven’t had any experience with IIS. I wanted a drop dead simple installation experience for my end users. In my case – they run setup.exe, and at the end of the process it opens a browser to a page running my Silverlight management application.
Fig 1. Old and new architectures
Now initially I must confess I didn’t think to host the Silverlight application inside WCF – instead I started looking at a variety of webservers that could run in a .NET process such as Cassini. I ruled them out for a number of reasons including:
- My WCF Service was already running on port 80 and I couldn’t find an easy way to port share between with an embedded web server
.
- I considered running Cassini – and then using that to host WCF services, but this isn’t listed as a supported way for hosting WCF and I really didn’t want to go that far out of band
.
One of the nice features built into the WCF infrastructure is Url-rewriting. This allows you take a particular WebMethod, say GetPage.asmx?pageName=x and re-write it to /x where x is the name of the page you wish to display. The method returns a stream, which when invoked by the web-browser returns some HTML.
Storage of web content:
Obviously I didn’t want HTML stored in-line with my C# code (for about 1000 reasons), so in my case I decided to embed the necessary files inside my assembly. I could have stuck them in the file-system somewhere, but I liked the fact that embedding them would make it impossible for people to tamper with them (ok – not impossible – but difficult). I created folders (Images and ClientBin) in my project and added the necessary images and xap files here. (Note – you must mark them as embedded resources or they will not get compiled into the assembly)
In the actual implementation class, WebServer.cs, files are extracted from the assembly and returned as a Stream.
//The service contract for the WebServer - Url re-writing is used to simulate
//accessing file resources on a normal web server
[ServiceContract]
public interface IWebServer
{
[OperationContract, WebGet(UriTemplate = "/")]
Stream Default();
[OperationContract, WebGet(UriTemplate = "/Images/{imagename}")]
Stream GetImage(string imageName);
[OperationContract, WebGet(UriTemplate = "/ClientBin/{xapname}")]
Stream GetXap(string xapName);
}
//WebServer extracts resources out of the Assembly and serves them up via a Stream
//Note: production code should have improved exception handling in cases where resources cannot be found
public class WebServer : IWebServer
{
public const string AssemblyRootPath = "ServiceHostedSilverlight.";
public Stream Default()
{
WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
return Assembly.GetExecutingAssembly().GetManifestResourceStream(AssemblyRootPath + "Default.htm");
}
public Stream GetXap(string xapFile)
{
WebOperationContext.Current.OutgoingResponse.ContentType = "application/x-silverlight-2";
return Assembly.GetExecutingAssembly().GetManifestResourceStream(AssemblyRootPath + "ClientBin." + xapFile);
}
public Stream GetImage(string imageName)
{
string ext = new FileInfo(imageName).Extension.ToLower();
WebOperationContext.Current.OutgoingResponse.ContentType = "image/" + ext;
return Assembly.GetExecutingAssembly().GetManifestResourceStream(AssemblyRootPath + "Images." + imageName);
}
}
In addition to the actual code there are a few critical pieces to the configuration of the WCF host that we need to look at in our app.config. Without WCF configured as shown below this will not work. This took quite a bit of stumbling to figure out the right mix of settings.
<behaviors>
<endpointBehaviors>
<behavior name="ServiceHostedSilverlight.WebServerEndpointBehavior">
<webHttp /> <!--Necessary for Http/REST style services-->
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="ServiceHostedSilverlight.WebServerBehavior">
<serviceMetadata httpGetEnabled="false" /> <!--must be false or the Url re-writing will not work-->
<serviceDebug httpHelpPageEnabled="false" includeExceptionDetailInFaults="true" />
<!-- must disable the help page or the default re-written url (/) will not work -->
</behavior>
<behavior name="ServiceHostedSilverlight.DataServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
All is well and good – ok – not quite. While this worked fine in IE (seems to look at the extension name as a last resort for determining the content-type of the page), Chrome and Firefox just gave me a prompt to open a file using a local application.
Some more digging revealed that you can set the content type within a web method. I set mine appropriately based on the type of content (see WebServer.cs above) and things were working in all my target browsers. The next issue I wanted to tackle was the default landing page if someone just went to http://myinternalserver . WCF lets you define parameter-less methods and I was able to do this so long as I didn’t have any other methods sharing the / url. This wasn't the case initially as by default WCF contains a help page that grabs the root url. After trying to figure out how to override the help page I realized I could just disable it, thereby enabling usage of root.
Another great tip I learned here is that if you place the resources in the same path as your re-written WCF method Url then the Visual Studio designer will work just fine because from the perspective of the HTML the resources will be in the same place.
Silverlight – ok finally to the Silverlight – hosting Silverlight is just like hosting any other content type except when you stream the Silverlight XAP you need to set the content-type to application/x-silverlight-2. Your Silverlight application then communicate with the other WCF services to acquire data or do whatever it needs to do.
Fig 2. Resultant web page
Obviously this little web server has its limitations – it doesn’t have a dynamic engine (e.g. – no ASP.NET). These limitations are largely mitigated by its pairing with Silverlight since the UI is handled client side. I like how lean the implementation is and I can think of a ton of cases where this is useful:
- A service that syncs data between two enterprise applications – the UI here would be minimal – but a robust log viewer could be presented through Silverlight.
- The Admin UI for a network service such as a Firewall or Spam filter.
- Pure configuration dialog instead of directing users to modify app.config settings. Your UI could give you validation and the ability to restart the service after a setting change if necessary.
- A commercial application you wish to package for quick and easy setup and administration.
This has become my go-to-method for service administrative UI’s. It lets me offer my customers a very rich UI experience without having to “mess with IIS”.
Code: (download here)
You will note that the sample project I have included hosts the WCF Services inside a console application instead of a Windows Service. Since I didn't want to make readers register a Windows service just to test the code I thought this was a better approach for demonstration purposes. Obviously hosting WCF (instead of a console app) is a well documented and simple task. You will also note that the solution builds the Silverlight App first and then automatically copies the XAP file to the ClientBin folder on the ServiceHostedSilverlight project - that ensures it has the latest version when it is compiled. Also - when you add/update service references to Silverlight you need to make sure that the console is up and running - since you cannot do this while in debugger - manually start the console app and then add your service reference in VS.
|
|
|
3 Comments
This is awesome. I have been working on an application that runs as a service and handles grabbing updates from another service and applying them to a local database. Presently I show a history of the updates by writing to a log file, but it would be much slicker to have a built in Silverlight UI for showing the information to the admins who depend on the service.
Very Nice Idea and Thx for sharing the write up and Code.
This will definitely catch up.
This is a very good work! I try to do the same without using WCF service, and it work fine, but i find that you have the same problem that I find using silverlight OOB update process. If you try to implement the update process for Out Of Browser application (using CheckDownloadUpdateAsync() method), when you have installed the application on desktop, it seems to make the download of the application everytime you launch the application, even if the application version is the same. Do you have any idea about it?
Login to post comments
|
|
|
|