我喜欢在using
块中实例化我的WCF服务客户端,因为它几乎是使用实现的资源的标准方法IDisposable
:
using (var client = new SomeWCFServiceClient()) { //Do something with the client }
但是,正如本MSDN文章中所述,在using
块中包装WCF客户端可能会掩盖导致客户端处于故障状态的任何错误(如超时或通信问题).简而言之,当调用Dispose()时,客户端的Close()方法会触发,但会因为处于故障状态而抛出错误.然后,第二个异常掩盖了原始异常.不好.
MSDN文章中建议的解决方法是完全避免使用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 proxy); public static class Service { public static ChannelFactory _channelFactory = new ChannelFactory (""); public static void Use(UseServiceDelegate codeBlock) { IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); bool success = false; try { codeBlock((T)proxy); proxy.Close(); success = true; } finally { if (!success) { proxy.Abort(); } } } }
然后允许:
Service.Use(orderService => { orderService.PlaceOrder(request); });
这不错,但我不认为它像using
块一样具有表现力和易于理解.
我正在尝试使用的解决方法我首先在blog.davidbarret.net上阅读.基本上,Dispose()
无论您在何处使用它,都会覆盖客户端的方法.就像是:
public partial class SomeWCFServiceClient : IDisposable { void IDisposable.Dispose() { if (this.State == CommunicationState.Faulted) { this.Abort(); } else { this.Close(); } } }
这似乎能够using
再次允许阻塞而没有掩盖故障状态异常的危险.
那么,我有什么其他的问题需要注意使用这些变通方法吗?有没有人想出更好的东西?
实际上,虽然我写了博客(参见Luke的回答),但我认为这比我的IDisposable包装更好.典型代码:
Service.Use(orderService=> { orderService.PlaceOrder(request); });
(根据评论编辑)
由于Use
返回void,处理返回值的最简单方法是通过捕获的变量:
int newOrderId = 0; // need a value for definite assignment Service.Use(orderService=> { newOrderId = orderService.PlaceOrder(request); }); Console.WriteLine(newOrderId); // should be updated
考虑到IServiceOriented.com倡导的解决方案与David Barret博客倡导的解决方案之间的选择,我更喜欢通过覆盖客户端的Dispose()方法提供的简单性.这允许我继续使用using()语句,就像人们对一次性对象所期望的那样.但是,正如@Brian指出的那样,这个解决方案包含一个竞争条件,因为状态在检查时可能不会出现故障,但可能在调用Close()时,在这种情况下仍然会发生CommunicationException.
因此,为了解决这个问题,我采用了一种混合了两全其美的解决方案.
void IDisposable.Dispose() { bool success = false; try { if (State != CommunicationState.Faulted) { Close(); success = true; } } finally { if (!success) Abort(); } }
我写了一个更高阶的函数来使它正常工作.我们已经在几个项目中使用了它,它看起来效果很好.这就是从一开始就应该做的事情,没有"使用"范式等等.
TReturn UseService(Func code) { var chanFactory = GetCachedFactory (); 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);
这与你的例子中的情况非常相似.在一些项目中,我们编写强类型的辅助方法,因此我们最终编写了诸如"Wcf.UseFooService(f => f ...)"之类的东西.
考虑到所有事情,我发现它非常优雅.你遇到过一个特殊的问题吗?
这允许插入其他漂亮的功能.例如,在一个站点上,站点代表登录用户对服务进行身份验证.(该站点本身没有凭据.)通过编写我们自己的"UseService"方法帮助程序,我们可以按照我们想要的方式配置通道工厂等.我们也不必使用生成的代理 - 任何接口都可以.
这是Microsoft推荐的处理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上问这个问题,微软甚至创建了一个专门的示例来演示如何处理异常:
C:\ WF_WCF_Samples\WCF \基本\客户端\ ExpectedExceptions\CS \客户端
下载示例: C#或 VB
考虑到涉及使用声明的问题很多,(加热?)内部讨论和线程就此问题,我不会浪费时间试图成为代码牛仔并找到一种更清洁的方式.我只是简单地说,并为我的服务器应用程序实现WCF客户端这种冗长(但可信)的方式.
可选的附加失败事件
许多例外来自CommunicationException
,我认为大多数例外都不应该重审.我在MSDN上浏览了每个例外,并找到了一个可重试异常的简短列表(除了TimeOutException
上面的内容).如果我错过了应该重试的异常,请告诉我.
// 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 }
不可否认,这是一些平凡的代码.我目前更喜欢这个答案,并且在该代码中看不到任何可能导致问题的"黑客".
我终于找到了解决这个问题的一个坚实步骤.
此自定义工具扩展了WCFProxyGenerator以提供异常处理代理.它生成一个额外的代理,称为
ExceptionHandlingProxy
继承ExceptionHandlingProxyBase
- 后者实现了代理功能的内容.结果是您可以选择使用继承的默认代理ClientBase
或ExceptionHandlingProxy
封装管理通道工厂和通道的生命周期的默认代理.ExceptionHandlingProxy在"添加服务引用"对话框中尊重您对异步方法和集合类型的选择.
Codeplex有一个名为异常处理WCF代理生成器的项目.它基本上为Visual Studio 2008安装了一个新的自定义工具,然后使用此工具生成新的服务代理(添加服务引用).它有一些很好的功能来处理故障通道,超时和安全处置.这里有一个很棒的视频叫做ExceptionHandlingProxyWrapper,它解释了它是如何工作的.
您可以安全地Using
再次使用该语句,如果通道在任何请求(TimeoutException或CommunicationException)上出现故障,Wrapper将重新初始化故障通道并重试查询.如果失败则会调用该Abort()
命令并处理代理并重新抛出异常.如果服务抛出一个FaultException
代码,它将停止执行,代理将被安全中止,抛出正常的异常.
根据Marc Gravell,MichaelGG和Matt Davis的回答,我们的开发人员提出了以下建议:
public static class UsingServiceClient { public static void Do(TClient client, Action 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));
它尽可能接近"using"语法,在调用void方法时不必返回虚值,并且可以多次调用服务(并返回多个值)而不必使用元组.
此外,ClientBase
如果需要,您可以将其与后代而不是ChannelFactory一起使用.
如果开发人员想要手动处理代理/通道,则会暴露扩展方法.
@Marc Gravell
使用它不是没关系的:
public static TResult Using(this T client, Func work) where T : ICommunicationObject { try { var result = work(client); client.Close(); return result; } catch (Exception e) { client.Abort(); throw; } }
或者,同样的(Func
情况下Service
这些将使返回变量更容易.
以下是来自问题的源的增强版本,并扩展为缓存多个通道工厂,并尝试按合同名称在配置文件中查找端点.
它使用.NET 4(特别是:逆变,LINQ,var
):
////// Delegate type of the service method to perform. /// /// The service proxy. ///The type of service to use. internal delegate void UseServiceDelegate(T proxy); /// /// Wraps using a WCF service. /// ///The type of service to use. internal static class Service{ /// /// A dictionary to hold looked-up endpoint names. /// private static readonly IDictionarycachedEndpointNames = new Dictionary (); /// /// A dictionary to hold created channel factories. /// private static readonly IDictionary> cachedFactories = new Dictionary >(); /// /// Uses the specified code block. /// /// The code block. internal static void Use(UseServiceDelegatecodeBlock) { var factory = GetChannelFactory(); var proxy = (IClientChannel)factory.CreateChannel(); var success = false; try { using (proxy) { codeBlock((T)proxy); } success = true; } finally { if (!success) { proxy.Abort(); } } } /// /// Gets the channel factory. /// ///The channel factory. private static ChannelFactoryGetChannelFactory() { lock (cachedFactories) { var endpointName = GetEndpointName(); if (cachedFactories.ContainsKey(endpointName)) { return cachedFactories[endpointName]; } var factory = new ChannelFactory (endpointName); cachedFactories.Add(endpointName, factory); return factory; } } /// /// Gets the name of the endpoint. /// ///The name of the endpoint. 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().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."); } }
这是什么?
这是已接受答案的CW版本,但(我认为完整)包括异常处理.
接受的答案引用了不再存在的这个网站.为了省去麻烦,我在这里列出了最相关的部分.此外,我稍微修改它以包括异常重试处理来处理那些讨厌的网络超时.
简单的WCF客户端使用
生成客户端代理后,这就是实现它所需的全部内容.
Service.Use(orderService=> { orderService.PlaceOrder(request); });
ServiceDelegate.cs
将此文件添加到您的解决方案.除非您想要更改重试次数或要处理的异常,否则不需要对此文件进行任何更改.
public delegate void UseServiceDelegate(T proxy); public static class Service { public static ChannelFactory _channelFactory = new ChannelFactory (""); public static void Use(UseServiceDelegate 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:我已将此帖发布为社区维基.我不会从这个答案中收集"积分",但如果您同意该实施,或者编辑它以使其更好,则更愿意您赞成它.
像这样的包装器可以工作:
public class ServiceClientWrapper: IDisposable { private ServiceType _channel; public ServiceType Channel { get { return _channel; } } private static ChannelFactory _channelFactory; public ServiceClientWrapper() { if(_channelFactory == null) // Given that the endpoint name is the same as FullName of contract. _channelFactory = new ChannelFactory (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()) { var request = ... response = clientWrapper.Channel.MyServiceCall(request); } // Use your response object.
如果需要,包装器当然可以捕获更多例外,但原理保持不变.