我喜欢在using块中实例化我的WCF服务客户端,因为它几乎是使用实现IDisposable的资源的标准方式:
using (var client = new SomeWCFServiceClient())
{
//Do something with the client
}
但是,正如这篇MSDN文章中提到的,将WCF客户端包装在using块中可能会掩盖导致客户端处于故障状态(如超时或通信问题)的任何错误。长话短说,当调用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>(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块那样具有表现力和易于理解。
我目前正在尝试使用的解决方法是我第一次在blog.davidbarret.net上读到的。基本上,无论在哪里使用客户机的Dispose()方法,都要重写它。喜欢的东西:
public partial class SomeWCFServiceClient : IDisposable
{
void IDisposable.Dispose()
{
if (this.State == CommunicationState.Faulted)
{
this.Abort();
}
else
{
this.Close();
}
}
}
这似乎能够允许using块再次出现,而不会有隐藏故障状态异常的危险。
那么,使用这些变通方法还有其他需要注意的问题吗?有没有人想出更好的办法?
我写了一个高阶函数来让它正确工作。我们已经在几个项目中使用了这个方法,看起来效果不错。这就是从一开始就应该做的事情,没有“使用”范式等等。
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);
这和你的例子很像。在一些项目中,我们编写强类型的帮助器方法,因此我们最终会写出类似“Wcf.UseFooService(f=>f…)”这样的东西。
从各方面考虑,我觉得它相当优雅。您遇到过什么特别的问题吗?
这允许插入其他漂亮的功能。例如,在一个站点上,站点代表登录用户向服务进行身份验证。(该网站本身没有凭据。)通过编写我们自己的“UseService”方法助手,我们可以按照我们想要的方式配置通道工厂,等等。我们也不局限于使用生成的代理——任何接口都可以。
根据Marc Gravell、MichaelGG和Matt Davis的回答,我们的开发人员得出了以下结论:
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));
它尽可能接近于“using”语法,在调用void方法时不必返回一个虚拟值,并且可以对服务进行多次调用(并返回多个值),而不必使用元组。
此外,如果需要,您可以将此用于ClientBase<T>后裔,而不是ChannelFactory。
如果开发人员希望手动处理代理/通道,则会公开扩展方法。
我在这篇文章中提到了一些答案,并根据我的需要定制了它。
我想在使用WCF客户端之前做一些事情,所以使用DoSomethingWithClient()方法。
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;
}
}
下面是helper类:
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();
}
}
}
}
我可以把它用作:
string data = Service<ServiceClient>.Use(x => x.GetData(7));