我喜欢在 using
块中实例化我的WCF服务客户端,因为它几乎是使用实现 IDisposable
的资源的标准方法:
using (var client = new SomeWCFServiceClient())
{
//Do something with the client
}
但是,如this MSDN article中所述,将WCF客户端包装在 using
块中可能会掩盖导致客户端处于故障状态的任何错误(如超时或通信问题) . 简而言之,当调用Dispose()时,客户端's Close() method fires, but throws an error because it'处于故障状态 . 然后,第二个异常将屏蔽原始异常 . 不好 .
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>(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);
});
那个's not bad, but I don'认为它与 using
块一样具有表现力和易于理解 .
我正在尝试使用的解决方法我首先阅读blog.davidbarret.net . 基本上,无论您在何处使用它,都会覆盖客户端的 Dispose()
方法 . 就像是:
public partial class SomeWCFServiceClient : IDisposable
{
void IDisposable.Dispose()
{
if (this.State == CommunicationState.Faulted)
{
this.Abort();
}
else
{
this.Close();
}
}
}
这似乎能够再次允许 using
块,而没有掩盖故障状态异常的危险 .
那么,我有什么其他的问题需要注意使用这些变通方法吗?有没有人想出更好的东西?
26 回答
我写了一个higher order function以使其正常工作 . 我们已经在几个项目中使用了它,它看起来效果很好 . 这就是从一开始就应该做的事情,没有"using"范例等等 .
你可以这样打电话:
这与您的示例中的情况非常相似 . 在某些项目中,我们编写了强类型的辅助方法,因此我们最终编写了诸如“Wcf.UseFooService(f => f ...)”之类的东西 .
考虑到所有事情,我发现它非常优雅 . 你遇到了什么特别的问题吗?
这允许插入其他漂亮的功能 . 例如,在一个站点上,站点代表登录用户对服务进行身份验证 . (该站点本身没有凭据 . )通过编写我们自己的“UseService”方法帮助程序,我们可以按照我们想要的方式配置通道工厂等 . 我们也不必使用生成的代理 - 任何接口都可以 .
覆盖客户端的Dispose()而无需基于ClientBase生成代理类,也无需manage channel creation and caching! (请注意,WcfClient不是ABSTRACT类,基于ClientBase)
@Marc Gravell
使用它不是没关系的:
或者,
(Func<T, TResult>)
的情况(Func<T, TResult>)
这些将使返回变量更容易 .
以下助手允许调用
void
和非void方法 . 用法:该课程本身是:
像这样的包装器可以工作:
这应该使您能够编写如下代码:
如果需要,包装器当然可以捕获更多例外,但原理保持不变 .
所以它允许很好地编写return语句:
根据Marc Gravell,MichaelGG和Matt Davis的回答,我们的开发人员提出了以下建议:
使用示例:
它尽可能接近“using”语法,在调用void方法时不必返回虚值,并且可以多次调用服务(并返回多个值)而不必使用元组 .
此外,如果需要,您可以使用
ClientBase<T>
后代而不是ChannelFactory .如果开发人员想要手动处理代理/通道,则会暴露扩展方法 .
我写了a simple base class来处理这个问题 . 它可以作为NuGet package使用,而且非常易于使用 .
如果您不需要IoC或正在使用自动生成的客户端(服务引用),那么您可以简单地使用包装器来管理关闭,并让GC在客户端处于安全状态时获取不会引发任何异常的情况 . GC将在serviceclient中调用Dispose,这将调用
Close
. 由于它已被封闭,因此不会造成任何损害 . 我在 生产环境 代码中使用它没有问题 .然后,当您访问服务器时,您创建客户端并在autodisconect中使用
using
:What is this?
这是已接受答案的CW版本,但(我认为完整)包括异常处理 .
接受的答案引用this website that is no longer around . 为了省去麻烦,我在这里列出了最相关的部分 . 另外,我稍微修改了它以包含exception retry handling来处理那些讨厌的网络超时 .
Simple WCF Client Usage
生成客户端代理后,这就是实现它所需的全部内容 .
ServiceDelegate.cs
将此文件添加到您的解决方案中除非您想要更改重试次数或更改次数,否则不需要对此文件进行任何更改你想要处理的例外 .
PS:我已将此帖发布为社区维基 . 我不会从这个答案中收集“积分”,但如果您同意该实施,或者编辑它以使其更好,则您更愿意支持它 .
我想在Marc Gravell's answer中添加Service的实现,以便使用ServiceClient而不是ChannelFactory .
这是Microsoft推荐的处理WCF客户端调用的方法:
有关详细信息,请参阅:Expected Exceptions
Additional information 很多人似乎在WCF上问这个问题,微软甚至创建了一个专门的示例来演示如何处理异常:
C:\ WF_WCF_Samples \ WCF \基本\客户端\ ExpectedExceptions \ CS \客户端
下载示例:C#或VB
考虑到在这个问题上有很多问题involving the using statement,(heated?) Internal discussions和threads,我只是把它搞砸了,并为我的服务器应用程序实现了这种冗长(但可信)的方式的WCF客户端 .
Optional Additional Failures to catch
许多例外来自
CommunicationException
,我认为大多数例外都不应该重审 . 我在MSDN上浏览了每个例外,并找到了一个可重试异常的简短列表(除了上面的TimeOutException
) . 如果我错过了应该重试的异常,请告诉我 .不可否认,这是一些平凡的代码 . 我目前更喜欢this answer,并且在该代码中看不到任何可能导致问题的"hacks" .
您还可以使用
DynamicProxy
扩展Dispose()
方法 . 这样你可以做类似的事情:我使用Castle动态代理来解决Dispose()问题,并且当它处于不可用状态时也实现了自动刷新通道 . 要使用它,您必须创建一个继承服务 Contract 和IDisposable的新接口 . 动态代理实现此接口并包装WCF通道:
我喜欢这个,因为您可以注入WCF服务而无需消费者担心WCF的任何细节 . 并没有像其他解决方案那样增加任何瑕疵 .
看看代码,它实际上非常简单:WCF Dynamic Proxy
使用扩展方法:
我在这篇文章中提到了几个答案,并根据我的需要进行了定制 .
我希望能够在使用之前使用WCF客户端执行某些操作,以便
DoSomethingWithClient()
方法 .这是辅助类:
我可以用它作为:
摘要
使用本答案中描述的技术,可以使用以下语法在using块中使用WCF服务:
您当然可以进一步调整它以实现特定于您的情况的更简洁的编程模型 - 但重点是我们可以创建
IMyService
的实现来表示正确实现一次性模式的通道 .详情
到目前为止给出的所有答案都解决了在
IDisposable
的WCF Channels 实现中绕过"bug"的问题 . 似乎提供最简洁的编程模型(允许您使用using
块来处理非托管资源)的答案是this one - 修改代理以实现IDisposable
并实现无错误的实现 . 这种方法的问题是可维护性 - 我们必须为我们使用的代理重新实现此功能 . 在这个答案的变体中,我们将看到我们如何使用组合而不是继承来使这种技术变得通用 .第一次尝试
似乎有各种各样的
IDisposable
实现实现,但为了论证,我们将使用currently accepted answer使用的自适应 .有了上面的课程,我们现在可以写了
这允许我们使用
using
块来使用我们的服务:使这个通用
到目前为止我们所做的只是重新制定Tomas' solution . 阻止此代码通用的是,必须为我们想要的每个服务 Contract 重新实现
ProxyWrapper
类 . 我们现在将看一个允许我们使用IL动态创建此类型的类:通过我们的新助手类,我们现在可以编写
请注意,对于继承
ClientBase<>
(而不是使用ChannelFactory<>
)的自动生成的客户端,或者如果要使用IDisposable
的其他实现来关闭通道,您也可以使用相同的技术(稍作修改) .我有一个自己的包装器,用于实现Dispose的通道,如下所示:
这看起来效果很好,并允许使用使用块 .
实际上,虽然我blogged(见Luke's answer),我认为this比我的IDisposable包装更好 . 典型代码:
(根据评论编辑)
由于
Use
返回void,处理返回值的最简单方法是通过捕获的变量:我有终于找到了解决这个问题的一个坚实步骤 .
Codeplex有一个名为 Exception Handling WCF Proxy Generator 的项目 . 它基本上为Visual Studio 2008安装了一个新的自定义工具,然后使用此工具生成新的服务代理(添加服务引用) . 它有一些很好的功能来处理故障通道,超时和安全处置 . 这里有一个很棒的视频,名为ExceptionHandlingProxyWrapper,详细解释了它是如何工作的 .
您可以安全地再次使用
Using
语句,如果通道在任何请求(TimeoutException或CommunicationException)上出现故障,Wrapper将重新初始化故障通道并重试查询 . 如果失败则会调用Abort()
命令并处理代理并重新抛出异常 . 如果服务抛出一个FaultException
代码,它将停止执行,代理将被安全中止,并按预期抛出正确的异常 .我们的系统架构通常使用Unity IoC框架来创建ClientBase的实例,因此没有确定的方法来强制其他开发人员甚至使用
using{}
块 . 为了使它尽可能简化,我创建了这个扩展ClientBase的自定义类,并处理关闭dispose上的通道,或者在有人没有明确处理Unity创建的实例的情况下完成 .还有一些东西需要在构造函数中完成,以设置自定义凭据和内容的通道,所以这里也是......
然后客户可以简单地:
呼叫者可以执行以下任何操作:
我喜欢这种关闭连接的方式:
以下是来自the question的源的增强版本,并扩展为缓存多个通道工厂,并尝试按 Contract 名称在配置文件中查找 endpoints .
它使用.NET 4(具体为:contravariance,LINQ,
var
):对于那些感兴趣的人,这里是接受答案的VB.NET翻译(下面) . 为简洁起见,我对它进行了一些改进,结合了其他人的一些技巧 .
我承认它不是原始标签(C#)的主题,但由于我无法找到这个精细解决方案的VB.NET版本,我认为其他人也会看起来 . Lambda翻译可能有点棘手,所以我想为别人省事 .
请注意,此特定实现提供了在运行时配置
ServiceEndpoint
的功能 .Code:
Usage:
如果在IServiceOriented.com倡导的解决方案和David Barret's blog倡导的解决方案之间做出选择,我更喜欢通过覆盖客户端的Dispose()方法提供的简单性 . 这允许我继续使用using()语句,就像人们对一次性对象所期望的那样 . 但是,正如@Brian指出的那样,这个解决方案包含一个竞争条件,因为状态在检查时可能不会出现故障,但可能在调用Close()时,在这种情况下仍然会发生CommunicationException .
因此,为了解决这个问题,我采用了一种混合两全其美的解决方案 .
我这样做的方法是创建一个显式实现IDisposable的继承类 . 这对使用gui添加服务引用(添加服务引用)的人很有用 . 我只是在生成服务引用的项目中删除此类,并使用它而不是默认客户端:
注意:这只是dispose的一个简单实现,如果您愿意,可以实现更复杂的dispose逻辑 .
然后,您可以使用安全客户端替换使用常规服务客户端进行的所有调用,如下所示:
我喜欢这个解决方案,因为它不需要我访问接口定义,我可以像我期望的那样使用
using
语句,同时允许我的代码看起来或多或少相同 .您仍然需要处理可以抛出的异常,如此线程中的其他注释所指出的那样 .