c# vb.net wcf using wcf-client

c# - endpoint web config



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

Me gusta crear una instancia de mis clientes de servicio WCF dentro de un bloque de using , ya que es prácticamente la forma estándar de usar recursos que se pueden implementar como 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 bloque de using podría enmascarar cualquier error que resulte en que el cliente quede en un estado de falla (como un tiempo de espera o un problema de comunicación). En pocas palabras, cuando se llama a Dispose (), el método Cerrar () del cliente se dispara, pero arroja un error porque se encuentra en estado de falla. 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 completamente el uso de un bloque de using y, en su lugar, crear una instancia de sus clientes y usarlos de esta forma:

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

En comparación con el bloque de using , creo que eso es feo. Y mucho código para escribir cada vez que necesites un cliente.

Por suerte, encontré algunas otras soluciones alternativas, 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 entonces permite:

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

Eso no es malo, pero no creo que sea tan expresivo y fácil de entender como el bloque de using .

La solución que estoy tratando de usar, leí primero en blog.davidbarret.net . Básicamente, usted reemplaza el método Dispose() 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 poder permitir el using bloque nuevamente sin el peligro de enmascarar una excepción de estado con falla.

Entonces, ¿hay otros errores que debo tener en cuenta para utilizar estas soluciones? ¿Alguien ha venido con algo mejor?


Resumen

Usando las técnicas descritas en esta respuesta, se 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 y específico para su situación, pero el punto es que podemos crear una implementación de IMyService reproduzca el canal que implementa correctamente el patrón desechable.

Detalles

Todas las respuestas dadas hasta ahora abordan el problema de sortear el "error" en la implementación de IDisposable en el canal WCF. La respuesta que parece ofrecer el modelo de programación más conciso (que le permite usar el bloque de using para disponer de recursos no administrados) es esta : donde se modifica el proxy para implementar IDisposable con una implementación IDisposable 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 la composición en lugar de la herencia para hacer que esta técnica sea 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 este 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 ProxyWrapperclase se debe volver a implementar para cada contrato de servicio que deseemos. Ahora veremos una clase que nos permite crear este tipo dinámicamente utilizando 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 de ayudantes 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 puede usar la misma técnica (con ligeras modificaciones) para los clientes generados automáticamente que heredan ClientBase<>(en lugar de usar ChannelFactory<>), o si desea usar una implementación diferente IDisposablepara cerrar su canal.


@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 las variables.


A continuación se muestra una versión mejorada de la fuente de la pregunta y extendida para almacenar en caché las fábricas de múltiples canales e intentar buscar el punto final en el archivo de configuración por nombre del 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."); } }


Basados ​​en 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));

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

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

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


Dada la opción entre la solución recomendada por IServiceOriented.com y la solución recomendada por blog.davidbarret.net , prefiero la simplicidad ofrecida al anular el método Dispose () del cliente. Esto me permite continuar usando la instrucción using () como se esperaría con un objeto desechable. Sin embargo, como lo señaló @Brian, esta solución contiene una condición de carrera en la que el Estado podría no tener fallas cuando se verifica, pero podría ser antes de que se llame a Close (), en cuyo caso aún se produce la excepción CommunicationException.

Entonces, para solucionar esto, empleé 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(); } }


En realidad, aunque blogged (ver la respuesta de Luke ), creo que this es mejor que mi envoltorio IDisposable. Codigo tipico:

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

(editar por comentarios)

Ya que Use devuelve void, 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


Escribí una función de orden superior para que funcione correctamente. Lo hemos usado en varios proyectos y parece que funciona muy bien. Así es como se deberían haber 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 más o menos lo que tienes en tu ejemplo. En algunos proyectos, escribimos métodos de ayuda 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 haya encontrado?

