2

我正在开发一个必须支持向后兼容性的客户端-服务器应用程序(.NET 4,WCF)。换句话说,旧客户端应该与新服务器兼容,反之亦然。结果,我们的客户端代码中充斥着以下语句:

if (_serverVersion > new Version(2, 1, 3))
{
    //show/hide something or call method Foo()...
}
else
{
   //show/hide something or call method Foo2()...
}

显然,这在某种程度上变成了维护的噩梦。幸运的是,我们被允许打破与每个次要版本的向后兼容性。当我们达到可能破坏兼容性的地步时,我想清理上面示例形式的代码。

我的问题:

(1)当它们不再“有效”时,有没有办法轻松识别这些代码块?我最初的想法是根据程序集的版本以某种方式有条件地应用 Obsolete 属性。当我们得到一个新的次要版本时,Obsolete 属性会“启动”,突然间我们会有几个指向这些代码块的编译器警告......有没有人做过这样的事情?或者有没有更好的方法来管理这个?

(2) 每次看到硬编码的版本,例如new Version(2, 1, 3). 更糟糕的是,在开发过程中,我们不知道发布的最终版本,所以版本检查是基于当前版本号 + 1 时开发人员添加检查。虽然这有效,但它不是很干净。关于如何改进的任何想法?

谢谢!

4

2 回答 2

1

我建议至少创建一个可以执行如下逻辑的方法:

public static class ServerUtilities
{
    public static bool IsValidToRun(Version desiredVersion)
    {
        if (_serverVersion >= desiredVersion)
            return true;
        else if (/* your other logic to determine if they're in some acceptable range */)
            return true;

        return false;
    }
}

然后,像这样使用:

if (ServerUtilities.IsValidToRun(new Version(2, 1, 3)))
{
    // Do new logic
}
else
{
    // Do old logic
}

如果您需要集中版本,请拥有一个静态的功能存储库到版本映射,以便您可以调用:

if (ServerUtilities.IsValidToRun(ServerFeatures.FancyFeatureRequiredVersion))
{
    ...
}

public static class ServerFeatures
{
    public static Version FancyFeatureRequiredVersion
    {
        get { return new Version(2, 1, 3); }
    }
}
于 2012-04-04T13:10:24.893 回答
0

另一种方法是实现服务合同的版本控制:此时您可以利用 WCF 自己的功能来忽略不会破坏客户端的微小更改,如此版本控制策略页面中所列。

在图 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 客户端必须做的额外工作中抽象出来的,并且关于使用哪个客户端的决定委托给工厂。我希望这个样本的一些变化/扩展能够满足您的需求。

于 2012-04-04T14:16:29.420 回答