Sunday 6 March 2011

How to host multiple isolated WCF services within a single Windows service with zero App.config

Note: The code featured in this blog posting was put together whilst working with David Marsh on the Tranquility.Net (Wcf App Server), an open source .Net app server available on CodePlex.

CodePlex project: http://tranquilitydotnet.codeplex.com/

The source code for this blog entry can be found on the CodePlex project (using the link above), click on the Source Code tab and download changeset 2671 (Initial commit of source).

Ok, hands up who loves IIS? Nope, me neither. Tired of it hogging RAM and eating up your server resources when hosting your lightweight Wcf services? Why not host all your services within a single Windows service on your server. But app pooling in IIS is pretty cool right, we can’t do without that – no problem, we’ll just wrap each of our services within it’s own AppDomain so we can recycle as required.

For an added bonus I’ve removed the Wcf configuration aka the A, B, C’s (Address, Binding and Contract’s) from the app.Config so you can dynamically load them at runtime. This means we could retrieve this information from a central configuration service or from a database, etc,

Ooh that all makes sense but it sounds hard? Nope, it’s easy.

Overview of Classes

Overview-of-classes

The Classes Explained

Program.cs:

The main entry point of the program, on startup of the Windows the service this class just creates and runs a ServiceContainer.

ServiceContainer.cs, ConfigService.cs, WcfServiceConfig.cs and WcfService.cs:

ServiceContainer-and-Config-class

When the ServiceContainer starts, it makes a call to the ConfigService to get the Wcf service information (assembly, service and contract names) along with the endpoint Uri address e.g net.tcp://localhost:8323/WcfServiceLibrary1/Service1. From this information the WcfAppServer can create the A. B, C for the Wcf configuration. The address is provided, the binding can be inferred from the start of the address and the contract and service types are read using reflection on the assembly.

Note: The service DLLs do not need to be referenced (within Visual Studio) by the WcfAppServer but the files will need to be placed in the same folder as the WcfAppServer.exe. This allows reflection to retrieve all required type information.

You can replace the ConfigService code for a call to your own config service or database. For this demonstration the ConfigService just returns hard coded information (see further down for code).

Once the ServiceContainer has its list of WcfServiceConfigs, it loops through each item first creating, then opening an IsolatedServiceHost for each service. A list of IsolatedServiceHosts is stored by the ServiceContainer.

ServiceContainer_OnStart

IsolatedServiceHost.cs:

This class isolates each service host by creating a ServiceHostWrapper object within a new AppDomain. This ensures one service faulting will not affect any of the other services.

IsolatedServiceHost

ServiceHostWrapper.cs:

This class simply serves as a wrapper around the generic .Net ServiceHost with the added bonus of being derived from MarshalByRefObject which allows for cross AppDomain communication. This enables our service container to send commands to our ServiceHost like Open, Close and Abort – which is pretty sweet! A couple of methods to easily setup the WCF config have also been included.

ServiceHostWrapper

WcfServiceInstaller.cs:

installer

This code allows the Windows Service to be installed from either a setup deployment project (.msi) or by using the InstallUtil command. For this example we’ll be using the InstallUtil command. The AfterInstall event will startup the service on our behalf service.

WcfHelper.cs:

WcfHelper

This class infers and creates the binding from the start of an endpoint address. For example:

net.tcp://localhost:7834/Assembly/Service requires the net tcp binding

http://localhost:7834/Assembly/Service requires the HTTP binding

Right, it’s….. SHOWTIME!

Ok, so you ready for some code? Here’s how it’s done.

Note: This code was created using Visual Studio 2010 Ultimate edition.

Create a Windows Service from Visual Studio, called “WcfAppServer”:

Create-windows-service

Once project has been created, remove the default Service1.cs file from your project.

As we’ll be dealing with WCF services, we’ll want to a reference to the System.ServiceModel and System.ServiceProcess frameworks. Just right click on References and select Add Reference…, then scroll down and double click on System.ServiceModel and System.ServiceProcess.

