Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Aggregating Services with a RoutingService

4.75/5 (3 votes)
1 May 2012MIT3 min read 31.7K   14  
Service aggregation of self hosted WCF services, using the built-in RoutingService .NET Framework class.

Introduction

Have you ever tried to do self-hosting of WCF services in a moderately sized SOA oriented architecture? If yes, you have surely encountered a question like this:

The answers to the above questions where either to implement a general service class that implements all service contracts that you want to publish and host it in a single ServiceHost, or to use Net TCP Port sharing, by which you give away a great portion of control over the system and also introduce a dependency on the port sharing service, which is not necessarily a desirable alternative.

The solution to the above problem is to move the routing of requests (that is what the port sharing service does) into your application by utilizing the built-in RoutingService class.

Implementation

The architecture of the solution consists of a base service consisting of only three classes you have to worry about.

The first is an abstract service handle base class that encapsulates a ServiceHost and takes care of it running on a separate thread. You can look up the code of this class in the appended code archive.

Next comes the ServiceShell class, which implements the before-mentioned handle base class and sets up the ServiceHost to host a RoutingService at a given base address. It can also expose (read: route to) multiple child services with custom contracts on addresses relative to its base address.

C#
public class ServiceShell : ServiceHandleBase
{
    private readonly Uri _baseAddress;
    private readonly RoutingConfiguration _routingConfiguration;
    private readonly Guid _serviceShellId;

    public ServiceShell(Uri baseAddress)
    {
        if (baseAddress == null)
        {
            throw new ArgumentNullException("baseAddress");
        }
        _baseAddress = baseAddress;
        _routingConfiguration = 
          new RoutingConfiguration { RouteOnHeadersOnly = true, SoapProcessingEnabled = true };
        _serviceShellId = Guid.NewGuid();
    }

    protected override ServiceHost CreateServiceHost()
    {
        var serviceHost = new ServiceHost(typeof(RoutingService), _baseAddress);
        serviceHost.Description.Behaviors.Add(new RoutingBehavior(_routingConfiguration));
        return serviceHost;
    }

    protected override ServiceEndpoint CreateService(ServiceHost serviceHost)
    {
        CheckDisposed();
        return serviceHost.AddServiceEndpoint(typeof (IDuplexSessionRouter), 
              new NetTcpBinding(SecurityMode.None, true), string.Empty);
    }

    public ServiceHandleBase RegisterChildService(Type serviceImplementation, 
           Type serviceContract, Uri relativeUri)
    {
        CheckDisposed();
        return new AggregatedService(this, serviceImplementation, serviceContract, relativeUri);
    }

    private void RemoveFromFilterTable(MessageFilter filter)
    {
        lock (SyncRoot)
        {
            if (_routingConfiguration.FilterTable.Remove(filter))
            {
                UpdateFilterTable();
            }
        }
    }

    private void AddToFilterTable(MessageFilter filter, 
            IEnumerable<ServiceEndpoint> services)
    {
        lock (SyncRoot)
        {
            _routingConfiguration.FilterTable.Add(filter, services);
            UpdateFilterTable();
        }
    }

    private void UpdateFilterTable()
    {
        var parentRoutingExtensions = Host.Extensions.Find<routingextension>();
        parentRoutingExtensions.ApplyConfiguration(_routingConfiguration);
    }
    // ...
}

So we come to the last class, the AggregatedService (also a service handle implementation) which is a private inner class of ServiceShell, since it needs access to some internal implementation details of ServiceShell. This class exposes the types passed to it as a service over a named pipes binding. The named pipe address uses a unique ID of its parent to ensure that multiple instances running on the same system won't have a name collision. When a service of this type starts, it also registers an EndpointAddressMessageFilter in the parent ServiceShell, consequently exposing/routing the service's endpoint on an address relative to the shell's base address - and of course - over the binding that the shell provides (in our case a net.tcp binding).

