首页 文章

WCF客户端“使用”块问题的最佳解决方法是什么?

提问于
浏览
380

我喜欢在 using 块中实例化我的WCF服务客户端,因为它几乎是使用实现 IDisposable 的资源的标准方法:

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

但是,如this MSDN article中所述,将WCF客户端包装在 using 块中可能会掩盖导致客户端处于故障状态的任何错误(如超时或通信问题) . 简而言之,当调用Dispose()时,客户端's Close() method fires, but throws an error because it'处于故障状态 . 然后,第二个异常将屏蔽原始异常 . 不好 .

MSDN文章中建议的解决方法是完全避免使用 using 块,而是实例化您的客户端并使用它们,如下所示:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

using 块相比,我觉得这很难看 . 每次需要客户端时都需要编写很多代码 .

幸运的是,我找到了一些其他的解决方法,例如IServiceOriented上的这个解决方法 . 你从:

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
}

然后允许:

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
});

那个's not bad, but I don'认为它与 using 块一样具有表现力和易于理解 .

我正在尝试使用的解决方法我首先阅读blog.davidbarret.net . 基本上,无论您在何处使用它,都会覆盖客户端的 Dispose() 方法 . 就像是:

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

这似乎能够再次允许 using 块,而没有掩盖故障状态异常的危险 .

那么,我有什么其他的问题需要注意使用这些变通方法吗?有没有人想出更好的东西?

