Using C#, Inversion of Control and WCF to produce a generic host for use in distributed systems.

Using C#, Inversion of Control and WCF to produce a generic host for use in distributed systems.

02/21/2008 23:48:48

.Net 3.0 & 3.5 provides a number of techniques for you to author your own distributed architectures and I'm going to attempt to explain a technique that "worked for me", allowing me to write a reliable, testable, service based application using the Windows Communication Foundation (WCF).

The goal of this little project is to create a multipurpose standalone server application that can host any code you wish, using WCF to provide other applications access to the code.  The server application should have no idea what it's hosting, nor be concerned with it.  New services should be installable in to the service host by configuration tweaks alone.  This will result in a low impact standard method to expose API's and web services to either external or internal applications using just configuration settings.

 

Pre-Requirements

Throughout this example I'll be presuming that the reader has a strong grasp of .Net. C#, and Microsoft technologies.  I'll also touch on nUnit testing and the Castle projects IOC container Windsor, so exposure to either or both of those products would be advantageous.  That said, for the most part, this should just be compile, configure and go.

Castle Windsor: http://www.castleproject.org/castle/download.html
NUnit: http://www.nunit.org/index.php?p=download (or mbUnit if you wish...)
and obviously Visual Studio 2005/2008.

 

Sample Architecture

The following is a pattern that I've found to work very successfully:

  • /ServiceApplication
    • /ConsoleHost
    • /SystemService
    • /ServiceContracts
    • /ServiceImplementations
    • /Test_SystemService
    • /Test_ServiceImplementations
    • /CommunicationManager
    • /Installation
  • /ClientApplication
    • /Model
    • /DataManager

We'll take a look at the client application later, but first a rough outline of the projects that the ServiceApplication consists of.

ConsoleHost is designed to be an exceptionally thin wrapper to provide a console view on the service, it's concerned only with user interaction and should use CommunicationManager for all of the legwork.

SystemService is an equally thin wrapper around CommunicationManager providing an installable service wrap for your application.

ServiceContracts is an assembly that should contain ONLY the WCF service contracts (C# interfaces) that you intend to make accessible remotely.  You should expect to share this compiled assembly with any client implementations, so ensure this is dependency free (which really shouldn't be a problem so long as you ensure that you only store interfaces in this assembly).

ServiceImplementations should consist of the ServiceApplication specific implementations of the interfaces defined within ServiceContracts.

Test_SystemService is designed to store unit tests that make use of WCF to connect to the running ServiceApplication, and Test_ServiceImplementations should contain unit tests designed to test the ServiceImplementations directly.  Having these two similar test projects allows you to troubleshoot connection related errors during development independently of code logic related errors.

The CommunicationManager does the brunt of the work, constructing WCF endpoints and maintaining them throughout their lifecycle.  This assembly takes care of the opening and closing of listening services.  It's constructed of a few key components I'll detail later.

Installation is reserved for an Installer of your choice, preferably one that can deploy and install Windows Services (WiX or old fashioned MSI projects, I'm looking at you).

I'm not going to go into great detail regarding the SystemService (it's a standard windows service which calls the same methods the console host will), nor the unit tests or installer.  I'm also not going to try and explain the intricate details of the configuration of WCF, there's a wealth of resources available online on this topic.  I will however supply some example configuration- enough to make the code functional.

 

Service Functionality

Define Your Service Contracts

The simplest way to start this project is to define an interface in C# which should describe a few basic operations that you wish to make available as a service (the WCF Service Contract).  I've distilled this down into an example I'm calling IExampleContract, the contents of which are:

using System.ServiceModel;

namespace DEJW.ServiceContracts
{
    [ServiceContract(Namespace = "http://namespace/", Name = "Example Service")]
    public interface IExampleContract
    {
        /// <summary>
        /// Returns the uniqueIdentifier supplied back to the calling application.
        /// </summary>
        /// <param name="uniqueIdentifier"></param>
        /// <returns></returns>
        [OperationContract]
        [FaultContract(typeof(ExceptionDetail))]
        string Handshake(string uniqueIdentifier);
    }
}


 

Define the ServiceImplementation tests

For the sake of following good "XP" practice, the next step should be constructing a simple unit test for the methods you wish to define, and then author the implementation top down.  For the above simple "Handshake" method, the following nUnit test should suffice:

[Test]
public void HandshakeWithSimpleString()
{
    string testString = "Hello World!";

    ExampleContractImpl impl = new ExampleContractImpl();
    string response = impl.Handshake(testString);

    Assert.AreEqual(testString, response);
}

Write the ServiceImplementations to fulfil the tests

With my (failing, no code!) unit test in place, I'll now write the code to pass the test.  The implementation should go into a ServiceImplementations project (because when you hand your ServiceContracts over to the consuming application, you'd not want to pass over the service implementation to go with it...).

