在日常的开发工作中,经常需要与WCF服务进行交互,主要是通过Web和MSMQ进行通信。经过一些初期的调整后,一切都运行得很顺利。通常情况下,这些服务都是在Windows到Web的通信环境中,可以控制客户端和服务器端的实现,因此有很多共享的类型。
然而,最近遇到一个快速增长的Web服务API,它遇到了元数据限制问题(显然,有最多30个方法的指导原则是有原因的!),这意味着无论是VS2008还是svcutil都无法可靠地生成可用的代理,除非改变每台可能需要生成代理的机器的标准配置。
经过一次大规模的重构,将一个服务拆分成了14个更小、更紧密的服务——这让面临一个更加巨大的任务:将这14个服务替换原来的一个服务。显然,这不会是一件容易的事,因为需要替换一个服务引用为14个!由于这个服务在几个项目中被使用,这可能会导致被钉在墙上。
在经过一番Google搜索、反射和检查生成的代码后,很快意识到,对于情况,根本不需要生成代理!因为所有的类型都是通过公共库在客户端和服务器之间共享的,而且WCF配置在配置文件中,可以直接使用ChannelFactory<>。这个聪明的类在WCF中做了所有的繁重工作,只需要服务合同接口和一个端点名称!
就像魔法一样,代理出现了...
using System;
using System.ServiceModel;
namespace MartinOnDotNet.Helpers.WCF
{
public class DynamicTidyProxy<TServiceContract> : IDisposable
{
public DynamicTidyProxy()
{
EndpointName = typeof(TServiceContract).Name;
}
public DynamicTidyProxy(string endpointConfigurationName)
{
EndpointName = endpointConfigurationName;
}
public string EndpointName { get; set; }
private ChannelFactory<TServiceContract> _channel;
private TServiceContract _client;
public TServiceContract Client
{
get
{
if (_client == null)
{
if (!typeof(TServiceContract).IsInterface)
throw new NotSupportedException("TServiceContract must be an interface!");
if (string.IsNullOrEmpty(EndpointName))
throw new NotSupportedException("EndpointName must be set prior to use!");
_channel = new ChannelFactory<TServiceContract>(EndpointName);
_client = _channel.CreateChannel();
}
return _client;
}
}
public void Dispose()
{
if (_channel != null)
{
_channel.CloseConnection();
}
GC.SuppressFinalize(this);
}
}
}
已经将默认构造函数编码为使用服务合同接口的类型名称...所以请确保配置文件匹配!然后可以在代码中动态创建WCF代理:
using (var proxy = new DynamicTidyProxy<IMagicComplexServiceContract>())
{
proxy.Client.DoSomeMagicallyComplexOperation();
}
不需要svcutil或VS2008服务引用!这也意味着不必花费那么多时间进行重构!(至少今天不用...保证这会发生)。
public static void CloseConnection(this ICommunicationObject serviceClient)
{
if (serviceClient == null)
return;
try
{
if (serviceClient.State == CommunicationState.Opened)
{
serviceClient.Close();
}
else
{
serviceClient.Abort();
}
}
catch (CommunicationException ex)
{
Logging.Logger.Log(ex);
try
{
serviceClient.Abort();
}
catch { }
}
catch (TimeoutException ex)
{
Logging.Logger.Log(ex);
try
{
serviceClient.Abort();
}
catch { }
}
catch (Exception ex)
{
Logging.Logger.Log(ex);
try
{
serviceClient.Abort();
}
catch { }
throw;
}
}