26 回答

  • 7

    我写了一个higher order function以使其正常工作 . 我们已经在几个项目中使用了它,它看起来效果很好 . 这就是从一开始就应该做的事情,没有"using"范例等等 .

    TReturn UseService<TChannel, TReturn>(Func<TChannel, TReturn> code)
    {
        var chanFactory = GetCachedFactory<TChannel>();
        TChannel channel = chanFactory.CreateChannel();
        bool error = true;
        try {
            TReturn result = code(channel);
            ((IClientChannel)channel).Close();
            error = false;
            return result;
        }
        finally {
            if (error) {
                ((IClientChannel)channel).Abort();
            }
        }
    }
    

    你可以这样打电话:

    int a = 1;
    int b = 2;
    int sum = UseService((ICalculator calc) => calc.Add(a, b));
    Console.WriteLine(sum);
    

    这与您的示例中的情况非常相似 . 在某些项目中,我们编写了强类型的辅助方法,因此我们最终编写了诸如“Wcf.UseFooService(f => f ...)”之类的东西 .

    考虑到所有事情,我发现它非常优雅 . 你遇到了什么特别的问题吗?

    这允许插入其他漂亮的功能 . 例如,在一个站点上,站点代表登录用户对服务进行身份验证 . (该站点本身没有凭据 . )通过编写我们自己的“UseService”方法帮助程序,我们可以按照我们想要的方式配置通道工厂等 . 我们也不必使用生成的代理 - 任何接口都可以 .

  • 128

    覆盖客户端的Dispose()而无需基于ClientBase生成代理类,也无需manage channel creation and caching! (请注意,WcfClient不是ABSTRACT类,基于ClientBase)

    // No need for a generated proxy class
    //using (WcfClient<IOrderService> orderService = new WcfClient<IOrderService>())
    //{
    //    results = orderService.GetProxy().PlaceOrder(input);
    //}
    
    public class WcfClient<TService> : ClientBase<TService>, IDisposable
        where TService : class
    {
        public WcfClient()
        {
        }
    
        public WcfClient(string endpointConfigurationName) :
            base(endpointConfigurationName)
        {
        }
    
        public WcfClient(string endpointConfigurationName, string remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
        {
        }
    
        public WcfClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
            base(endpointConfigurationName, remoteAddress)
        {
        }
    
        public WcfClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
            base(binding, remoteAddress)
        {
        }
    
        protected virtual void OnDispose()
        {
            bool success = false;
    
            if ((base.Channel as IClientChannel) != null)
            {
                try
                {
                    if ((base.Channel as IClientChannel).State != CommunicationState.Faulted)
                    {
                        (base.Channel as IClientChannel).Close();
                        success = true;
                    }
                }
                finally
                {
                    if (!success)
                    {
                        (base.Channel as IClientChannel).Abort();
                    }
                }
            }
        }
    
        public TService GetProxy()
        {
            return this.Channel as TService;
        }
    
        public void Dispose()
        {
            OnDispose();
        }
    }
    
  • 26

    @Marc Gravell

    使用它不是没关系的:

    public static TResult Using<T, TResult>(this T client, Func<T, TResult> work)
            where T : ICommunicationObject
    {
        try
        {
            var result = work(client);
    
            client.Close();
    
            return result;
        }
        catch (Exception e)
        {
            client.Abort();
    
            throw;
        }
    }
    

    或者, (Func<T, TResult>) 的情况 (Func<T, TResult>)

    这些将使返回变量更容易 .

  • 7

    以下助手允许调用 void 和非void方法 . 用法:

    var calculator = new WcfInvoker<CalculatorClient>(() => new CalculatorClient());
    var sum = calculator.Invoke(c => c.Sum(42, 42));
    calculator.Invoke(c => c.RebootComputer());
    

    该课程本身是:

    public class WcfInvoker<TService>
        where TService : ICommunicationObject
    {
        readonly Func<TService> _clientFactory;
    
        public WcfInvoker(Func<TService> clientFactory)
        {
            _clientFactory = clientFactory;
        }
    
        public T Invoke<T>(Func<TService, T> action)
        {
            var client = _clientFactory();
            try
            {
                var result = action(client);
                client.Close();
                return result;
            }
            catch
            {
                client.Abort();
                throw;
            }
        }
    
        public void Invoke(Action<TService> action)
        {
            Invoke<object>(client =>
            {
                action(client);
                return null;
            });
        }
    }
    
  • 3

    像这样的包装器可以工作:

    public class ServiceClientWrapper<ServiceType> : IDisposable
    {
        private ServiceType _channel;
        public ServiceType Channel
        {
            get { return _channel; }
        }
    
        private static ChannelFactory<ServiceType> _channelFactory;
    
        public ServiceClientWrapper()
        {
            if(_channelFactory == null)
                 // Given that the endpoint name is the same as FullName of contract.
                _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
            _channel = _channelFactory.CreateChannel();
            ((IChannel)_channel).Open();
        }
    
        public void Dispose()
        {
            try
            {
                ((IChannel)_channel).Close();
            }
            catch (Exception e)
            {
                ((IChannel)_channel).Abort();
                // TODO: Insert logging
            }
        }
    }
    

    这应该使您能够编写如下代码:

    ResponseType response = null;
    using(var clientWrapper = new ServiceClientWrapper<IService>())
    {
        var request = ...
        response = clientWrapper.Channel.MyServiceCall(request);
    }
    // Use your response object.
    

    如果需要,包装器当然可以捕获更多例外,但原理保持不变 .

  • 1
    public static class Service<TChannel>
    {
        public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");
    
        public static TReturn Use<TReturn>(Func<TChannel,TReturn> codeBlock)
        {
            var proxy = (IClientChannel)ChannelFactory.CreateChannel();
            var success = false;
            try
            {
                var result = codeBlock((TChannel)proxy);
                proxy.Close();
                success = true;
                return result;
            }
            finally
            {
                if (!success)
                {
                    proxy.Abort();
                }
            }
        }
    }
    

    所以它允许很好地编写return语句:

    return Service<IOrderService>.Use(orderService => 
    { 
        return orderService.PlaceOrder(request); 
    });
    
  • 2

    根据Marc Gravell,MichaelGG和Matt Davis的回答,我们的开发人员提出了以下建议:

    public static class UsingServiceClient
    {
        public static void Do<TClient>(TClient client, Action<TClient> execute)
            where TClient : class, ICommunicationObject
        {
            try
            {
                execute(client);
            }
            finally
            {
                client.DisposeSafely();
            }
        }
    
        public static void DisposeSafely(this ICommunicationObject client)
        {
            if (client == null)
            {
                return;
            }
    
            bool success = false;
    
            try
            {
                if (client.State != CommunicationState.Faulted)
                {
                    client.Close();
                    success = true;
                }
            }
            finally
            {
                if (!success)
                {
                    client.Abort();
                }
            }
        }
    }
    

    使用示例:

    string result = string.Empty;
    
    UsingServiceClient.Do(
        new MyServiceClient(),
        client =>
        result = client.GetServiceResult(parameters));
    

    它尽可能接近“using”语法,在调用void方法时不必返回虚值,并且可以多次调用服务(并返回多个值)而不必使用元组 .

    此外,如果需要,您可以使用 ClientBase<T> 后代而不是ChannelFactory .

    如果开发人员想要手动处理代理/通道,则会暴露扩展方法 .

  • 4

    我写了a simple base class来处理这个问题 . 它可以作为NuGet package使用,而且非常易于使用 .

    //MemberServiceClient is the class generated by SvcUtil
    public class MemberServiceManager : ServiceClientBase<MemberServiceClient>
    {
        public User GetUser(int userId)
        {
            return PerformServiceOperation(client => client.GetUser(userId));
        }
    
        //you can also check if any error occured if you can't throw exceptions       
        public bool TryGetUser(int userId, out User user)
        {
            return TryPerformServiceOperation(c => c.GetUser(userId), out user);
        }
    }
    
  • 0

    如果您不需要IoC或正在使用自动生成的客户端(服务引用),那么您可以简单地使用包装器来管理关闭,并让GC在客户端处于安全状态时获取不会引发任何异常的情况 . GC将在serviceclient中调用Dispose,这将调用 Close . 由于它已被封闭,因此不会造成任何损害 . 我在 生产环境 代码中使用它没有问题 .

    public class AutoCloseWcf : IDisposable
    {
    
        private ICommunicationObject CommunicationObject;
    
        public AutoDisconnect(ICommunicationObject CommunicationObject)
        {
            this.CommunicationObject = CommunicationObject;
        }
    
        public void Dispose()
        {
            if (CommunicationObject == null)
                return;
            try {
                if (CommunicationObject.State != CommunicationState.Faulted) {
                    CommunicationObject.Close();
                } else {
                    CommunicationObject.Abort();
                }
            } catch (CommunicationException ce) {
                CommunicationObject.Abort();
            } catch (TimeoutException toe) {
                CommunicationObject.Abort();
            } catch (Exception e) {
                CommunicationObject.Abort();
                //Perhaps log this
    
            } finally {
                CommunicationObject = null;
            }
        }
    }
    

    然后,当您访问服务器时,您创建客户端并在autodisconect中使用 using

    var Ws = new ServiceClient("netTcpEndPointName");
    using (new AutoCloseWcf(Ws)) {
    
        Ws.Open();
    
        Ws.Test();
    }
    
  • 0

    What is this?

    这是已接受答案的CW版本,但(我认为完整)包括异常处理 .

    接受的答案引用this website that is no longer around . 为了省去麻烦,我在这里列出了最相关的部分 . 另外,我稍微修改了它以包含exception retry handling来处理那些讨厌的网络超时 .

    Simple WCF Client Usage

    生成客户端代理后,这就是实现它所需的全部内容 .

    Service<IOrderService>.Use(orderService=>
    {
      orderService.PlaceOrder(request);
    });
    

    ServiceDelegate.cs

    将此文件添加到您的解决方案中除非您想要更改重试次数或更改次数,否则不需要对此文件进行任何更改你想要处理的例外 .

    public delegate void UseServiceDelegate<T>(T proxy);
    
    public static class Service<T>
    {
        public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 
    
        public static void Use(UseServiceDelegate<T> codeBlock)
        {
            IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
            bool success = false;
    
    
           Exception mostRecentEx = null;
           int millsecondsToSleep = 1000;
    
           for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
           {
               try
               {
                   codeBlock((T)proxy);
                   proxy.Close();
                   success = true; 
                   break;
               }
    
               // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
               catch (ChannelTerminatedException cte)
               {
                  mostRecentEx = cte;
                   proxy.Abort();
                   //  delay (backoff) and retry 
                   Thread.Sleep(millsecondsToSleep  * (i + 1)); 
               }
    
               // The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
               // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
               catch (EndpointNotFoundException enfe)
               {
                  mostRecentEx = enfe;
                   proxy.Abort();
                   //  delay (backoff) and retry 
                   Thread.Sleep(millsecondsToSleep * (i + 1)); 
               }
    
               // The following exception that is thrown when a server is too busy to accept a message.
               catch (ServerTooBusyException stbe)
               {
                  mostRecentEx = stbe;
                   proxy.Abort();
    
                   //  delay (backoff) and retry 
                   Thread.Sleep(millsecondsToSleep * (i + 1)); 
               }
               catch (TimeoutException timeoutEx)
               {
                   mostRecentEx = timeoutEx;
                   proxy.Abort();
    
                   //  delay (backoff) and retry 
                   Thread.Sleep(millsecondsToSleep * (i + 1)); 
               } 
               catch (CommunicationException comException)
               {
                   mostRecentEx = comException;
                   proxy.Abort();
    
                   //  delay (backoff) and retry 
                   Thread.Sleep(millsecondsToSleep * (i + 1)); 
               }
               catch(Exception )
               {
                    // rethrow any other exception not defined here
                    // You may want to define a custom Exception class to pass information such as failure count, and failure type
                    proxy.Abort();
                    throw ;  
               }
           }
           if (success == false && mostRecentEx != null) 
           { 
               proxy.Abort();
               throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
           }
    
        }
    }
    

    PS:我已将此帖发布为社区维基 . 我不会从这个答案中收集“积分”,但如果您同意该实施,或者编辑它以使其更好,则您更愿意支持它 .

  • 8

    我想在Marc Gravell's answer中添加Service的实现,以便使用ServiceClient而不是ChannelFactory .

    public interface IServiceConnector<out TServiceInterface>
    {
        void Connect(Action<TServiceInterface> clientUsage);
        TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage);
    }
    
    internal class ServiceConnector<TService, TServiceInterface> : IServiceConnector<TServiceInterface>
        where TServiceInterface : class where TService : ClientBase<TServiceInterface>, TServiceInterface, new()
    {
        public TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage)
        {
            var result = default(TResult);
            Connect(channel =>
            {
                result = channelUsage(channel);
            });
            return result;
        }
    
        public void Connect(Action<TServiceInterface> clientUsage)
        {
            if (clientUsage == null)
            {
                throw new ArgumentNullException("clientUsage");
            }
            var isChanneldClosed = false;
            var client = new TService();
            try
            {
                clientUsage(client);
                client.Close();
                isChanneldClosed = true;
            }
            finally
            {
                if (!isChanneldClosed)
                {
                    client.Abort();
                }
            }
        }
    }
    
  • 0

    这是Microsoft推荐的处理WCF客户端调用的方法:

    有关详细信息,请参阅:Expected Exceptions

    try
    {
        ...
        double result = client.Add(value1, value2);
        ...
        client.Close();
    }
    catch (TimeoutException exception)
    {
        Console.WriteLine("Got {0}", exception.GetType());
        client.Abort();
    }
    catch (CommunicationException exception)
    {
        Console.WriteLine("Got {0}", exception.GetType());
        client.Abort();
    }
    

    Additional information 很多人似乎在WCF上问这个问题,微软甚至创建了一个专门的示例来演示如何处理异常:

    C:\ WF_WCF_Samples \ WCF \基本\客户端\ ExpectedExceptions \ CS \客户端

    下载示例:C#VB

    考虑到在这个问题上有很多问题involving the using statement(heated?) Internal discussionsthreads,我只是把它搞砸了,并为我的服务器应用程序实现了这种冗长(但可信)的方式的WCF客户端 .

    Optional Additional Failures to catch

    许多例外来自 CommunicationException ,我认为大多数例外都不应该重审 . 我在MSDN上浏览了每个例外,并找到了一个可重试异常的简短列表(除了上面的 TimeOutException ) . 如果我错过了应该重试的异常,请告诉我 .

    // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
    catch (ChannelTerminatedException cte)
    {
    secureSecretService.Abort();
    // todo: Implement delay (backoff) and retry
    }
    
    // The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
    // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
    catch (EndpointNotFoundException enfe)
    {
    secureSecretService.Abort();
    // todo: Implement delay (backoff) and retry
    }
    
    // The following exception that is thrown when a server is too busy to accept a message.
    catch (ServerTooBusyException stbe)
    {
    secureSecretService.Abort();
    // todo: Implement delay (backoff) and retry
    }
    

    不可否认,这是一些平凡的代码 . 我目前更喜欢this answer,并且在该代码中看不到任何可能导致问题的"hacks" .

  • 10

    您还可以使用 DynamicProxy 扩展 Dispose() 方法 . 这样你可以做类似的事情:

    using (var wrapperdProxy = new Proxy<yourProxy>())
    {
       // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
    }
    
  • -2

    我使用Castle动态代理来解决Dispose()问题,并且当它处于不可用状态时也实现了自动刷新通道 . 要使用它,您必须创建一个继承服务 Contract 和IDisposable的新接口 . 动态代理实现此接口并包装WCF通道:

    Func<object> createChannel = () =>
        ChannelFactory<IHelloWorldService>
            .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
    var factory = new WcfProxyFactory();
    var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
    proxy.HelloWorld();
    

    我喜欢这个,因为您可以注入WCF服务而无需消费者担心WCF的任何细节 . 并没有像其他解决方案那样增加任何瑕疵 .

    看看代码,它实际上非常简单:WCF Dynamic Proxy

  • 1

    使用扩展方法:

    public static class CommunicationObjectExtensions
    {
        public static TResult MakeSafeServiceCall<TResult, TService>(this TService client, Func<TService, TResult> method) where TService : ICommunicationObject
        {
            TResult result;
    
            try
            {
                result = method(client);
            }
            finally
            {
                try
                {
                    client.Close();
                }
                catch (CommunicationException)
                {
                    client.Abort(); // Don't care about these exceptions. The call has completed anyway.
                }
                catch (TimeoutException)
                {
                    client.Abort(); // Don't care about these exceptions. The call has completed anyway.
                }
                catch (Exception)
                {
                    client.Abort();
                    throw;
                }
            }
    
            return result;
        }
    }
    
  • 1

    我在这篇文章中提到了几个答案,并根据我的需要进行了定制 .

    我希望能够在使用之前使用WCF客户端执行某些操作,以便 DoSomethingWithClient() 方法 .

    public interface IServiceClientFactory<T>
    {
        T DoSomethingWithClient();
    }
    public partial class ServiceClient : IServiceClientFactory<ServiceClient>
    {
        public ServiceClient DoSomethingWithClient()
        {
            var client = this;
            // do somthing here as set client credentials, etc.
            //client.ClientCredentials = ... ;
            return client;
        }
    }
    

    这是辅助类:

    public static class Service<TClient>
        where TClient : class, ICommunicationObject, IServiceClientFactory<TClient>, new()
    {
        public static TReturn Use<TReturn>(Func<TClient, TReturn> codeBlock)
        {
            TClient client = default(TClient);
            bool success = false;
            try
            {
                client = new TClient().DoSomethingWithClient();
                TReturn result = codeBlock(client);
                client.Close();
                success = true;
                return result;
            }
            finally
            {
                if (!success && client != null)
                {
                    client.Abort();
                }
            }
        }
    }
    

    我可以用它作为:

    string data = Service<ServiceClient>.Use(x => x.GetData(7));
    
  • 29

    摘要

    使用本答案中描述的技术,可以使用以下语法在using块中使用WCF服务:

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

    您当然可以进一步调整它以实现特定于您的情况的更简洁的编程模型 - 但重点是我们可以创建 IMyService 的实现来表示正确实现一次性模式的通道 .


    详情

    到目前为止给出的所有答案都解决了在 IDisposable 的WCF Channels 实现中绕过"bug"的问题 . 似乎提供最简洁的编程模型(允许您使用 using 块来处理非托管资源)的答案是this one - 修改代理以实现 IDisposable 并实现无错误的实现 . 这种方法的问题是可维护性 - 我们必须为我们使用的代理重新实现此功能 . 在这个答案的变体中,我们将看到我们如何使用组合而不是继承来使这种技术变得通用 .

    第一次尝试

    似乎有各种各样的 IDisposable 实现实现,但为了论证,我们将使用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();
        }
    }
    

    有了上面的课程,我们现在可以写了

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

    这允许我们使用 using 块来使用我们的服务:

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

    使这个通用

    到目前为止我们所做的只是重新制定Tomas' solution . 阻止此代码通用的是,必须为我们想要的每个服务 Contract 重新实现 ProxyWrapper 类 . 我们现在将看一个允许我们使用IL动态创建此类型的类:

    public class ServiceHelper<T>
    {
        private readonly ChannelFactory<T> _channelFactory;
    
        private static readonly Func<T, IDisposable, T> _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<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));
    
        }
    
        public ServiceHelper(ChannelFactory<T> 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();
        }
    }
    

    通过我们的新助手类,我们现在可以编写

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

    请注意,对于继承 ClientBase<> (而不是使用 ChannelFactory<> )的自动生成的客户端,或者如果要使用 IDisposable 的其他实现来关闭通道,您也可以使用相同的技术(稍作修改) .

  • 0

    我有一个自己的包装器,用于实现Dispose的通道,如下所示:

    public void Dispose()
    {
            try
            {
                if (channel.State == CommunicationState.Faulted)
                {
                    channel.Abort();
                }
                else
                {
                    channel.Close();
                }
            }
            catch (CommunicationException)
            {
                channel.Abort();
            }
            catch (TimeoutException)
            {
                channel.Abort();
            }
            catch (Exception)
            {
                channel.Abort();
                throw;
            }
    }
    

    这看起来效果很好,并允许使用使用块 .

  • 3

    实际上,虽然我blogged(见Luke's answer),我认为this比我的IDisposable包装更好 . 典型代码:

    Service<IOrderService>.Use(orderService=>
    {
      orderService.PlaceOrder(request);
    });
    

    (根据评论编辑)

    由于 Use 返回void,处理返回值的最简单方法是通过捕获的变量:

    int newOrderId = 0; // need a value for definite assignment
    Service<IOrderService>.Use(orderService=>
      {
        newOrderId = orderService.PlaceOrder(request);
      });
    Console.WriteLine(newOrderId); // should be updated
    
  • 0

    我有终于找到了解决这个问题的一个坚实步骤 .

    此自定义工具扩展了WCFProxyGenerator以提供异常处理代理 . 它生成一个名为ExceptionHandlingProxy <T>的附加代理,它继承ExceptionHandlingProxyBase <T> - 后者实现了代理功能的内容 . 结果是您可以选择使用继承ClientBase <T>或ExceptionHandlingProxy <T>的默认代理,该代理封装管理通道工厂和通道的生命周期 . ExceptionHandlingProxy在“添加服务引用”对话框中尊重您对异步方法和集合类型的选择 .

    Codeplex有一个名为 Exception Handling WCF Proxy Generator 的项目 . 它基本上为Visual Studio 2008安装了一个新的自定义工具,然后使用此工具生成新的服务代理(添加服务引用) . 它有一些很好的功能来处理故障通道,超时和安全处置 . 这里有一个很棒的视频,名为ExceptionHandlingProxyWrapper,详细解释了它是如何工作的 .

    您可以安全地再次使用 Using 语句,如果通道在任何请求(TimeoutException或CommunicationException)上出现故障,Wrapper将重新初始化故障通道并重试查询 . 如果失败则会调用 Abort() 命令并处理代理并重新抛出异常 . 如果服务抛出一个 FaultException 代码,它将停止执行,代理将被安全中止,并按预期抛出正确的异常 .

  • 84

    我们的系统架构通常使用Unity IoC框架来创建ClientBase的实例,因此没有确定的方法来强制其他开发人员甚至使用 using{} 块 . 为了使它尽可能简化,我创建了这个扩展ClientBase的自定义类,并处理关闭dispose上的通道,或者在有人没有明确处理Unity创建的实例的情况下完成 .

    还有一些东西需要在构造函数中完成,以设置自定义凭据和内容的通道,所以这里也是......

    public abstract class PFServer2ServerClientBase<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
    {
        private bool disposed = false;
    
        public PFServer2ServerClientBase()
        {
            // Copy information from custom identity into credentials, and other channel setup...
        }
    
        ~PFServer2ServerClientBase()
        {
            this.Dispose(false);
        }
    
        void IDisposable.Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        public void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                try
                {
                        if (this.State == CommunicationState.Opened)
                            this.Close();
                }
                finally
                {
                    if (this.State == CommunicationState.Faulted)
                        this.Abort();
                }
                this.disposed = true;
            }
        }
    }
    

    然后客户可以简单地:

    internal class TestClient : PFServer2ServerClientBase<ITest>, ITest
    {
        public string TestMethod(int value)
        {
            return base.Channel.TestMethod(value);
        }
    }
    

    呼叫者可以执行以下任何操作:

    public SomeClass
    {
        [Dependency]
        public ITest test { get; set; }
    
        // Not the best, but should still work due to finalizer.
        public string Method1(int value)
        {
            return this.test.TestMethod(value);
        }
    
        // The good way to do it
        public string Method2(int value)
        {
            using(ITest t = unityContainer.Resolve<ITest>())
            {
                return t.TestMethod(value);
            }
        }
    }
    
  • 14

    我喜欢这种关闭连接的方式:

    var client = new ProxyClient();
    try
    {
        ...
        client.Close();
    }
    finally
    {
        if(client.State != CommunicationState.Closed)
            client.Abort();
    }
    
  • 5

    以下是来自the question的源的增强版本,并扩展为缓存多个通道工厂,并尝试按 Contract 名称在配置文件中查找 endpoints .

    它使用.NET 4(具体为:contravariance,LINQ, var ):

    /// <summary>
    /// Delegate type of the service method to perform.
    /// </summary>
    /// <param name="proxy">The service proxy.</param>
    /// <typeparam name="T">The type of service to use.</typeparam>
    internal delegate void UseServiceDelegate<in T>(T proxy);
    
    /// <summary>
    /// Wraps using a WCF service.
    /// </summary>
    /// <typeparam name="T">The type of service to use.</typeparam>
    internal static class Service<T>
    {
        /// <summary>
        /// A dictionary to hold looked-up endpoint names.
        /// </summary>
        private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();
    
        /// <summary>
        /// A dictionary to hold created channel factories.
        /// </summary>
        private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
            new Dictionary<string, ChannelFactory<T>>();
    
        /// <summary>
        /// Uses the specified code block.
        /// </summary>
        /// <param name="codeBlock">The code block.</param>
        internal static void Use(UseServiceDelegate<T> codeBlock)
        {
            var factory = GetChannelFactory();
            var proxy = (IClientChannel)factory.CreateChannel();
            var success = false;
    
            try
            {
                using (proxy)
                {
                    codeBlock((T)proxy);
                }
    
                success = true;
            }
            finally
            {
                if (!success)
                {
                    proxy.Abort();
                }
            }
        }
    
        /// <summary>
        /// Gets the channel factory.
        /// </summary>
        /// <returns>The channel factory.</returns>
        private static ChannelFactory<T> GetChannelFactory()
        {
            lock (cachedFactories)
            {
                var endpointName = GetEndpointName();
    
                if (cachedFactories.ContainsKey(endpointName))
                {
                    return cachedFactories[endpointName];
                }
    
                var factory = new ChannelFactory<T>(endpointName);
    
                cachedFactories.Add(endpointName, factory);
                return factory;
            }
        }
    
        /// <summary>
        /// Gets the name of the endpoint.
        /// </summary>
        /// <returns>The name of the endpoint.</returns>
        private static string GetEndpointName()
        {
            var type = typeof(T);
            var fullName = type.FullName;
    
            lock (cachedFactories)
            {
                if (cachedEndpointNames.ContainsKey(type))
                {
                    return cachedEndpointNames[type];
                }
    
                var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;
    
                if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
                {
                    foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                    {
                        cachedEndpointNames.Add(type, endpointName);
                        return endpointName;
                    }
                }
            }
    
            throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
        }
    }
    
  • 4

    对于那些感兴趣的人,这里是接受答案的VB.NET翻译(下面) . 为简洁起见,我对它进行了一些改进,结合了其他人的一些技巧 .

    我承认它不是原始标签(C#)的主题,但由于我无法找到这个精细解决方案的VB.NET版本,我认为其他人也会看起来 . Lambda翻译可能有点棘手,所以我想为别人省事 .

    请注意,此特定实现提供了在运行时配置 ServiceEndpoint 的功能 .


    Code:

    Namespace Service
      Public NotInheritable Class Disposable(Of T)
        Public Shared ChannelFactory As New ChannelFactory(Of T)(Service)
    
        Public Shared Sub Use(Execute As Action(Of T))
          Dim oProxy As IClientChannel
    
          oProxy = ChannelFactory.CreateChannel
    
          Try
            Execute(oProxy)
            oProxy.Close()
    
          Catch
            oProxy.Abort()
            Throw
    
          End Try
        End Sub
    
    
    
        Public Shared Function Use(Of TResult)(Execute As Func(Of T, TResult)) As TResult
          Dim oProxy As IClientChannel
    
          oProxy = ChannelFactory.CreateChannel
    
          Try
            Use = Execute(oProxy)
            oProxy.Close()
    
          Catch
            oProxy.Abort()
            Throw
    
          End Try
        End Function
    
    
    
        Public Shared ReadOnly Property Service As ServiceEndpoint
          Get
            Return New ServiceEndpoint(
              ContractDescription.GetContract(
                GetType(T),
                GetType(Action(Of T))),
              New BasicHttpBinding,
              New EndpointAddress(Utils.WcfUri.ToString))
          End Get
        End Property
      End Class
    End Namespace
    

    Usage:

    Public ReadOnly Property Jobs As List(Of Service.Job)
      Get
        Disposable(Of IService).Use(Sub(Client) Jobs = Client.GetJobs(Me.Status))
      End Get
    End Property
    
    Public ReadOnly Property Jobs As List(Of Service.Job)
      Get
        Return Disposable(Of IService).Use(Function(Client) Client.GetJobs(Me.Status))
      End Get
    End Property
    
  • 1

    如果在IServiceOriented.com倡导的解决方案和David Barret's blog倡导的解决方案之间做出选择,我更喜欢通过覆盖客户端的Dispose()方法提供的简单性 . 这允许我继续使用using()语句,就像人们对一次性对象所期望的那样 . 但是,正如@Brian指出的那样,这个解决方案包含一个竞争条件,因为状态在检查时可能不会出现故障,但可能在调用Close()时,在这种情况下仍然会发生CommunicationException .

    因此,为了解决这个问题,我采用了一种混合两全其美的解决方案 .

    void IDisposable.Dispose()
    {
        bool success = false;
        try 
        {
            if (State != CommunicationState.Faulted) 
            {
                Close();
                success = true;
            }
        } 
        finally 
        {
            if (!success) 
                Abort();
        }
    }
    
  • 1

    我这样做的方法是创建一个显式实现IDisposable的继承类 . 这对使用gui添加服务引用(添加服务引用)的人很有用 . 我只是在生成服务引用的项目中删除此类,并使用它而不是默认客户端:

    using System;
    using System.ServiceModel;
    using MyApp.MyService; // The name you gave the service namespace
    
    namespace MyApp.Helpers.Services
    {
        public class MyServiceClientSafe : MyServiceClient, IDisposable
        {
            void IDisposable.Dispose()
            {
                if (State == CommunicationState.Faulted)
                {
                    Abort();
                }
                else if (State != CommunicationState.Closed)
                {
                    Close();
                }
    
                // Further error checks and disposal logic as desired..
            }
        }
    }
    

    注意:这只是dispose的一个简单实现,如果您愿意,可以实现更复杂的dispose逻辑 .

    然后,您可以使用安全客户端替换使用常规服务客户端进行的所有调用,如下所示:

    using (MyServiceClientSafe client = new MyServiceClientSafe())
    {
        var result = client.MyServiceMethod();
    }
    

    我喜欢这个解决方案,因为它不需要我访问接口定义,我可以像我期望的那样使用 using 语句,同时允许我的代码看起来或多或少相同 .

    您仍然需要处理可以抛出的异常,如此线程中的其他注释所指出的那样 .

相关问题