¿Cuál es la mejor solución para el problema de bloqueo del cliente WCF `using`?

404

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

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

Pero, como se señaló en este artículo de MSDN , envolver un cliente WCF en un usingbloque podría enmascarar cualquier error que provoque que el cliente quede en un estado defectuoso (como un tiempo de espera o un problema de comunicación). En pocas palabras, cuando se llama a Dispose (), el método Close () del cliente se dispara, pero arroja un error porque está en un estado defectuoso. La excepción original queda enmascarada por la segunda excepción. No está bien.

La solución sugerida en el artículo de MSDN es evitar por completo el uso de un usingbloque y, en su lugar, crear una instancia de sus clientes y usarlos de la siguiente manera:

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

En comparación con el usingbloque, creo que es feo. Y mucho código para escribir cada vez que necesite un cliente.

Afortunadamente, encontré algunas otras soluciones, como esta en IServiceOriented. 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(); 
            } 
        } 
     } 
} 

Lo que luego permite:

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

Eso no está mal, pero no creo que sea tan expresivo y fácil de entender como el usingbloque.

La solución que estoy tratando de usar actualmente la leí por primera vez en blog.davidbarret.net . Básicamente, usted anula el Dispose()método del cliente donde sea que lo use. 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 usingbloqueo nuevamente sin el peligro de enmascarar una excepción de estado con falla.

Entonces, ¿hay otras trampas que deba tener en cuenta al usar estas soluciones? ¿Alguien ha encontrado algo mejor?

Eric King
fuente
42
El último (que inspecciona esto. Estado) es una carrera; es posible que no tenga errores cuando verifica el booleano, pero puede tener errores cuando llama a Cerrar ().
Brian
15
Lees estado; No tiene fallas. Antes de llamar a Cerrar (), el canal falla. Cerrar () tiros. Juego terminado.
Brian
44
El tiempo pasa. Puede ser un período de tiempo muy corto, pero técnicamente, en el período de tiempo entre verificar el estado del canal y pedirle que se cierre, el estado del canal puede cambiar.
Eric King
8
Yo usaría en Action<T>lugar de UseServiceDelegate<T>. menor.
FELIZ
2
Realmente no me gusta este ayudante estático, Service<T>ya que complica las pruebas unitarias (como hacen la mayoría de las cosas estáticas). Preferiría que no sea estático para que pueda inyectarse en la clase que lo está usando.
Fabio Marreco

Respuestas:

137

En realidad, aunque escribí en un blog (ver la respuesta de Luke ), creo que esto es mejor que mi envoltorio desechable ID. Código típico:

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

(editar por comentarios)

Dado que Usedevuelve vacío, la forma más fácil de manejar los valores de retorno 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
Marc Gravell
fuente
2
@MarcGravell ¿Dónde podría inyectar ese cliente? Supongo que ChannelFactory crea el cliente, y el objeto de fábrica se actualiza dentro de la clase de Servicio, lo que significa que el código debe ser refactorizado un poco para permitir una fábrica personalizada. ¿Es correcto o me estoy perdiendo algo obvio aquí?
Anttu
16
Puede modificar fácilmente el contenedor para que no necesite una variable de captura para el resultado. Algo como esto: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
Chris
3
Tal vez útil https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/ y https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/ e http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón
¿Cómo puedo agregar credenciales de esta manera?
Hippasus
2
En mi opinión, la solución más correcta sería: 1) Realizar el patrón Cerrar / Anular sin una condición de carrera 2) Manejar la situación cuando la operación de servicio arroja excepciones 3) Manejar las situaciones en que los métodos Cerrar y Abortar arrojan excepciones 4) Manejar excepciones asincrónicas como ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet
88

Se puede elegir entre la solución recomendada por IServiceOriented.com y la solución recomendada por el blog de David Barret , prefiero la simplicidad ofrecida al anular el método Dispose () del cliente. Esto me permite seguir usando la instrucción using () como cabría esperar con un objeto desechable. Sin embargo, como señaló @Brian, esta solución contiene una condición de carrera en la que el Estado podría no tener una falla cuando se verifica, pero podría ser para cuando se llame a Close (), en cuyo caso todavía se produce CommunicationException.

