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

前端 未结 26 1603
余生分开走
余生分开走 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:21

    For those interested, here's a VB.NET translation of the accepted answer (below). I've refined it a bit for brevity, combining some of the tips by others in this thread.

    I admit it's off-topic for the originating tags (C#), but as I wasn't able to find a VB.NET version of this fine solution I assume that others will be looking as well. The Lambda translation can be a bit tricky, so I'd like to save someone the trouble.

    Note that this particular implementation provides the ability to configure the ServiceEndpoint at runtime.


    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
    
    0 讨论(0)
  • 2020-11-22 00:22

    This is Microsoft's recommended way to handle WCF client calls:

    For more detail see: 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 So many people seem to be asking this question on WCF that Microsoft even created a dedicated sample to demonstrate how to handle exceptions:

    c:\WF_WCF_Samples\WCF\Basic\Client\ExpectedExceptions\CS\client

    Download the sample: C# or VB

    Considering that there are so many issues involving the using statement, (heated?) Internal discussions and threads on this issue, I'm not going to waste my time trying to become a code cowboy and find a cleaner way. I'll just suck it up, and implement WCF clients this verbose (yet trusted) way for my server applications.

    Optional Additional Failures to catch

    Many exceptions derive from CommunicationException and I don't think most of those exceptions should be retried. I drudged through each exception on MSDN and found a short list of retry-able exceptions (in addition to TimeOutException above). Do let me know if I missed an exception that should be retried.

      // 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
    }
    

    Admittedly, this is a bit of mundane code to write. I currently prefer this answer, and don't see any "hacks" in that code that may cause issues down the road.

    0 讨论(0)
  • 2020-11-22 00:25

    Based on answers by Marc Gravell, MichaelGG, and Matt Davis, our developers came up with the following:

    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();
                }
            }
        }
    }
    

    Example of use:

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

    It's as close to the "using" syntax as possible, you don't have to return a dummy value when calling a void method, and you can make multiple calls to the service (and return multiple values) without having to use tuples.

    Also, you can use this with ClientBase<T> descendants instead of ChannelFactory if desired.

    The extension method is exposed if a developer wants to manually dispose of a proxy/channel instead.

    0 讨论(0)
  • 2020-11-22 00:27

    I wrote a higher order function to make it work right. We've used this in several projects and it seems to work great. This is how things should have been done from the start, without the "using" paradigm or so on.

    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();
            }
        }
    }
    

    You can make calls like this:

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

    This is pretty much just like you have in your example. In some projects, we write strongly typed helper methods, so we end up writing things like "Wcf.UseFooService(f=>f...)".

    I find it quite elegant, all things considered. Is there a particular problem you encountered?

    This allows other nifty features to be plugged in. For instance, on one site, the site authenticates to the service on behalf of the logged in user. (The site has no credentials by itself.) By writing our own "UseService" method helper, we can configure the channel factory the way we want, etc. We're also not bound to using the generated proxies -- any interface will do.

    0 讨论(0)
  • 2020-11-22 00:29

    You could also use a DynamicProxy to extend the Dispose() method. This way you could do something like:

    using (var wrapperdProxy = new Proxy<yourProxy>())
    {
       // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
    }
    
    0 讨论(0)
  • 2020-11-22 00:31

    I have my own wrapper for a channel which implements Dispose as follows:

    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;
            }
    }
    

    This seems to work well and allows a using block to be used.

    0 讨论(0)
提交回复
热议问题