It wasn’t until a few weeks ago that I discovered that Castle has this facility that manages WCF client proxies. I knew there was a server-side facility but the client stuff was new to me. Very well, but at this moment however, I’m using LinFu, which is really great, but does not have this feature (at least, I couldn’t find it). With the excuse of getting to know a bit more about LinFu, I decided to quickly put it together myself…
Before you continue, you must know that the following implementation only incorporates my specific requirements (I’m certain they’re the most common, but nonetheless). It does not care about sessions, duplex communication or any of that. I’ve got a failry conservative service layer with services that are instantiated per call and since they are (almost) at the use case level, it is not very likely that client code performs subsequent calls on a single proxy instance. Subsequent calls are possible but a new channel is created each time.
Until today I used to revert to a ServiceHelper
class which provided some extension methods that accepted Action<T>
or Func<T,TResult>
lambda’s that where called inside a try, catch, finally block to make sure we dispose the channel properly. I never really liked this way of working, it is cumbersome and doesn’t abstract away the fact that you’re dealing with WCF services. Additionally, I was not able to inject WCF services neither. Am I glad that’s over!
Ok, how does it work? Well, after creating a container, all you have to do is this:
container.PreProcessors.Add(new ClientChannelPreProcessor());
This line of code above makes sure the LinFu container calls a pre-processor whenever a service interface needs to be resolved.
public class ClientChannelPreProcessor : IPreProcessor { public void Preprocess(IServiceRequest request) { if (request.ServiceType.IsDefined(typeof(ServiceContractAttribute), true)) request.ActualFactory = new ClientChannelFactory(); } }
This pre-processor couldn’t be more straight-forward: If we need to resolve a ServiceContract
interface, don’t use the default service factory, but a custom one. Well, actually, there is no default service factory; request.ActualFactory
is null because we didn’t register the service, we don’t need to.
public class ClientChannelFactory : IFactory { public object CreateInstance(IFactoryRequest request) { if (!request.ServiceType.IsDefined(typeof(ServiceContractAttribute), true)) throw new InvalidOperationException("The ClientChannelFactory is intended for WCF services only."); var factory = new ProxyFactory(); var interceptor = new ClientChannelInterceptor(request.ServiceType, request.ServiceName); return factory.CreateProxy(request.ServiceType, interceptor); } }
The client channel factory first creates an interceptor that knows about the interface type and the requested service name, if any. It then creates a dynamic proxy with the interceptor and returns is as the resolved service.
Finally, we have our interceptor that performs the actual service calls by creating the channel right before the actual service call is performed. After the call the channel is disposed by Closing or aborting it as necessary.
public class ClientChannelInterceptor : IInterceptor { private readonly Func<object> CreateChannel; public ClientChannelInterceptor(Type serviceType, string instanceName) { CreateChannel = GetChannelCreationDelegate(serviceType, instanceName); } public object Intercept(IInvocationInfo info) { var target = CreateChannel(); try { return info.TargetMethod.Invoke(target, info.Arguments); } finally { DisposeServiceInstance(target); } } private static void DisposeServiceInstance(object target) { var client = target as IClientChannel; if (client == null || client.State == CommunicationState.Closed) return; try { client.Close(); } catch { client.Abort(); } } private static Func<object> GetChannelCreationDelegate(Type interfaceType, string name) { if (name == null) name = ""; // revert to default endpoint var factoryType = typeof(ChannelFactory<>).MakeGenericType(interfaceType); var factory = Activator.CreateInstance(factoryType, name); var createChannelMethod = factoryType.GetMethod("CreateChannel", Type.EmptyTypes); return (Func<object>)Delegate.CreateDelegate(typeof(Func<object>), factory, createChannelMethod); } }
One last thing about resolving services with instance names. If you resolve services without specifying an instance name (which is most likely), the ChannelFactory
will create a proxy for the configured endpoint that has no name. If you specify a name it must be the name of an endpoint in your configuration file.