Entonces, para evitar esto, he empleado una solución que combina lo mejor de ambos mundos.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}
Matt Davis
fuente
2
¿no es arriesgado usar la declaración 'Try-Later' (o el azúcar sintáctico - "using () {}") con recursos no administrados? Caso en cuestión, si la opción "Cerrar" falla, la excepción no se detecta y finalmente no se puede ejecutar. Además, si hay una excepción en la instrucción finally, puede enmascarar otras excepciones. Creo que es por eso que se prefiere Try-Catch.
Zack Jannsen
Zack, no está claro en tu objeto; ¿Qué me estoy perdiendo? Si el método Cerrar produce una excepción, el bloque finalmente se ejecutará antes de que se produzca la excepción. ¿Derecha?
Patrick Szalapski
1
@ jmoreno, deshice su edición. Si te das cuenta, no hay ningún bloqueo en absoluto en el método. La idea es que cualquier excepción que ocurra (incluso en el final) debe ser lanzada, no capturada en silencio.
Matt Davis
55
@MattDavis ¿Por qué necesitas una successbandera? ¿Por qué no try { Close(); } catch { Abort(); throw; }?
Konstantin Spirin
¿Qué pasa con probar / atrapar Close(); success = true;? No quisiera que se lanzara una excepción si pudiera abortarla con éxito en el bloque finalmente. Solo quisiera que se lanzara una excepción si Abort () fallara en ese caso. De esta forma, el try / catch ocultaría la posible excepción de condición de carrera y aún le permitiría abortar () la conexión en el bloque finalmente.
goku_da_master
32

Escribí una función de orden superior para que funcione correctamente. Lo hemos usado en varios proyectos y parece funcionar muy bien. Así es como deberían haberse hecho las cosas desde el principio, sin el paradigma de "uso", etc.

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 muy parecido a lo que tienes en tu ejemplo. En algunos proyectos, escribimos métodos auxiliares fuertemente tipados, por lo que terminamos escribiendo cosas como "Wcf.UseFooService (f => f ...)".

Me parece bastante elegante, considerando todas las cosas. ¿Hay algún problema en particular que encontraste?

Esto permite que se conecten otras funciones ingeniosas. Por ejemplo, en un sitio, el sitio se autentica en el servicio en nombre del usuario conectado. (El sitio no tiene credenciales por sí mismo). Al escribir 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; cualquier interfaz servirá .

MichaelGG
fuente
Recibo una excepción: la propiedad Address en ChannelFactory.Endpoint era nula. El punto final de ChannelFactory debe tener una dirección válida especificada . ¿Qué es el GetCachedFactorymétodo?
Marshall
28

Esta es la forma recomendada por Microsoft para manejar las llamadas de clientes 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 Muchas personas parecen 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

Teniendo en cuenta que hay tantos problemas relacionados con el uso de la declaración , (¿acalorado?) Debates internos e hilos sobre este tema, no voy a perder el tiempo tratando de convertirme en un vaquero del código y encontrar una forma más limpia. Simplemente lo absorberé e implementaré clientes WCF de esta manera detallada (pero confiable) para mis aplicaciones de servidor.

Fallas adicionales opcionales para atrapar

Muchas excepciones se derivan CommunicationExceptiony no creo que la mayoría de esas excepciones deba ser reintentada. Revisé cada excepción en MSDN y encontré una breve lista de excepciones reintentables (además de las TimeOutExceptionanteriores). Avíseme si me perdí una excepción que debería ser reintentada.

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

Es cierto que este es un código mundano para escribir. Actualmente prefiero esta respuesta , y no veo ningún "pirateo" en ese código que pueda causar problemas en el futuro.

goodguys_activate
fuente
1
¿El código de la muestra sigue causando problemas? Intenté ejecutar el proyecto UsingUsing (VS2013) pero la línea "Hope this code wasn't important, because it might not happen."aún se ejecuta ...
janv8000
14

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 optar por utilizar el proxy predeterminado que hereda ClientBase<T>o ExceptionHandlingProxy<T>que encapsula la administración de la vida útil de la fábrica y el canal del 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. Aquí hay un excelente video llamado ExceptionHandlingProxyWrapper que explica exactamente cómo funciona esto.