A third reference System.Configuration.Install is also required by the service installer class.

Add-reference

In order to be able to build the project whilst we add each file, just to make sure we haven’t entered a typeo, you’ll want to comment out the reference to recently removed Service1 class.

Program.cs

Next up we’ll need to add our files, you can cut and paste from the code below or download these files from the source code links at the bottom of this blog.


WcfHelper.cs


ServiceHostWrapper.cs


IsolatedServiceHost.cs


WcfService.cs

WcfServiceConfig.cs

ConfigService.cs


ServiceContainer.cs


So we can install our Windows Service we’ll need to add an installer class.


WcfAppServerInstaller.cs


Finally, update the Program.cs file to instantiate our ServiceContainer class on start up:


We now need to add our two Wcf Service Libraries to the project which will act as sample libraries for our demo. From the Visual Studio main menu select File > Add > Add New Project, and then select WCF > WCF Service Library, accepting the default name and location for the project.


Add-new-wcf-project-1


Repeat the process again to add a second WcfServiceLibrary, this time with the default name WcfServiceLibrary2.


To ensure each Wcf Service Library returns a unique message (so we can tell them apart during the demo), update each service to return a relevant message.


WcfServiceLibrary1.Service1.cs


And repeat for WcfServiceLibrary2.Service1.cs.


Now the next bit is optional and I’ll explain why. The WcfAppServer can load and host any Wcf Service classes from a .Net assembly. All you need to do is drop the dll into the same folder as the WcfAppServer.exe and using reflection, the service will load them from our “config service”.


For this demo I’m going to include the project references to save having to build and copy across the DLL files manually. To add the references to our WcfAppServer project right click on the WcfAppServer project > Add Reference…, then select Projects and double click on each of our new projects:


Add-project-reference


So with a quick “CTRL + SHIFT + B” to build the project in debug mode, we are now ready to install our new Windows Service. Just run the following commands from the Visual Studio Command Prompt:


install-service


Note: The Visual Studio Command Prompt can be found in the Visual Studio tools shortcut folder (Start > Programs > Microsoft Visual Studio 2010 > Visual Studio Tools > Visual Studio Command Prompt (2010)).


Now we can start up our Wcf App Server Windows Service and test our hosting.


Open the Services mmc management window (Start > Control Panel > Administration Tools > Services)


Start-Service


Now our Windows Service is up and running we can test them using the WcfTestClient. To add this Tool to Visual Studio, from the main menu in Visual Studio select Tools > External Tools, then enter the following details:


WcfTestClient


The WcfTestClient.exe can be found within C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\ folder.


Note: This tool is installed relative to your Visual Studio installation folder (64 bit machines will be different from the above address).


You can now run the tool from within Visual Studio. From the main menu Tools > WcfTestClient


You can now add each of our services to the test client by right clicking on My Service Projects > Add Service…


add-service


Once you’ve added both the Wcf endpoints you’re ready to start test driving your services!


Testing WcfServiceLibrary1


testing-nettcp-1


Testing WcfServiceLibrary2


testing-nettcp-2

