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

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

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

但是,正如在这篇MSDN文章中指出的那样,将WCF客户端封装在using块中可能会掩盖导致客户端处于故障状态(如超时或通信问题)的任何错误。 长话短说,在调用Dispose()时,客户端的Close()方法会触发,但会因为处于故障状态而引发错误。 原来的exception然后被第二个exception所掩盖。 不好。

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

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

相比于using块,我认为这是丑陋的。 每当你需要一个客户端时,需要写很多代码。

幸运的是,我发现了一些其他的解决方法,比如IServiceOriented上的这个。 你从以下开始:

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

然后允许:

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

这并不坏,但我认为它不像using块那样具有expression力和易于理解的性质。

我正在尝试使用的解决方法我首先在blog.davidbarret.net上阅读 。 基本上你可以覆盖客户端的Dispose()方法。 就像是:

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

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

那么,我还有什么其他问题需要注意使用这些解决方法吗? 有人提出任何更好的?

实际上,虽然我博客 (见卢克的回答 ),但我认为这比我的IDisposable包装好。 典型代码:

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

(编辑每个评论)

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

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

在IServiceOriented.com所倡导的解决scheme与David Barret的博客倡导的解决scheme之间进行select时,我更喜欢覆盖客户端的Dispose()方法所提供的简单性。 这允许我继续使用using()语句,就像使用可丢弃对象所期望的那样。 不过,正如@Brian所指出的那样,这个解决scheme包含一个竞争条件,即状态在检查时可能不会出错,但在调用Close()的时候,CommunicationException仍然会发生。

所以,为了解决这个问题,我采用了一个混合两全其美的解决scheme。

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

我写了一个更高级的函数来使它正确工作。 我们已经在几个项目中使用了它,它似乎很好。 这就是从一开始就应该做的事情,没有“使用”范例等等。

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

你可以这样打电话:

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

这几乎就像你的例子。 在一些项目中,我们编写了强types的帮助器方法,所以我们最终编写了诸如“Wcf.UseFooService(f => f …)”之类的东西。

我觉得它很优雅,所有的考虑。 有没有遇到特别的问题?

这允许插入其他漂亮的function。例如,在一个站点上,站点代表login的用户对服务进行authentication。 (该网站本身没有证书。)通过编写我们自己的“UseService”方法助手,我们可以按照我们想要的方式configuration渠道工厂等等。我们也不一定会使用生成的代理 – 任何接口都会。

这是微软推荐的处理WCF客户端调用的方式:

有关更多详细信息,请参阅: 预期的例外

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

附加信息许多人似乎在WCF上提出这个问题,微软甚至还创build了一个专门的示例来演示如何处理exception:

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

下载示例: C#或VB

考虑到涉及使用声明的问题太多了 (激烈?)在这个问题上的内部讨论和线索 ,我不会浪费时间试图成为一个牛仔代码,find一个更干净的方式。 我只是把它吸了起来,并为我的服务器应用程序实现WCF客户端这种冗长(但可信)的方式。

可选的其他故障要赶上

CommunicationException引发了许多exception,我不认为这些exception中的大多数应该重试。 我在MSDN上的每一个exception都苦苦思索,发现了一个可重试exception的简短列表(除了上面的TimeOutException )。 如果我错过了应该重试的exception,请告诉我。

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

无可否认,这是一个世俗的代码写的。 我目前更喜欢这个答案 ,并没有看到在该代码中的任何“黑客”,可能会导致问题的道路上。

我终于find了一些可靠的方法来解决这个问题。

此自定义工具扩展WCFProxyGenerator以提供exception处理代理。 它生成一个名为ExceptionHandlingProxy<T>的附加代理,它inheritance了ExceptionHandlingProxyBase<T> – 后者实现了代理function。 结果是您可以select使用inheritanceClientBase<T>ExceptionHandlingProxy<T>的默认代理来封装pipe理通道工厂和通道的生命周期。 ExceptionHandlingProxy在“添加服务引用”对话框中尊重您在asynchronous方法和集合types方面的select。

Codeplex有一个名为exception处理WCF代理生成器的项目。 它基本上安装了一个新的自定义工具到Visual Studio 2008,然后使用这个工具来生成新的服务代理(添加服务引用) 。 它有一些很好的function来处理故障的通道,超时和安全处置。 这里有一个很棒的video,叫做ExceptionHandlingProxyWrapper,它解释了这是如何工作的。

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

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

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

使用示例:

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

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

此外,如果需要,您可以将此与ClientBase<T>后代用于代替ChannelFactory。

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

@Marc Gravell

这不是好的使用:

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

或者,对于Service<IOrderService>.Use ,同样的事情(Func<T, TResult>)

这将使返回的variables更容易。

这是什么?

这是接受的答案的CW版本,但(我认为完成)包括exception处理。

被接受的答案引用了这个不再存在的网站 。 为了节省您的麻烦,我在这里包含了最相关的部分。 此外,我稍微修改它以包括exception重试处理来处理那些讨厌的networking超时。

简单的WCF客户端使用

一旦你生成你的客户端代理,这就是你需要实现它。

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

ServiceDelegate.cs

将此文件添加到您的解决scheme。 除非要更改重试次数或要处理的exception,否则不需要更改此文件。

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

PS:我把这个post写成了一个社区维基。 我不会从这个答案中收集“积分”,但是如果您同意实施,或者为了使其更好而编辑,则喜欢您喜欢它。