Puede Usingvolver a usar la declaración de forma segura y, si el canal presenta un error en cualquier solicitud (TimeoutException o CommunicationException), el Contenedor reiniciará el canal con error y volverá a intentar la consulta. Si eso falla, llamará al Abort()comando y eliminará el proxy y volverá a lanzar la excepción. Si el servicio arroja un FaultExceptioncódigo, dejará de ejecutarse, y el proxy se cancelará de forma segura arrojando la excepción correcta como se esperaba.

Neil
fuente
@Shimmy Status Beta. Fecha: sábado 11 de julio de 2009 por Michele Bustamante . Proyecto muerto?
Kiquenet
11

En base a las respuestas de Marc Gravell, MichaelGG y Matt Davis, a nuestros desarrolladores se les ocurrió 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));

Está lo más cerca posible de la sintaxis de "uso", no tiene que devolver un valor ficticio cuando se llama a un método nulo, y puede hacer varias llamadas al servicio (y devolver múltiples valores) sin tener que usar tuplas.

Además, puede usar esto con ClientBase<T>descendientes en lugar de ChannelFactory si lo desea.

El método de extensión se expone si un desarrollador desea deshacerse manualmente de un proxy / canal.

TrueWill
fuente
Usar esto tiene sentido si estoy usando PoolingDuplex y no cierro la conexión después de una llamada, por lo que mi servicio al cliente podría vivir incluso unos pocos días y manejar las devoluciones de llamadas del servidor. Por lo que yo entiendo, ¿la solución que se discute aquí tiene sentido para una llamada por sesión?
todo el
@sll: esto es para cerrar la conexión inmediatamente después de que regrese la llamada (una llamada por sesión).
TrueWill
@cacho Hacer DisposeSafelyprivado es ciertamente una opción, y evitaría confusión. Puede haber casos de uso en los que alguien quiera llamarlo directamente, pero no se me ocurre ninguno.
TrueWill
@truewill solo para documentación, también es importante mencionar que este método es seguro para subprocesos, ¿verdad?
Cacho Santa
1
En mi opinión, la solución más correcta sería: 1) Realizar el patrón Cerrar / Anular sin una condición de carrera 2) Manejar la situación cuando la operación de servicio arroja excepciones 3) Manejar las situaciones en que los métodos Cerrar y Abortar arrojan excepciones 4) Manejar excepciones asincrónicas como ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet
8

@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 caso deService<IOrderService>.Use

Esto facilitaría la devolución de variables.

pangular
fuente
2
+1 @MarcGravell Creo que su respuesta 'también podría ser mejor': P (y la acción se puede implementar en términos de un Func con un retorno nulo). Toda esta página es un desastre: iría a formular una unificada y comentaría sobre dups si pensara usar WCF en cualquier momento de esta década ...
Ruben Bartelink
7

¿Que 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, incluyo 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 red.

Uso simple del cliente WCF

Una vez que 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 modificar 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 esta publicación una wiki comunitaria. No recogeré "puntos" de esta respuesta, pero prefiero que lo voten si está de acuerdo con la implementación, o lo edite para mejorarlo.

LamonteCristo
fuente
No estoy seguro de estar de acuerdo con su caracterización de esta respuesta. Es la versión CW con su idea de manejo de excepciones agregado.
John Saunders
@JohnSaunders - Verdadero (mi concepto de manejo de excepciones). Avíseme de las excepciones que me faltan o estoy manejando mal.
goodguys_activate
¿Qué pasa con la variable de éxito? Necesita agregar al código fuente: si (éxito) regresa; ??
Kiquenet
Si la primera llamada se lanza y la segunda tiene éxito, mostRecentEx no será nulo, por lo que está lanzando una excepción que falló 5 reintentos de todos modos. ¿O me estoy perdiendo algo? No veo dónde se borra el MostRecentEx si en un segundo, tercer, cuarto o quinto intento tuvo éxito. Tampoco veo un retorno o éxito. Debería perderme algo aquí, pero este código no se ejecutará siempre 5 veces si no se produce una excepción.
Bart Calixto
@Bart: agregué success == falsela declaración if final
goodguys_activate
7

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

