另一种选择wo您应该实施服务合约的版本控制:此时,您可以利用WCF自己的功能忽略不会中断客户端的微小更改,如Versioning Strategies页面上列出的那样。
在图1中,您可以看到,向操作签名添加新参数时,从操作签名中删除参数并添加新操作,客户端不受影响。
如果仍然发生重大更改或您的客户端必须支持这两个版本(请纠正我,如果我错了,因为我不知道您的部署策略),您可以在不同端点提供不同版本的服务并在您的客户端代码中拥有一个WCF客户端工厂,然后可以将其配置为返回适当端点的客户端。
在这一点上,你已经隔离了不同客户端的不同实现,这可能比现在更清洁,更少维护噩梦。
非常基本的示例实现以澄清一些事情:假设我们有我们的服务,旧的和新的两种不同的合同。
[ServiceContract(Name = "Service", Namespace = "http://stackoverflow.com/2012/03")]
public interface IServiceOld
{
[OperationContract]
void DoWork();
}
[ServiceContract(Name = "Service", Namespace = "http://stackoverflow.com/2012/04")]
public interface IServiceNew
{
[OperationContract]
void DoWork();
[OperationContract]
void DoAdditionalWork();
}
请注意两个服务如何具有相同的名称但名称空间不同。
让我们来处理让客户端必须能够同时支持扩展和新服务以及旧服务的问题。假设我们以前只是称为DoWork,并且我们想要处理客户端的情况,我们想调用DoAdditionalWork方法,因为假设DoAdditionalWork可能需要来自客户端的一些额外参数。那么服务的配置可以是这样的:
<service name="ConsoleApplication1.Service">
<endpoint address="http://localhost:8732/test/new" binding="wsHttpBinding" contract="ConsoleApplication1.IServiceNew" />
<endpoint address="http://localhost:8732/test/old" binding="wsHttpBinding" contract="ConsoleApplication1.IServiceOld" />
...
</service>
好,我们有服务端,现在到了有趣的部分:我们愿与使用相同的接口服务进行通信。在这种情况下,我会使用旧的,但您可能需要在其间插入适配器。理想情况下,我们的客户端代码,我们会做这样的事情:
IServiceOld client = *Magic*
client.DoWork();
在这种情况下,神奇的是一个简单的工厂是这样的:
internal class ClientFactory
{
public IServiceOld GetClient()
{
string service = ConfigurationManager.AppSettings["Service"];
if(service == "Old")
return new ClientOld();
else if(service == "New")
return new ClientNew();
throw new NotImplementedException();
}
}
我委托的使用到客户端的决定app.config,但是你可以在那里插入你的版本检查。 ClientOld的实现仅仅是IServiceOld一个普通WCF客户端:
public class ClientOld : IServiceOld
{
private IServiceOld m_Client;
public ClientOld()
{
var factory = new ChannelFactory<IServiceOld>(new WSHttpBinding(), "http://localhost:8732/test/old");
m_Client = factory.CreateChannel();
}
public void DoWork()
{
m_Client.DoWork();
}
...
}
ClientNew而是实现我们希望的行为,即调用DoAdditionalWork操作:
public class ClientNew : IServiceOld
{
private IServiceNew m_Client;
public ClientNew()
{
var factory = new ChannelFactory<IServiceNew>(new WSHttpBinding(), "http://localhost:8732/test/new");
m_Client = factory.CreateChannel();
}
public void DoWork()
{
m_Client.DoWork();
m_Client.DoAdditionalWork();
}
...
}
就是这样,现在我们的客户可以用在下面的例子中:
var client = new ClientFactory().GetClient();
client.DoWork();
我们取得了什么成果?使用客户端的代码是从实际的WCF客户端必须做的额外工作中抽象出来的,并决定将使用哪个客户端委派给工厂。我希望这个样本的一些变化/扩展适合您的需求。
感谢您的快速响应!至少,我认为我们应该用IsValidToRun这样的方法来集中这个逻辑。我认为ServerFeatures类是一个好主意,并且可以帮助我们跟踪哪些检查可以在新版本发布时取消。 – 2012-04-04 13:31:16
@JohnRussell,这也将给你一个很好的方式来打破目的。当你不再需要检查,编译,然后修复所有依赖它的被破坏的地方时,你可以删除一个'ServerFeatures'版本。 – 2012-04-04 13:38:56
准确!我喜欢!我想我将实际上使ServerFeature成为一个枚举,然后用辅助方法执行Version to ServerFeature映射,例如一个大转换语句。这将允许我们重复使用相同版本的多个功能,并将所有这些逻辑放在一个位置。然后,IsValidToRun可以将ServerFeature作为参数,并有望阻止消费者使用动态版本对象。谢谢您的帮助! – 2012-04-04 15:08:29