C#
private class AggregatedService : ServiceHandleBase
{
    private readonly Binding _binding;
    private readonly MessageFilter _filter;
    private readonly ServiceShell _parent;
    private readonly Uri _rootUri;
    private readonly Type _serviceContract;
    private readonly Type _serviceImplementation;
    private readonly Uri _rootUriExternal;

    public AggregatedService(ServiceShell parent, Type serviceImplementation, 
           Type serviceContract, Uri relativeUri)
    {
        var baseUri = new Uri("net.pipe://localhost/" + 
                      parent._serviceShellId + "/");
        _rootUri = new Uri(baseUri, relativeUri);
        _parent = parent;
        _serviceContract = serviceContract;
        _serviceImplementation = serviceImplementation;
        _binding = new NetNamedPipeBinding();
        _rootUriExternal = new Uri(_parent._baseAddress, relativeUri);
        _filter = new EndpointAddressMessageFilter(new EndpointAddress(_rootUriExternal));
    }

    protected override ServiceHost CreateServiceHost()
    {
        CheckDisposed();
        return new ServiceHost(_serviceImplementation, _rootUri);
    }

    protected override ServiceEndpoint CreateService(ServiceHost serviceHost)
    {
        CheckDisposed();
        return serviceHost.AddServiceEndpoint(_serviceContract, _binding, string.Empty);
    }

    public override bool Start()
    {
        lock (SyncRoot)
        {
            bool result = base.Start();
            if (result)
            {
                _parent.AddToFilterTable(_filter, new[] { Service });
            }
            return result;
        }
    }
    // ....
}

Demo application

The demo application contains two simple service definitions. A calculation service that contains a two way add operation, and a duplex echo service that does some string manipulation and returns the result to a duplex callback contract. The services neither use port sharing nor is there a need for a class implementing all service contracts.

C#
static void Main()
{
    var shellUri = new Uri("net.tcp://localhost:10011/AggregatedService/");
    var shellBinding = new NetTcpBinding(SecurityMode.None, true);

    using (var shell = new ServiceShell(shellUri))
    using (var echo = shell.RegisterChildService(typeof(EchoServer), 
           typeof(IEchoServer), new Uri("Echo", UriKind.Relative)))
    using (var calc = shell.RegisterChildService(typeof(CalculatorServer), 
           typeof(ICalculator), new Uri("Calc", UriKind.Relative)))
    {
        // External URI                                      -> Internal URI
        // ------------------------------------------------------------------------
        shell.Start();  // net.tcp://localhost:10011/AggregatedService/      -> N/A
        echo.Start();   // net.tcp://localhost:10011/AggregatedService/Echo  -> net.pipe://.../Echo
        calc.Start();   // net.tcp://localhost:10011/AggregatedService/Calc  -> net.pipe://.../Calc
        
        var echoClient = new EchoClient(); 
        var echoChannelFactory = new DuplexChannelFactory<IEchoServer>(echoClient, 
            shellBinding, new Uri(shellUri, "Echo").ToString());
        var echoChannel = echoChannelFactory.CreateChannel(); 

        var calcChannelFactory = new ChannelFactory<icalculator>(shellBinding, 
            new Uri(shellUri, "Calc").ToString());
        var calcChannel = calcChannelFactory.CreateChannel();

        // ...        
        var c = calcChannel.Add(1, 2); // c == 3
        // ...
        echoChannel.Shout("demo");         
        // ...
    }
}

Conclusion

As you can see, the RoutingService virtually eliminates the need to use the external port sharing service, and other unmaintainable approaches.

Points of interest

Instead of the named pipe binding, we could also use the NullTransport binding, but I did not use it since named pipes are apparently at least as fast.

You can read more about WCF routing on MSDN. I also stumbled across a nice article about filtering.

The source code of this article is also available on BitBucket as a mercurial repository.

License

This article, along with any associated source code and files, is licensed under The MIT License