What is the best workaround for the WCF client `using` block issue?

前端 未结 26 1755
余生分开走
余生分开走 2020-11-22 00:03

I like instantiating my WCF service clients within a using block as it\'s pretty much the standard way to use resources that implement IDisposable:

26条回答
  •  我寻月下人不归
    2020-11-22 00:16

    Summary

    Using the techniques described in this answer one can consume a WCF service in a using block with the following syntax:

    var channelFactory = new ChannelFactory("");
    
    var serviceHelper = new ServiceHelper(channelFactory);
    var proxy = serviceHelper.CreateChannel();
    using (proxy as IDisposable)
    {
        proxy.DoWork();
    }
    

    You can of course adapt this even further to achieve a more concise programming model specific to your situation - but the point is that we can create an implementation of IMyService reprenting the channel which correctly implements the disposable pattern.


    Details

    All the answers given thus far address the problem of getting around the "bug" in the WCF Channel implemention of IDisposable. The answer that seems to offer the most concise programming model (allowing you to use the using block to dispose on unmanaged resources) is this one - where the proxy is modifed to implement IDisposable with a bug-free implementation. The problem with this approach is maintainability - we have to re-implement this functionality for ever proxy we use. On a variation of this answer we will see how we can use composition rather than inheritance to make this technique generic.

    First Attempt

    There seem to various implementations for the IDisposable implementation, but for sake of argument we will use an adaption of that used by the currently accepted answer.

    [ServiceContract]
    public interface IMyService
    {
        [OperationContract]
        void DoWork();
    }
    
    public class ProxyDisposer : IDisposable
    {
        private IClientChannel _clientChannel;
    
    
        public ProxyDisposer(IClientChannel clientChannel)
        {
            _clientChannel = clientChannel;
        }
    
        public void Dispose()
        {
            var success = false;
            try
            {
                _clientChannel.Close();
                success = true;
            }
            finally
            {
                if (!success)
                    _clientChannel.Abort();
                _clientChannel = null;
            }
        }
    }
    
    public class ProxyWrapper : IMyService, IDisposable
    {
        private IMyService _proxy;
        private IDisposable _proxyDisposer;
    
        public ProxyWrapper(IMyService proxy, IDisposable disposable)
        {
            _proxy = proxy;
            _proxyDisposer = disposable;
        }
    
        public void DoWork()
        {
            _proxy.DoWork();
        }
    
        public void Dispose()
        {
            _proxyDisposer.Dispose();
        }
    }
    

    Armed with the above classes we can now write

    public class ServiceHelper
    {
        private readonly ChannelFactory _channelFactory;
    
        public ServiceHelper(ChannelFactory channelFactory )
        {
            _channelFactory = channelFactory;
        }
    
        public IMyService CreateChannel()
        {
            var channel = _channelFactory.CreateChannel();
            var channelDisposer = new ProxyDisposer(channel as IClientChannel);
            return new ProxyWrapper(channel, channelDisposer);
        }
    }
    

    This allows us to consume our service using the using block:

    ServiceHelper serviceHelper = ...;
    var proxy = serviceHelper.CreateChannel();
    using (proxy as IDisposable)
    {
        proxy.DoWork();
    }
    

    Making this generic

    All we have done so far is to reformulate Tomas' solution. What prevents this code from being generic is the fact that ProxyWrapper class has to be re-implemented for every service contract we want. We will now look at a class that allows us to create this type dynamically using IL:

    public class ServiceHelper
    {
        private readonly ChannelFactory _channelFactory;
    
        private static readonly Func _channelCreator;
    
        static ServiceHelper()
        {
            /** 
             * Create a method that can be used generate the channel. 
             * This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
             * */
            var assemblyName = Guid.NewGuid().ToString();
            var an = new AssemblyName(assemblyName);
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
            var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);
    
            var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));
    
            var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
                new[] { typeof(T), typeof(IDisposable) });
    
            var ilGen = channelCreatorMethod.GetILGenerator();
            var proxyVariable = ilGen.DeclareLocal(typeof(T));
            var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
            ilGen.Emit(OpCodes.Ldarg, proxyVariable);
            ilGen.Emit(OpCodes.Ldarg, disposableVariable);
            ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
            ilGen.Emit(OpCodes.Ret);
    
            _channelCreator =
                (Func)channelCreatorMethod.CreateDelegate(typeof(Func));
    
        }
    
        public ServiceHelper(ChannelFactory channelFactory)
        {
            _channelFactory = channelFactory;
        }
    
        public T CreateChannel()
        {
            var channel = _channelFactory.CreateChannel();
            var channelDisposer = new ProxyDisposer(channel as IClientChannel);
            return _channelCreator(channel, channelDisposer);
        }
    
       /**
        * Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
        * This method is actually more generic than this exact scenario.
        * */
        private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
        {
            TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
                TypeAttributes.Public | TypeAttributes.Class);
    
            var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
                tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));
    
            #region Constructor
    
            var constructorBuilder = tb.DefineConstructor(
                MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
                MethodAttributes.RTSpecialName,
                CallingConventions.Standard,
                interfacesToInjectAndImplement);
    
            var il = constructorBuilder.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));
    
            for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
            {
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldarg, i);
                il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
            }
            il.Emit(OpCodes.Ret);
    
            #endregion
    
            #region Add Interface Implementations
    
            foreach (var type in interfacesToInjectAndImplement)
            {
                tb.AddInterfaceImplementation(type);
            }
    
            #endregion
    
            #region Implement Interfaces
    
            foreach (var type in interfacesToInjectAndImplement)
            {
                foreach (var method in type.GetMethods())
                {
                    var methodBuilder = tb.DefineMethod(method.Name,
                        MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
                        MethodAttributes.Final | MethodAttributes.NewSlot,
                        method.ReturnType,
                        method.GetParameters().Select(p => p.ParameterType).ToArray());
                    il = methodBuilder.GetILGenerator();
    
                    if (method.ReturnType == typeof(void))
                    {
                        il.Emit(OpCodes.Nop);
                        il.Emit(OpCodes.Ldarg_0);
                        il.Emit(OpCodes.Ldfld, typeFields[type]);
                        il.Emit(OpCodes.Callvirt, method);
                        il.Emit(OpCodes.Ret);
                    }
                    else
                    {
                        il.DeclareLocal(method.ReturnType);
    
                        il.Emit(OpCodes.Nop);
                        il.Emit(OpCodes.Ldarg_0);
                        il.Emit(OpCodes.Ldfld, typeFields[type]);
    
                        var methodParameterInfos = method.GetParameters();
                        for (var i = 0; i < methodParameterInfos.Length; i++)
                            il.Emit(OpCodes.Ldarg, (i + 1));
                        il.Emit(OpCodes.Callvirt, method);
    
                        il.Emit(OpCodes.Stloc_0);
                        var defineLabel = il.DefineLabel();
                        il.Emit(OpCodes.Br_S, defineLabel);
                        il.MarkLabel(defineLabel);
                        il.Emit(OpCodes.Ldloc_0);
                        il.Emit(OpCodes.Ret);
                    }
    
                    tb.DefineMethodOverride(methodBuilder, method);
                }
            }
    
            #endregion
    
            return tb.CreateType();
        }
    }
    

    With our new helper class we can now write

    var channelFactory = new ChannelFactory("");
    
    var serviceHelper = new ServiceHelper(channelFactory);
    var proxy = serviceHelper.CreateChannel();
    using (proxy as IDisposable)
    {
        proxy.DoWork();
    }
    

    Note that you could also use the same technique (with slight modifications) for auto-generated clients inheriting for ClientBase<> (instead of using ChannelFactory<>), or if you want to use a different implementation of IDisposable to close your channel.

提交回复
热议问题