27 comments:

  1. I started to spec out something like this right before I started googling how it might work. I was looking for something that would be a little more generic than strict WCF hosting -- instead using the standard app.config files by default. I love the idea that you proposed, however, to use a database back-end for the config. I'm not sure I can migrate to that right away though. I may use your starting point and see how I can work back to something more generic, maybe with a structure like this that is auto-loaded based on filesystemwatcher:
    AppHosterSvc
    AppHosterSvc\Apps
    AppHosterSvc\Apps\Test1
    AppHosterSvc\Apps\Test1\app.config
    AppHosterSvc\Apps\Test1\bin\*.dll
    AppHosterSvc\Apps\Test2
    AppHosterSvc\Apps\Test2\app.config
    AppHosterSvc\Apps\Test2\bin\*.dll

    ReplyDelete
  2. Hi jlb0001, I've updated the tranquility to support SQLite embedded database for its config service. Check out the CodePlex link for the latest source code:

    http://tranquilitydotnet.codeplex.com/

    ReplyDelete
  3. Hi Steve, I will be working with one of our devs soon to use your codeplex project as a starting point. We'll try adding the auto-loading directory structure proposed in the first comment above. Maybe we'll have something share-worthy that can be contributed back?

    ReplyDelete
  4. Hi jlb0001

    Sorry for the delay I was climbing mountains in Nepal for a month or so and I managed to miss your comment on my return.

    Contributions are always welcome! Let me know how you get on with your project. I did initially use MEF as the plugin loader but wasn't keen to impose attributes on the services. I'm sure there's a cleaner more generic loading solution available.

    Give me a shout if you want to chat at any point, the best email to get me on is stevenhollidge@hotmail.com

    Steve

    PS
    If you're ever interested in MEF I've written a blog post on the basics here: http://stevenhollidge.blogspot.com/2011/04/how-to-use-mef-with-c.html

    ReplyDelete
  5. jlb0001, the current version on CodePlex uses the directory and file name supplied by the config service to copy the DLL service libraries files locally to the app server before hosting the services. This seemed the simplest solution.

    ReplyDelete
  6. Hi Steve,

    First off, thanks for this great post! I have been looking into your code as we are considering using it as a starting point for our service implementations. I have found that when closing and recreating services memory is not reclaimed.

    After some further investigation I came across some articles on AppDomain Unloading. I may have a solution for this. You can use service host AppDomain tracking in the WcfServiceHostFactory and after calling dispose on appServiceHost on AppServer, lookup and unload the service hosts appdomain which seems to solve the problem.

    I've also implemented a LocalDBConfigService class featuring the upcoming SQL server 2012 Local DB feature as a configuration database backend.

    Let me know if you want me to send you the code.

    Thanks again for this great article!

    Ioannis

    PS: In the readme you may want to include that NetTcpPortSharing must be enabled.

    ReplyDelete
    Replies
    1. Hi Ioannis,

      I am also running into the problem that memory is not reclaimed after recreating the services.
      Could you maybe show me some code how you solved it ?

      Geert

      Delete
    2. Ioannis, I'm sorry I missed your comment in February. I was away and must have missed the email but yes I'd love to include your LocalDBConfigService class in the project.

      I can either add you as a member of the CodePlex team for the project or you can email me the code, let me know which you'd prefer.

      I'm always happy to accept new members to the team if anyone would like to add their code! :)

      Delete
  7. Hi Geert, could you put together an example and send me a link to download your code and I'll take a look. Thanks Steve

    ReplyDelete
    Replies
    1. Hi Steve,

      In my code I am doing a simple StopServer() and StartServer() from a timer to pick up new library versions. Eventually the server the service is running on is running out of memory.
      So I think it has something to do with the comment of Ioannis about unloading the service hosts appdomain ?
      Or am I doing something wrong ?

      I also tested recycling a service from the Admin Client.
      I see the memory use go up every time I recycle a service from the adminclient.

      Maybe you have another solution ?

      Thanks, Geert

      Delete
  8. Geert, I've updated the source code on CodePlex to unload the AppDomain. Once the garbage collector kicks in the memory is now reclaimed. Could you try change set 15379 and let me know how you get on? Thanks, Steve

    http://tranquilitydotnet.codeplex.com/SourceControl/list/changesets

    ReplyDelete
    Replies
    1. Hi Steve,

      I have implemented your changes in my own code and it's working fine now, no more memory loss. Learned some new things along the way about Appdomains and refelction.
      Thanks !

      Geert

      Delete
  9. Hi, very helpful work !

    In my case, I've got a little problem: I'm not sure but the WcfServiceLibrary you write do not seem to work if one service is defined by myservice1.contracts.dll & myservice1.service.dll (about reflection and linking config to service...)

    Am I right?

    How can I solve this, my services being already used and written with this assemblies architecture?

    P.S. sorry for my english, I'm french, that explains...

    ReplyDelete