using DEJW.CommunicationManager;
using DEJW.ServiceContracts;

namespace DEJW.ServiceImplementations
{
    public class ExampleContractImpl: IExampleContract, IPlugableService
    {
        #region IExampleContract Members

        public string Handshake(string uniqueIdentifier)
        {
            return uniqueIdentifier;
        }

        #endregion
    }
}

You may have noticed that the implementation features a rogue little interface called IPlugableService that doesn't appear to need implementing.  This is a concession to the Castle IOC framework and I'll elaborate on its use in the section on CommunicationManager.

Communication Manager

The CommunicationManager assembly consists of a few key components. 

  • HostManager, responsible for opening and closing WCF endpoints as per the configuration in app.config.
  • IOCFactory, a wrapper for the Windsor containers that helps quickly load a concrete implementation from a configuration value and assembly name.
  • IPlugableService interface, an empty interface used that must be implemented by our service implementations in order for the Windsor container to identify them.
  • IPlugableServiceFactory, an configuration parsing library that inspects configuration and instantiates instances of all the IPlugableServices' defined in app.config.

I'm not going to go into great detail regarding HostManager, most of the code was adapted from various Microsoft WCF samples with a few generic collections tacked on and a couple of wrapping methods.  There's nothing high-tech here, it just works.

Usage is very simple:

_hostManager = new HostManager();
_hostManager.AddServiceContracts(PlugableServiceFactory.RetrieveCollectionOfPlugableServices());
_hostManager.StartListening();

As long as your configuration settings are correct, magic happens here.

The key method there is PlugableServiceFactory.RetrieveCollectionOfPlugableServices().  This method loads and traverses the Castle Windsor configuration, and then loads by name every instances of IPlugableService it finds in the config file.  HostManager then uses a typeof on each of these loaded IPlugableService to create a instance of their concrete type, ignoring IPlugableService entirely.  IPlugableService is an interface in place simply so we can tell Windsor what to do.  HostManager then configures the concrete classes as WCF ServiceHosts using the configuration specified.

Configuration Settings

Ensure the following is included in your app.config file:

<configSections>
  <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
</configSections>

<castle>
  <components>
    <component  id="service1"
                service="DEJW.CommunicationManager.IPlugableService, DEJW.CommunicationManager"
                type="DEJW.ServiceImplementations.ExampleContractImpl, DEJW.ServiceImplementations" />

    <component  id="service2"
                service="DEJW.CommunicationManager.IPlugableService, DEJW.CommunicationManager"
                type="DEJW.ServiceImplementations.ExampleContractTwoImpl, DEJW.ServiceImplementations" />
  </components>
</castle>

If you look carefully you'll see why IPlugableService is important; the Windsor components will attempt to instantiate your components as instances of that empty interface.  The type attribute points to "NameSpace.ConcreteClass, assembly" allowing the Windsor container to find your service implementations.

Next you need to add the relevant WCF configuration, it's quite lengthy if you're explicit, but descriptive.

