Search code examples
c#wcfcastle-windsorwcffacility

Windsor WCF Client for multiple endpoints


I have a service running on multiple different servers with very similar configurations. I want to be able to use Castle Windsor WCF Facility to generate a client for arbitrary endpoint addresses.

public class ServiceFactory {
    public IService GetService(string hostName){
        ....
    }
}

Now, I will know at compile time what all my services will be, so I can do this:

var container = new WindsorContainer();

// ...

container.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero);
container.Register(Component.For<IService>().AsWcfClient(new DefaultClientModel()
{
    Endpoint = WcfEndpoint.BoundTo(new NetTcpBinding()).At("net.tcp://hostname:port")
}).Named("hostname"));

And then do my ServiceFactory like this:

public class ServiceFactory
{
    private readonly IWindsorContainer _container;

    public ServiceFactory(IWindsorContainer container)
    {
        _container = container;
    }

    public IService GetService(string hostName)
    {
        return _container.Resolve<IService>(hostName);
    }
}

But this is not robust against me forgetting to configure a particular endpoint. Is there a more elegant solution?


Solution

  • I had to spelunk through the WCF Facility source code, but YES! there is a more elegant solution:

    The WCF facility integrates with the Typed Factory Facility which is used for dynamically creating factories at runtime. The WCF facility adds functionality to the Typed Factory Facility for resolving factories which produce a service connection from an IWcfEndpoint.

    Specifically, it knows how to resolve a factory method in the form:

    IService Create(IWcfEndpoint endpoint);
    

    Where IService is something with the [ServiceContract] attribute.

    First I make my service factory depend on a Func<IWcfEndpoint,IService>:

    public class ServiceFactory {
        public ServiceFactory(Func<IWcfEndpoint, IService> resolveService){
            _resolveService = resolveService;
        }
    
        public IService GetService(string hostName){
            return _resolveService(WcfEndpoint.BoundTo(new NetTcpBinding()).At($"net.tcp://{hostName}:port"));
        }
    }
    

    Then when installing, I add the TypedFactoryFacility:

    var container = new WindsorContainer();
    
    // ...
    
    container.AddFacility<TypedFactoryFacility>();
    container.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero);
    container.Register(
        Component.For<Func<IWcfEndpoint, IService>>().AsFactory(),
        Component.For<ServiceFactory>().ImplementedBy<ServiceFactory>());   
    

    Windsor will then provide a ServiceFactory to anything that asks for one, and wire it up to produce an IService client from a hostname:

    public class FooClass {
        public FooClass(ServiceFactory serviceFactory){
            var service = serviceFactory.GetService("localhost");
        }
    }