Esto permite que se conecten otras características ingeniosas. Por ejemplo, en un sitio, el sitio se autentica ante el servicio en nombre del usuario que ha iniciado sesión. (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 funcionará. .


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

Para más detalles vea: 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 Mucha 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

Descarga la muestra: C# o VB

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

Opcionales fallas adicionales para atrapar

Muchas excepciones se derivan de CommunicationException y no creo que deba reintentarse la mayoría de esas excepciones. Revisé cada excepción en MSDN y encontré una lista corta de excepciones reintentables (además de la TimeOutException ). Hágame saber si me perdí una excepción que debería volver a intentar.

// 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 esto es un poco de código mundano para escribir. Actualmente prefiero esta respuesta y no veo ningún "hackeo" en ese código que pueda causar problemas en el futuro.


Finalmente he encontrado algunos pasos sólidos hacia una solución limpia para 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 función 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 gestió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 servicio proxy (Agregar referencia de servicio) . Tiene una buena funcionalidad para lidiar con los canales defectuosos, tiempos de espera y eliminación segura. Aquí hay un excelente video llamado ExceptionHandlingProxyWrapper explica exactamente cómo funciona esto.

Puede volver a usar la instrucción Using forma segura, y si el canal falla en cualquier solicitud (TimeoutException o CommunicationException), el Wrapper reinicializará el canal con falla y volverá a intentar la consulta. Si eso falla, llamará al comando Abort() y eliminará el proxy y volverá a emitir la excepción. Si el servicio lanza un código de FaultException , dejará de ejecutarse y el proxy se cancelará de forma segura y lanzará la excepción correcta como se esperaba.


Si no necesita IoC o está utilizando un cliente autogenerado (Referencia de servicio), entonces puede usar un contenedor para gestionar el cierre y dejar que el GC tome la base de clientes cuando esté en un estado seguro que no arrojará ninguna excepción. El GC llamará a Dispose in serviceclient, y esto llamará a 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 using en el autodisconect:

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


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 permitirte escribir código como:

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

Por supuesto, la envoltura podría detectar más excepciones si fuera necesario, pero el principio sigue siendo el mismo.


Utilicé el proxy dinámico de Castle para resolver el problema de Dispose (), y también implementé la actualización automática del canal cuando estaba 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 cruces añadidos como las otras soluciones.

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


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


¿Que es esto?

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

La respuesta aceptada hace referencia a this . Para evitarle problemas, aquí incluyo las partes más relevantes. Además, lo modifiqué ligeramente para incluir el manejo de reintentos de excepciones para manejar esos molestos tiempos de espera de red.

Uso simple del cliente WCF

Una vez que genere 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 desee controlar.

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 de la comunidad. No recopilaré "puntos" de esta respuesta, pero preferiría que los votara si está de acuerdo con la implementación, o la edite para mejorarla.


Me gusta esta forma de cerrar la conexión:

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


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

Quería la capacidad de hacer algo con el cliente de WCF antes de usarlo para 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 de ayuda:

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


Tengo mi propio envoltorio para un canal que implementa Desechar 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.


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


El siguiente ayudante permite llamar voidy no anular métodos. 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; }); } }


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


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


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). Simplemente suelto esta clase en el proyecto que hace referencia al servicio y lo 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 dispose más compleja si lo desea.

Luego, puede reemplazar todas las 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 la Interfaz y puedo usar la usingdeclaración como esperaría y permitir que mi código se vea más o menos igual.

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


Nuestra arquitectura de sistema a menudo utiliza el marco de Unity IoC para crear instancias de ClientBase, por lo que no hay una forma segura de imponer que los otros desarrolladores utilicen using{}bloques. Para hacerlo lo más infalible posible, hice esta clase personalizada que amplía ClientBase y se encarga de cerrar el canal cuando se desecha o finaliza 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 las credenciales personalizadas, 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 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); } } }


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 solución fina, asumo que otros también estarán buscando. La traducción Lambda puede ser un poco complicada, así que me gustaría ahorrarle a alguien el problema.

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

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


También podrías usar un DynamicProxypara extender el Dispose()método. 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. }


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 declaraciones de retorno muy bien:

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