As part of some work to solve some problems I was experiencing with unclosed connections in WCF, I decided that on all calls to a method on a WCF interface, the channel should be closed immediately after use.
With the channels being generated by a ChannelFactory, I decided it would be best to write an implementation of the interface that simply re-implemented all the interfaces methods, with a using statement to close the channel, effectively creating a method like the following for every call.
public void SomeRemoteMethod() { var channel = GetChannel(); using((IDisposable)channel) { channel.SomeRemoteMethod(); } }
While that solved my problem, I did feel like I was introducing a lot of redundancy into the code as there where a dozen methods that needed implementing.
Also, that particular implementation added an issue where if the channel faulted, the call to the Dispose() method would result in another error being thrown, obscuring the initial exception.
Having recently gained some experience in dynamically generating classes in C#, I decided the best solution would be to dynamically generate a class that implemented the interface, and generated the same code as above:
public static class InterfaceWrapper { #region Private Fields private static IDictionary<string, ModuleBuilder> _builders = new Dictionary<string, ModuleBuilder>(); private static IDictionary<Type, Type> _types = new Dictionary<Type, Type>(); private static object _lockObject = new object(); #endregion #region Public Methods /// <summary> /// Creates an interface that matches the interface defined by <typeparamref name="T"/> /// </summary> public static T CreateInterface<T>(Func<T> getter, Type wrapperType) { return (T)CreateInterfaceInstance<T>(getter, wrapperType); } // Note that calling this method will cause any further // attempts to generate an interface to fail public static void Save() { foreach (var builder in _builders.Select(b => b.Value)) { var ass = (AssemblyBuilder)builder.Assembly; try { ass.Save(ass.GetName().Name + ".dll"); } catch { } } } #endregion #region Private Methods private static T CreateInterfaceInstance<T>(Func<T> getter, Type wrapperType) { var destType = GenerateInterfaceType(getter, wrapperType); return (T)Activator.CreateInstance(destType); } private static Type GenerateInterfaceType<T>(Func<T> getter, Type wrapperType) { #region Cache Fetch var sourceType = typeof(T); Type newType; if (_types.TryGetValue(sourceType, out newType)) return newType; // Make sure the same interface isn't implemented twice lock (_lockObject) { if (_types.TryGetValue(sourceType, out newType)) return newType; #endregion #region Validation if (!sourceType.IsInterface) throw new ArgumentException("Type T is not an interface", "T"); if (!wrapperType.GetInterfaces().Contains(typeof(IDisposable))) throw new ArgumentException("Type must implement IDisposable.", "wrapperType"); var wrapperTypeConstructor = wrapperType.GetConstructor(new[] { typeof(object) }); if (wrapperTypeConstructor == null) throw new ArgumentException("Type must have a single constructor that takes a single object parameter.", "wrapperType"); var getterMethod = getter.Method; if ((getterMethod.Attributes & MethodAttributes.Public) != MethodAttributes.Public) throw new ArgumentException("Method must be public.", "getter"); #endregion #region Module and Assembly Creation var orginalAssemblyName = sourceType.Assembly.GetName().Name; ModuleBuilder moduleBuilder; if (!_builders.TryGetValue(orginalAssemblyName, out moduleBuilder)) { var newAssemblyName = new AssemblyName(Guid.NewGuid() + "." + orginalAssemblyName); var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly( newAssemblyName, System.Reflection.Emit.AssemblyBuilderAccess.RunAndSave); moduleBuilder = dynamicAssembly.DefineDynamicModule( newAssemblyName.Name, newAssemblyName + ".dll"); _builders.Add(orginalAssemblyName, moduleBuilder); } var assemblyName = moduleBuilder.Assembly.GetName(); #endregion #region Create the TypeBuilder var typeBuilder = moduleBuilder.DefineType( sourceType.FullName, TypeAttributes.Public | TypeAttributes.Class, typeof(object), new[] { sourceType }); #endregion #region Enumerate interface inheritance hierarchy var interfaces = new List<Type>(); IEnumerable<Type> subList; subList = new[] { sourceType }; while (subList.Count() != 0) { interfaces.AddRange(subList); subList = subList.SelectMany(i => i.GetInterfaces()); } interfaces = interfaces.Distinct().ToList(); #endregion #region Create the methods foreach (var method in interfaces.SelectMany(i => i.GetMethods())) { // Define the method based on the interfaces definition var newMethod = typeBuilder.DefineMethod( method.Name, method.Attributes ^ MethodAttributes.Abstract, method.CallingConvention, method.ReturnType, method.ReturnParameter.GetRequiredCustomModifiers(), method.ReturnParameter.GetOptionalCustomModifiers(), method.GetParameters().Select(p => p.ParameterType).ToArray(), method.GetParameters().Select(p => p.GetRequiredCustomModifiers()).ToArray(), method.GetParameters().Select(p => p.GetOptionalCustomModifiers()).ToArray() ); // Check to see if we have a return type bool hasReturnValue = method.ReturnType != typeof(void); var methodBody = newMethod.GetILGenerator(); // sourceType var0; // wrapperType var1; methodBody.DeclareLocal(sourceType); methodBody.DeclareLocal(wrapperType); // returnType var2; if (hasReturnValue) methodBody.DeclareLocal(method.ReturnType); // var0 = getter(); methodBody.Emit(OpCodes.Call, getterMethod); methodBody.Emit(OpCodes.Stloc_0); // var1 = new wrapperType(var0); methodBody.Emit(OpCodes.Ldloc_0); methodBody.Emit(OpCodes.Newobj, wrapperTypeConstructor); methodBody.Emit(OpCodes.Stloc_1); // using (var1) { methodBody.BeginExceptionBlock(); // (load the object to call the method on) methodBody.Emit(OpCodes.Ldloc_0); // (load any parameters) for (int i = 1; i <= method.GetParameters().Length; ++i) methodBody.Emit(OpCodes.Ldarg, i); // var2 = var0.method(...); methodBody.Emit(OpCodes.Callvirt, method); if (hasReturnValue) methodBody.Emit(OpCodes.Stloc_2); // } (end using) methodBody.BeginFinallyBlock(); methodBody.Emit(OpCodes.Ldloc_1); methodBody.Emit(OpCodes.Callvirt, typeof(IDisposable).GetMethod("Dispose")); methodBody.EndExceptionBlock(); // return var2; if (hasReturnValue) methodBody.Emit(OpCodes.Ldloc_2); // return; methodBody.Emit(OpCodes.Ret); } #endregion #region Create and return the defined type newType = typeBuilder.CreateType(); _types.Add(sourceType, newType); return newType; } #endregion } #endregion }
Then, using the following class for the wrapperType parameter, I solved the disposing a faulted channel problem also:
public class ChannelDisposer : IDisposable { private object _channel; public ChannelDisposer(object channel) { if (!(channel is IChannel)) throw new ArgumentException("Argument dosn't implement IChannel", "channel"); if (!(channel is IDisposable)) throw new ArgumentException("Argument dosn't implement IDisposable", "channel"); _channel = channel; } #region IDisposable Members public void Dispose() { if (((IChannel)_channel).State != CommunicationState.Faulted) ((IDisposable)_channel).Dispose(); } #endregion }
Then finally I can pull it all together with a single call:
IService service = InterfaceWrapper.CreateInterface<IService>(CreateServiceInstance, typeof(ChannelDisposer));
This code is provided As Is with no warranty expressed or implied.
hi.. I had the same issue and solved it in a different way..
Basically, ive intercepted the calls to the inner WCF proxy (that inherits from RealProxy).
The following code is just an example. It wont compile (external dependencies), but u can get the idea.
The *main* trick is the line:
_InnerRealProxy = RemotingServices.GetRealProxy(_Instancia);
internal class ClientChannelProxy : RealProxy
{
private static readonly object[] _NullArray = { };
private ChannelFactoryCustom _ChFactory;
private IClientChannel _Instancia;
private RealProxy _InnerRealProxy;
public ClientChannelProxy(Binding Binding, EndpointAddress Address, string EndpointBehaviorConfig) :
base(typeof(T))
{
//_ChFactory = CacheChannelFactory.GetChannelFactory(Interface, Binding, Address);
//_Instancia = _ChFactory.ChannelFactory.CreateClientChannel();
_ChFactory = new ChannelFactoryCustom(Binding, Address, EndpointBehaviorConfig);
_ChFactory.Open();
_Instancia = _ChFactory.CreateClientChannel();
_InnerRealProxy = RemotingServices.GetRealProxy(_Instancia);
}
public override IMessage Invoke(IMessage msg)
{
string MethodName = (string) msg.Properties["__MethodName"];
object Args = msg.Properties["__Args"];
if (MethodName != null && MethodName == "Dispose" &&
Args != null && (Args as Array).Length == 0)
{
return InvokeDispose(msg);
}
if (_InnerRealProxy == null)
throw new ObjectDisposedException(this.GetProxiedType().FullName);
return _InnerRealProxy.Invoke(msg);
}
private IMethodReturnMessage InvokeDispose(IMessage msg)
{
CloseAbort(_Instancia);
_Instancia = null;
_InnerRealProxy = null;
//CacheChannelFactory.LibereChannelFactory(_ChFactory);
CloseAbort(_ChFactory);
_ChFactory = null;
IMethodCallMessage MethodCallMessage = msg as IMethodCallMessage;
return new ReturnMessage(null, _NullArray, 0, MethodCallMessage.LogicalCallContext, MethodCallMessage);
}
private static void CloseAbort(ICommunicationObject obj)
{
if (obj == null)
return;
if (obj.State == CommunicationState.Closed)
return;
bool success = false;
try
{
if (obj.State != CommunicationState.Faulted)
obj.Close();
else
obj.Abort();
success = true;
}
finally
{
if (!success)
obj.Abort();
}
}
}
Hey ‘uildson’,
Thanks for the comment. Good thinking on using the GetRealProxy(), and hopefully in WCF’s next incarnation we won’t have to work so hard to fix something so seemingly simple!
Phil