以下是来自问题的源代码的增强版本,并扩展为caching多个通道工厂,并尝试通过合同名称查找configuration文件中的端点。

它使用.NET 4(特别是:反转,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."); } } 

像这样的包装将工作:

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

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

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

如果需要的话,包装程序当然可以捕获更多的例外情况,但原则保持不变。

我使用了Castledynamic代理来解决Dispose()问题,并且在通道处于不可用状态时也实现了自动刷新。 要使用它,您必须创build一个inheritance您的服务合同和IDisposable的新界面。 dynamic代理实现这个接口并包装一个WCF通道:

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

我喜欢这个,因为你可以注入WCF服务而无需消费者需要担心WCF的任何细节。 而且没有像其他解决scheme一样添加垃圾。

看看代码,其实很简单: WCFdynamic代理

如果您不需要IoC或使用自动生成的客户端(服务参考),那么您可以简单地使用包装来pipe理closures,并让GC在客户端处于安全状态时不会抛出任何exception。 GC将在serviceclient中调用Dispose,这将调用Close 。 由于它已经closures,所以不会造成任何损害。 我正在使用这个没有问题的生产代码。

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

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

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

使用扩展方法:

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

概要

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

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

你当然可以适应这个更进一步的实现一个更简洁的编程模型,具体到你的情况 – 但重点是,我们可以创build一个实现IMyService reprenting正确实现一次性模式的渠道。


细节

到目前为止给出的所有答案都解决了绕过WCF Channel实现IDisposable的“bug”的问题。 似乎提供了最简洁的编程模型(允许您使用using块来处理非托pipe资源)的答案就是这样 – 代理被修改为实现IDisposable和一个无bug的实现。 这种方法的问题是可维护性 – 我们必须重新实现这个function,以便我们使用的代理服务器。 在这个答案的一个变种,我们将看到我们如何使用组合而不是inheritance来使这种技术通用。

第一次尝试

对于IDisposable实现似乎有不同的实现,但为了争论起见,我们将使用由当前接受的答案使用的实现 。

 [ServiceContract] public interface IMyService { [OperationContract] void DoWork(); } public class ProxyDisposer : IDisposable { private IClientChannel _clientChannel; public ProxyDisposer(IClientChannel clientChannel) { _clientChannel = clientChannel; } public void Dispose() { var success = false; try { _clientChannel.Close(); success = true; } finally { if (!success) _clientChannel.Abort(); _clientChannel = null; } } } public class ProxyWrapper : IMyService, IDisposable { private IMyService _proxy; private IDisposable _proxyDisposer; public ProxyWrapper(IMyService proxy, IDisposable disposable) { _proxy = proxy; _proxyDisposer = disposable; } public void DoWork() { _proxy.DoWork(); } public void Dispose() { _proxyDisposer.Dispose(); } } 

现在我们可以编写上述类的武装

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

这使我们可以使用using块来使用我们的服务:

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

制作这个通用的

到目前为止,我们所做的只是重新形成托马斯的解决scheme 。 什么阻止这个代码是通用的是ProxyWrapper类必须重新实现我们想要的每个服务合同。 现在我们来看一个允许我们使用ILdynamic创build这个类的类:

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

With our new helper class we can now write

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

Note that you could also use the same technique (with slight modifications) for auto-generated clients inheriting for ClientBase<> (instead of using ChannelFactory<> ), or if you want to use a different implementation of IDisposable to close your channel.

I have written a simple base class that handles this. It's available as a NuGet package and it's quite easy to use.

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

So it allows to write return statements nicely:

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

I'd like to add implementation of Service from Marc Gravell's answer for case of using ServiceClient instead of 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(); } } } } 

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

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

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


码:

 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 

用法:

 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 

Our system architecture often uses the Unity IoC framework to create instances of ClientBase so there's no sure way to enforce that the other developers even use using{} blocks. In order to make it as fool-proof as possible, I made this custom class that extends ClientBase, and handles closing down the channel on dispose, or on finalize in case someone doesn't explicitly dispose of the Unity created instance.

There is also stuff that needed to be done in the constructor to set up the channel for custom credentials and stuff, so that's in here too…

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

Then a client can simply:

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

And the caller can do any of these:

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

I like this way of closing connection:

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

I referred few answers on this post and customized it as per my needs.

I wanted the ability to do something with WCF client before using it so the DoSomethingWithClient() method.

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

Here is the helper class:

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

And I can use it as:

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

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

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

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

The following helper allows to call void and non-void methods. 用法:

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

The class itself is:

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

Override the client's Dispose() without the need to generate a proxy class based on ClientBase, also without the need to manage channel creation and caching ! (Note that WcfClient is not an ABSTRACT class and is based on 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(); } } 

My method of doing this has been to create an inherited class that explicitly implements IDisposable. This is useful for folks who use the gui to add the service reference ( Add Service Reference ). I just drop this class in the project making the service reference and use it instead of the default client:

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

Note: This is just a simple implementation of dispose, you can implement more complex dispose logic if you like.

You can then replace all your calls made with the regular service client with the safe clients, like this:

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

I like this solution as it does not require me to have access to the Interface definitions and I can use the using statement as I would expect while allowing my code to look more or less the same.

You will still need to handle the exceptions which can be thrown as pointed out in other comments in this thread.

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

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