Utiliza .NET 4 (específicamente: contravarianza, 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.");
    }
}
Jesse C. Slicer
fuente
1
¿Por qué usar en UseServiceDelegate<T>lugar de Action<T>?
Mike Mayer
1
La única razón por la que puedo pensar que el autor original lo hizo fue tener un delegado fuertemente tipado que el desarrollador sabría que pertenece a llamar a un servicio. Pero, por lo que puedo ver, Action<T>funciona igual de bien.
Jesse C. Slicer
5

Un envoltorio como este 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 contenedor podría, por supuesto, capturar más excepciones si fuera necesario, pero el principio sigue siendo el mismo.

Tomás Jansson
fuente
Recuerdo la discusión sobre Dispose que no se llama bajo ciertas condiciones ... lo que resulta en una pérdida de memoria con WCF.
goodguys_activate
No estoy seguro de que haya resultado en pérdidas de memoria, pero el problema es este. Cuando llama Disposea un IChannel, podría arrojar una excepción si el canal está en estado defectuoso, esto es un problema ya que Microsoft especifica que Disposenunca se debe lanzar. Entonces, lo que hace el código anterior es manejar el caso cuando Closearroja una excepción. Si Aborttira, podría ser algo muy mal. Escribí una publicación de blog al respecto en diciembre pasado: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson
4

Utilicé el proxy dinámico de Castle 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 herede su contrato de servicio e IDisposable. 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 los detalles de WCF. Y no hay nada adicional como las otras soluciones.

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

Jay Douglass
fuente
4

Use 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;
    }
}
Johan Nyman
fuente
4

Si no necesita IoC o está utilizando un cliente autogenerado (Referencia de servicio), puede simplemente usar un contenedor para administrar el cierre y dejar que el GC tome la base de clientes cuando esté en un estado seguro que no arroje ninguna excepción. El GC llamará a Dispose en serviceclient, y esto llamará Close. Como 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;
        }
    }
}

Luego, cuando accede al servidor, crea el cliente y lo usa usingen la autodisconección:

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

    Ws.Open();

    Ws.Test();
}
Luiz felipe
fuente
3

Resumen

Usando las técnicas descritas en esta respuesta, uno puede consumir un servicio WCF en un bloque de uso 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 IMyServicereprenting del canal que implemente correctamente el patrón desechable.


Detalles

Todas las respuestas dadas hasta ahora abordan el problema de evitar el "error" en la implementación de WCF Channel de IDisposable. La respuesta que parece ofrecer el modelo de programación más conciso (que le permite usar el usingbloque para disponer de recursos no administrados) es esta , donde el proxy se modifica para implementarlo IDisposablecon 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 usemos. En una variación de esta respuesta, veremos cómo podemos usar la composición en lugar de la herencia para hacer que esta técnica sea genérica.

Primer intento

Parecen varias implementaciones para la IDisposableimplementación, pero en aras de un argumento usaremos 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();
    }
}

Armados 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 usingbloque:

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 evita que este código sea genérico es el hecho de que la ProxyWrapperclase debe volver a implementarse 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 auxiliar ahora podemos escribir

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 para heredar ClientBase<>(en lugar de usar ChannelFactory<>), o si desea usar una implementación diferente de IDisposablepara cerrar su canal.

Lawrence
fuente
2

Me gusta esta forma de cerrar la conexión:

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

He escrito una clase base simple 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);
    }
}
Ufuk Hacıoğulları
fuente
¿Alguna actualización para VS2013-.net 4.5.1? ¿alguna opción para Reintentar como stackoverflow.com/a/9370880/206730 ? -
Kiquenet
@Kiquenet Ya no estoy trabajando en WCF. Si me envía una solicitud de extracción, puedo fusionarla y actualizar el paquete.
Ufuk Hacıoğulları
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();
            }
        }
    }
}

