¿Cuál es la mejor solución para el problema de bloque `usando ' del cliente WCF?


Me gusta crear instancias de mis clientes de servicio WCF dentro de un bloque using ya que es más o menos la forma estándar de usar los recursos que implementan IDisposable:

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

Pero, como se indica en este artículo de MSDN, envolver un cliente WCF en un bloque using podría enmascarar cualquier error que resulte en que el cliente quede en un estado fallido (como un tiempo de espera o un problema de comunicación). En resumen, cuando se llama a Dispose (), el método Close() del cliente se dispara, pero lanza un error porque está en un estado fallido. La excepción original es entonces enmascarada por la segunda excepción. No es bueno.

La solución sugerida en el artículo de MSDN es evitar completamente el uso de un bloque using, y en su lugar crear instancias de sus clientes y usarlos algo como esto:

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

Comparado con el bloque using, creo que es feo. Y mucho código para escribir cada vez que necesites un cliente.

Afortunadamente, encontré algunas otras soluciones, como esta en IServiceOriented. Tú empiezas con:

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

Que luego permite:

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

Eso no está mal, pero no creo que sea tan expresivo y fácilmente comprensible como el bloque using.

La solución que actualmente estoy tratando de usar Leí por primera vez sobre blog.davidbarret.net . Básicamente sobrescribes el método Dispose() del cliente dondequiera que lo uses. Algo como:

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

Esto parece ser capaz de permitir el bloque using de nuevo sin el peligro de enmascarar una excepción de estado fallido.

Entonces, ¿hay algún otro truco que tenga que buscar para usar estas soluciones? ¿A alguien se le ocurrió algo mejor?

Author: live2, 2009-02-22

26 answers

En Realidad, aunque yo blogs (ver Lucas la respuesta de), creo que este es mejor que mi IDisposable contenedor. Código típico:

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

(editar por comentarios)

Dado que Use devuelve void, la forma más fácil de manejar los valores devueltos es a través de una variable capturada:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated
 127
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:12

Dada una opción entre la solución defendida por IServiceOriented.com y la solución defendida por El blog de David Barret, prefiero la simplicidad ofrecida al anular el método Dispose() del cliente. Esto me permite continuar usando la instrucción using () como uno esperaría con un objeto desechable. Sin embargo, como @ Brian señaló, esta solución contiene una condición de carrera en la que el Estado podría no tener errores cuando se comprueba, pero podría ser para el momento en que se llama a Close (), en en qué caso la excepción de comunicación todavía ocurre.

Así que, para evitar esto, he empleado una solución que mezcla lo mejor de ambos mundos.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}
 84
Author: Matt Davis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-17 18:48:03

Escribí una función de orden superior para que funcionara bien. Hemos utilizado esto en varios proyectos y parece funcionar muy bien. Así es como se deberían haber hecho las cosas desde el principio, sin el paradigma de "usar" o algo así.

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

Puedes hacer llamadas como esta:

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

Esto es más o menos como lo tienes en tu ejemplo. En algunos proyectos, escribimos métodos de ayuda fuertemente tipeados, por lo que terminamos escribiendo cosas como "Wcf.UseFooService (f= > f...)".

Lo encuentro bastante elegante, considerándolo todo. ¿Hay algún problema en particular que haya encontrado?

Esto permite conectar otras características ingeniosas. Por ejemplo, en un sitio, el sitio se autentica en el servicio en nombre del usuario que ha iniciado sesión. (El sitio no tiene credenciales por sí mismo.) Escribiendo nuestro propio ayudante de método "UseService", podemos configurar la fábrica de canales de la manera que queramos, etc. Tampoco estamos obligados a usar los proxies generados any cualquier interfaz servirá.

 28
Author: MichaelGG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-10 06:21:24

Esta es la forma recomendada de Microsoft para manejar las llamadas de cliente WCF:

Para más detalles ver: Excepciones esperadas

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

Información Adicional Tanta gente parece estar haciendo esta pregunta en WCF que Microsoft incluso creó una muestra dedicada para demostrar cómo manejar las excepciones:

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

Descargue la muestra: C# o VB

Considerando que hay tantos problemas que involucran la instrucción using, (¿calentada?) Discusiones internas y hilos en este tema, no voy a perder mi tiempo tratando de convertirme en un vaquero de código y encontrar una manera más limpia. Simplemente lo soportaré e implementaré clientes WCF de esta manera detallada (pero confiable) para mis aplicaciones de servidor.

Fallos adicionales opcionales de captura

Muchas excepciones derivan de CommunicationException y no creo que la mayoría de esas excepciones deberían ser juzgado de nuevo. Me esforcé a través de cada excepción en MSDN y encontré una breve lista de excepciones capaces de reintentar (además de TimeOutException anterior). Hágamelo saber si me perdí una excepción que debe ser juzgado de nuevo.

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

Ciertamente, esto es un poco de código mundano para escribir. Actualmente prefiero esta respuesta, y no veo ningún "hacks" en ese código que pueda causar problemas en el futuro.

 26
Author: random65537,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:24

Finalmente he encontrado algunos pasos sólidos hacia una solución limpia a este problema.

Esta herramienta personalizada extiende WCFProxyGenerator para proporcionar un proxy de manejo de excepciones. Genera un proxy adicional llamado ExceptionHandlingProxy<T> que hereda ExceptionHandlingProxyBase<T> - el último de los cuales implementa la carne de la funcionalidad del proxy. El resultado es que puede elegir usar el proxy predeterminado que hereda ClientBase<T> o ExceptionHandlingProxy<T> que encapsula la administración de la vida útil de la fábrica de canales y el canal. ExceptionHandlingProxy respeta sus selecciones en el cuadro de diálogo Agregar referencia de servicio con respecto a los métodos asíncronos y los tipos de colección.

Codeplex tiene un proyecto llamado Exception Handling WCF Proxy Generator . Básicamente instala una nueva herramienta personalizada en Visual Studio 2008, luego usa esta herramienta para generar el nuevo proxy de servicio (Agregar referencia de servicio). Tiene una buena funcionalidad para lidiar con canales defectuosos, tiempos de espera y eliminación segura. Hay un excelente video aquí llamado ExceptionHandlingProxyWrapper explicando exactamente cómo funciona esto.

Puede usar de forma segura la instrucción Using de nuevo, y si el canal tiene errores en cualquier solicitud (TimeoutException o CommunicationException), el Wrapper reiniciará el canal con errores y volverá a intentar la consulta. Si eso falla, entonces llamará al comando Abort() y dispondrá del proxy y volverá a generar la Excepción. Si el servicio lanza un código FaultException dejará de ejecutarse, y el proxy se abortará de forma segura lanzando la excepción correcta como se esperaba.

 14
Author: Neil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-26 16:40:58

Basado en las respuestas de Marc Gravell, MichaelGG y Matt Davis, nuestros desarrolladores crearon lo siguiente:

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

Ejemplo de uso:

string result = string.Empty;

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

Es lo más cercano posible a la sintaxis "using", no tiene que devolver un valor ficticio al llamar a un método void, y puede hacer varias llamadas al servicio (y devolver varios valores) sin tener que usar tuplas.

También, puede usar esto con ClientBase<T> descendientes en lugar de ChannelFactory si lo desea.

El el método de extensión está expuesto si un desarrollador quiere deshacerse manualmente de un proxy/canal en su lugar.

 10
Author: TrueWill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-24 20:41:27

@Marc Gravell

No estaría bien usar esto:

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

O, lo mismo (Func<T, TResult>) en el caso de Service<IOrderService>.Use

Esto facilitaría el retorno de variables.

 8
Author: codeRecap,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-02 15:16:56

¿Qué es esto?

Esta es la versión CW de la respuesta aceptada pero con (lo que considero completo) Manejo de excepciones incluido.

La respuesta aceptada hace referencia a este sitio web que ya no existe. Para ahorrarle problemas, estoy incluyendo las partes más relevantes aquí. Además, lo modifiqué ligeramente para incluir el manejo de reintentos de excepción para manejar esos molestos tiempos de espera de la red.

Uso simple del Cliente WCF

Una vez usted genera su proxy del lado del cliente, esto es todo lo que necesita para implementarlo.

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

ServiceDelegate.cs

Agregue este archivo a su solución. No se necesitan cambios en este archivo, a menos que desee alterar el número de reintentos o las excepciones que desea manejar.

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

    }
}

PD: He hecho de este post un wiki de la comunidad. No voy a recoger "puntos" de esta respuesta, pero prefiero que vote si está de acuerdo con la implementación, o editarlo para hacerlo mejor.

 7
Author: LamonteCristo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:11

A continuación se muestra una versión mejorada de la fuente de la pregunta y extendida para almacenar en caché fábricas de múltiples canales e intentar buscar el punto final en el archivo de configuración por nombre de contrato.

Utiliza. NET 4 (específicamente: 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.");
    }
}
 7
Author: Jesse C. Slicer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:03:02

Una envoltura como esta funcionaría:

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

Eso debería permitirle escribir código como:

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

El wrapper podría, por supuesto, capturar más excepciones si es necesario, pero el principio sigue siendo el mismo.

 5
Author: Tomas Jansson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-26 16:43:08

Usé Castle dynamic proxy para resolver el problema Dispose (), y también implementé la actualización automática del canal cuando está en un estado inutilizable. Para usar esto debe crear una nueva interfaz que hereda su contrato de servicio e identificable. El proxy dinámico implementa esta interfaz y envuelve un canal WCF:

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

Me gusta esto, ya que puede inyectar servicios WCF sin que los consumidores tengan que preocuparse por ningún detalle de WCF. Y no hay un cruft añadido como el otro solución.

Echa un vistazo al código, en realidad es bastante simple: WCF Dynamic Proxy

 4
Author: Jay Douglass,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-06 05:29:58

Si no necesita IoC o está utilizando un cliente autogenerado (Referencia de servicio), entonces puede usar un envoltorio para administrar el cierre y dejar que el GC tome la base de clientes cuando esté en un estado seguro que no genere ninguna excepción. El GC llamará a Dispose en serviceclient, y este llamará Close. Dado que ya está cerrado, no puede causar ningún daño. Estoy usando esto sin problemas en el código de producción.

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

Entonces cuando usted está accediendo al servidor, usted cree el cliente y use using en la autodisconección:

var Ws = new ServiceClient("netTcpEndPointName");
using (new AutoCloseWcf(Ws)) {

    Ws.Open();

    Ws.Test();
}
 4
Author: Luiz Felipe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-26 16:53:10

Utilice un método de extensión:

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;
    }
}
 3
Author: Johan Nyman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-09 08:47:10

Resumen

Usando las técnicas descritas en esta respuesta uno puede consumir un servicio WCF en un bloque using con la siguiente sintaxis:

var channelFactory = new ChannelFactory<IMyService>("");

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

Por supuesto, puede adaptar esto aún más para lograr un modelo de programación más conciso específico para su situación, pero el punto es que podemos crear una implementación de IMyService reprenting el canal que implementa correctamente el patrón desechable.


Detalles

Todas las respuestas dadas hasta ahora abordar el problema de sortear el" bug " en la implementación del canal WCF de IDisposable. La respuesta que parece ofrecer el modelo de programación más conciso (que le permite usar el bloque using para disponer de recursos no administrados) es este - donde el proxy se modifica para implementar IDisposable con una implementación libre de errores. El problema con este enfoque es la capacidad de mantenimiento: tenemos que volver a implementar esta funcionalidad para cada proxy que utilicemos. En una variación de esta respuesta veremos cómo podemos usar composición en lugar de herencia para hacer esta técnica genérica.

Primer intento

Parece que hay varias implementaciones para la implementación IDisposable, pero por el bien del argumento utilizaremos una adaptación de la utilizada por la respuesta actualmente aceptada.

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

Armado con las clases anteriores, ahora podemos escribir

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

Esto nos permite consumir nuestro servicio usando el bloque using:

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

Haciendo esto genérico

Todo lo que hemos hecho hasta ahora es reformular la solución de Tomás. Lo que impide que este código sea genérico es el hecho de que la clase ProxyWrapper tiene que ser re-implementada para cada contrato de servicio que queramos. Ahora veremos una clase que nos permite crear este tipo dinámicamente usando 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();
    }
}

Con nuestra nueva clase helper ahora podemos escribir{[19]]}

var channelFactory = new ChannelFactory<IMyService>("");

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

Tenga en cuenta que también podría usar la misma técnica (con ligeras modificaciones) para clientes generados automáticamente heredar para ClientBase<> (en lugar de usar ChannelFactory<>), o si desea utilizar una implementación diferente de IDisposable para cerrar su canal.

 3
Author: Lawrence,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:07

Me gusta esta forma de cerrar la conexión:

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}
 2
Author: Uriil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-07 07:07:59

He escrito una simple clase base que maneja esto. Está disponible como un paquete NuGet y es bastante fácil de usar.

//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);
    }
}
 1
Author: Ufuk Hacıoğulları,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-15 22:20:54
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();
            }
        }
    }
}

Así que permite escribir sentencias return muy bien:

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 
 1
Author: Andriy Buday,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-08 16:22:56

Me gustaría añadir la implementación del Servicio desde La respuesta de Marc Gravell para el caso de usar ServiceClient en lugar de 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();
            }
        }
    }
}
 1
Author: PSsam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:03:02

Para aquellos interesados, aquí hay un VB.NET traducción de la respuesta aceptada (abajo). Lo he refinado un poco para la brevedad, combinando algunos de los consejos de otros en este hilo.

Admito que está fuera de tema para las etiquetas de origen (C#), pero como no pude encontrar una VB.NET versión de esta solución fina asumo que otros estarán mirando también. La traducción de Lambda puede ser un poco complicada, así que me gustaría ahorrarle a alguien el problema.

Tenga en cuenta que esta implementación particular proporciona la capacidad de configurar ServiceEndpoint en tiempo de ejecución.


Código:

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

Uso:

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
Author: InteXX,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-07 04:59:56

Nuestra arquitectura de sistema a menudo utiliza la unidad IOC framework para crear instancias de ClientBase por lo que no hay una manera segura de imponer que los otros desarrolladores incluso usen bloques using{}. Para que sea lo más infalible posible, hice esta clase personalizada que extiende ClientBase y maneja el cierre del canal en dispose, o en finalize en caso de que alguien no disponga explícitamente de la instancia creada por Unity.

También hay cosas que se necesitan hacer en el constructor para configurar el canal para credenciales personalizadas y cosas, así que eso está aquí también...

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

Entonces un cliente puede simplemente:

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

Y la persona que llama puede hacer cualquiera de estas:

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);
        }
    }
}
 1
Author: CodingWithSpike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-26 16:36:19

Me referí a algunas respuestas en este post y lo personalizé según mis necesidades.

Quería la capacidad de hacer algo con el cliente WCF antes de usarlo, por lo que el método 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;
    }
}

Aquí está la clase helper:

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

Y puedo usarlo como:

string data = Service<ServiceClient>.Use(x => x.GetData(7));
 0
Author: hIpPy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-16 06:53:23

Tengo mi propia envoltura para un canal que los implementos Desechan de la siguiente manera:

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

Esto parece funcionar bien y permite usar un bloque de uso.

 0
Author: Joe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-15 12:13:46

El siguiente helper permite llamar a los métodos void y no-void. Uso:

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

La clase misma es:

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;
        });
    }
}
 0
Author: Konstantin Spirin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-23 01:48:35

Invalida la función Dispose() del cliente sin la necesidad de generar una clase proxy basada en ClientBase, también sin la necesidad de administrar la creación de canales y el almacenamiento en caché! (Tenga en cuenta que WcfClient no es una clase ABSTRACTA y se basa en 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();
    }
}
 0
Author: Murad Duraidi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-07 11:25:06

Mi método para hacer esto ha sido crear una clase heredada que implemente explícitamente identificable. Esto es útil para las personas que usan la gui para agregar la referencia de servicio ( Add Service Reference ). Simplemente dejo esta clase en el proyecto haciendo la referencia del servicio y la uso en lugar del cliente predeterminado:

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

Nota: Esto es solo una implementación simple de dispose, puede implementar una lógica de dispose más compleja si lo desea.

A continuación, puede reemplazar todas las llamadas realizadas con el cliente de servicio regular con los clientes seguros, así:

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

Me gusta esta solución, ya que no requiere que tenga acceso a las definiciones de la Interfaz y puedo usar la instrucción using como era de esperar mientras que permite que mi código se vea más o menos igual.

Todavía tendrá que manejar las excepciones que se pueden lanzar como se señala en otros comentarios en este hilo.

 0
Author: Aleksandr Albert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-28 19:30:39

También podría usar un DynamicProxy para extender el método Dispose(). De esta manera usted podría hacer algo como:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
 -2
Author: Uri Abramson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-01 15:55:23