<system.serviceModel>
   <bindings>
     <netTcpBinding>
       <binding name="IExampleContractBinding"
        transferMode="Buffered"
        transactionProtocol="OleTransactions"
        hostNameComparisonMode="StrongWildcard"
        closeTimeout="00:01:00"
        openTimeout="00:01:00"
        receiveTimeout="00:10:00"
        sendTimeout="00:10:00"
        transactionFlow="false"
        maxBufferPoolSize="50000000"
        maxBufferSize="268435455"
        maxReceivedMessageSize="268435455"
        maxConnections="10000"
                >
         <readerQuotas
           maxDepth="50000000"
           maxStringContentLength="50000000"
           maxArrayLength="50000000"
           maxBytesPerRead="50000000"
           maxNameTableCharCount="50000000"
         />
         <reliableSession ordered="false"
           inactivityTimeout="00:10:00"
           enabled="false"
         />
         <security mode="None">
           <transport clientCredentialType="Windows" />
           <message clientCredentialType="Windows"/>
         </security>
       </binding>

     </netTcpBinding>

   </bindings>

   <behaviors>
     <serviceBehaviors>

       <behavior name="IExampleContractBehaviour">
         <serviceMetadata httpGetEnabled="true" />
         <dataContractSerializer maxItemsInObjectGraph="100000"/>
         <serviceThrottling maxConcurrentCalls="100" maxConcurrentSessions="100" />
       </behavior>

     </serviceBehaviors>
   </behaviors>

   <services>
     <service name="DEJW.ServiceImplementations.ExampleContractImpl" behaviorConfiguration="IExampleContractBehaviour">

       <endpoint name="IExampleContractEndpoint"
                 address="query"
                 binding="netTcpBinding"
                 bindingConfiguration="IExampleContractBinding"
                 contract="DEJW.ServiceContracts.IExampleContract" />

       <endpoint name="IExampleContractMetaData"
                 address="mex"
                 binding="mexHttpBinding"
               

;  contract="IMetadataExchange" />

       <host>
         <baseAddresses>
           <add baseAddress="net.tcp://localhost:8000/DistributedServer" />
           <add baseAddress="http://localhost:8001/DistributedServer" />
         </baseAddresses>
       </host>
     </service>

     <service name="DEJW.ServiceImplementations.ExampleContractTwoImpl" behaviorConfiguration="IExampleContractBehaviour">

       <endpoint name="IExampleContractTwoEndpoint"
                 address="query"
                 binding="netTcpBinding"
                 bindingConfiguration="IExampleContractBinding"
                 contract="DEJW.ServiceContracts.IExampleContractTwo" />

       <endpoint name="IExampleContractTwoMetaData"
                 address="mex"
                 binding="mexHttpBinding"
                 contract="IMetadataExchange" />

       <host>
         <baseAddresses>
           <add baseAddress="net.tcp://localhost:8002/DistributedServer" />
           <add baseAddress="http://localhost:8003/DistributedServer" />
         </baseAddresses>
       </host>
     </service>
   </services>
</system.serviceModel>

Obviously your mileage may vary with the above configuration (a lot of those values are values I've used in my day job for development purposes, but you'll want to tighten down lots of those values and, I suspect, enable security for any production system).

Control Application

The control application that goes with this code is practically nonexistent (one of the stated goals) just stick the aforementioned usage example in the main method, followed by some kind of message and a ReadLine() to stop the application exiting.  Something akin to:

_hostManager = new HostManager();
_hostManager.AddServiceContracts(PlugableServiceFactory.RetrieveCollectionOfPlugableServices());
_hostManager.StartListening();

Console.WriteLine("Press any key to exit");
Console.ReadLine();

For the windows service implementation create a static instance of HostManager and override protected override void OnStart(string[] args), in this method call the StartListening() method on HostManager, and override OnStop to call the StopListening() method for a clean shutdown.

Maintainability

The key benefit to this approach to service hosting is how trivial it makes expanding your services.  In order to add extra functionality to an existing service you need to modify the service contract and service implementation, recompile those two projects and let the HostManager do the rest.  If you wish to host an entirely new service, create a new service contract and implementation, and remember to add a configuration section to app.config, and again, let HostManager take care of the details.

 

Source code

The source code provided here is NOT a compiling project.  Instead I've provided the CommunicationManager project (if you've read the above details you'll realise that's all you really need), the example ServiceContracts and example ServiceImplementations, alongside the example unit test and an example App.config.

If you wish to use this code, remember you'll need a copy of the Windsor components, you'll need NUnit if you want to run the unit test and you'll need to write your own console / service wrapper.

Some of the code in here was inspired by writing a more specific service for my employer that made me wonder if I could abstract the service host portion of the code into a more general purpose application.  Apparently it was possible!  I've not reused any code however I'd imagine the similarities are striking, so whilst not "production tested", the methods and techniques used should be relatively bug free.  No warranty etc etc, but I hope somebody finds this useful.  It's definitely the approach I intend to take to authoring services from this point forwards.

 Download GenericWCFServer.zip

Now Playing: Zimmers Hole - When You Were Shouting At The Devil... We Were In League With Satan