Por lo tanto, permite escribir declaraciones de devolución muy bien:

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 
Andriy Buday
fuente
1

Me gustaría agregar la implementación del Servicio de 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();
            }
        }
    }
}
PSsam
fuente
1

Para aquellos interesados, aquí hay una traducción VB.NET de la respuesta aceptada (a continuación). Lo he refinado un poco por 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 versión VB.NET de esta excelente solución, supongo que otros también buscarán. La traducción de Lambda puede ser un poco complicada, por lo que me gustaría salvar a alguien del problema.

Tenga en cuenta que esta implementación particular proporciona la capacidad de configurar el ServiceEndpointtiempo 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
InteXX
fuente
1

La arquitectura de nuestro sistema a menudo usa el marco de trabajo de Unity IoC para crear instancias de ClientBase, por lo que no hay una forma segura de exigir que los otros desarrolladores incluso usen using{}bloques. Para que sea lo más infalible posible, hice esta clase personalizada que extiende ClientBase y maneja el cierre del canal al desechar o al finalizar en caso de que alguien no elimine explícitamente la instancia creada por Unity.

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

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 estos:

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);
        }
    }
}
CodingWithSpike
fuente
Nunca utiliza el parámetro disposición en su método de
eliminación
@Chad - Estaba siguiendo el patrón de diseño común Finalizar / Eliminar de Microsoft: msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx Sin embargo, es cierto que no estoy usando la variable, porque no No es necesario realizar ninguna limpieza diferente entre una disposición normal y una finalización. Podría reescribirse simplemente para finalizar la llamada Dispose () y mover el código de Dispose (bool) a Dispose ().
CodingWithSpike
Los finalizadores agregan gastos generales y no son deterministas. Los evito siempre que sea posible. Puede usar las fábricas automáticas de Unity para inyectar delegados y ponerlos en bloques de uso, u (mejor) ocultar el comportamiento del servicio de creación / llamada / disposición detrás de un método en una interfaz inyectada. Cada llamada a la dependencia crea el proxy, lo llama y lo elimina.
TrueWill
0

Remití pocas respuestas en esta publicación y la personalicé según mis necesidades.

Quería la capacidad de hacer algo con el cliente WCF antes de usarlo, así que el DoSomethingWithClient()método.

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 auxiliar:

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));
FELICIDAD
fuente
¿Qué pasa con el constructor de cliente que utiliza enlace y endpoing? TClient (enlace, finalización)
Kiquenet
0

Tengo mi propio contenedor para un canal que implementa Dispose 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 utilizar un bloque de uso.

Joe
fuente
0

El siguiente ayudante permite llamar voidy métodos no anulados. Uso:

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

La clase en sí 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;
        });
    }
}
Konstantin Spirin
fuente
0

Anule Dispose () del cliente sin la necesidad de generar una clase proxy basada en ClientBase, ¡también sin la necesidad de administrar la creación y el almacenamiento en caché del canal ! (Tenga en cuenta que WcfClient no es una clase ABSTRACT 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();
    }
}
Murad Duraidi
fuente
0

Mi método para hacer esto ha sido crear una clase heredada que implemente explícitamente IDisposable. Esto es útil para las personas que usan la interfaz gráfica de usuario para agregar la referencia de servicio (Agregar referencia de servicio). Solo dejo caer esta clase en el proyecto haciendo referencia al 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: Esta es solo una implementación simple de dispose, puede implementar una lógica de eliminación más compleja si lo desea.

Luego puede reemplazar todas sus llamadas realizadas con el cliente de servicio regular por los clientes seguros, de esta manera:

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 interfaz y puedo usar el using declaración como esperaría, mientras que permite que mi código se vea más o menos igual.

Aún tendrá que manejar las excepciones que se pueden lanzar como se señala en otros comentarios en este hilo.

Aleksandr Albert
fuente
-2

También podría usar a DynamicProxypara extender el Dispose()método. De esta manera podrías hacer algo como:

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