WCF开发之契约的版本控制
通常来说有两种控制方式:
• 严格的版本控制:
– 对于服务或者数据契约的任何修改都需要形成独立
的新版本(formal versioning)
• 实用的版本控制:
– 对于向后和向前同时兼容的版本相容性– 服务与数
据契约
– 保存未知元素(如果可行的话)
– 容忍缺失的元素
– 只有当签名发生显著变化并无法和老的版本相容时才需要形成独立的新版本
模拟一个场景:
• ServiceA上的版本1.0的服务契约
• 两个操作:1,2
非严格的方法:
– 在现有的契约上添加新的方法
– 相同的命名空间
– 更新客户端到最新的版本
半严格的方法1:
– 在使用新的命名空间的新的契约上添加新的方法
– 继承自旧的契约
– 在原有的端点上暴露新的契约
半严格的方法2:
– 在使用新的命名空间的新的契约上添加新的方法
– 继承自旧的契约
– 在新的端点上暴露新的契约
独立的新版本控制:
– 为所有的操作在新的命名空间下创建新的契约
– 没有继承关系
– 独立创建服务和端点
– 共享基类服务类型中的公共函数
下面通过一个具体的Demo1来体会一下通过继承处理的版本控制的方法:
初始的程序很简单,一个Service一个Client,主要代码如下:
Service代码:
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace BusinessServices
{
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
[ServiceContract]
public interface IMyServiceA
{
[OperationContract]
string Operation1();
[OperationContract]
string Operation2();
}
}
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace BusinessServices
{
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in App.config.
public class MyService : IMyServiceA
{
public string Operation1()
{
return "IMyServiceA.Operation1() is invoked.";
}
public string Operation2()
{
return "IMyServiceA.Operation2() is invoked.";
}
}
}
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service behaviorConfiguration="BusinessServices.Service1Behavior"
name="BusinessServices.MyService">
<endpoint address="" binding="wsHttpBinding" contract="BusinessServices.IMyServiceA">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/WCF/Charlesliu" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="BusinessServices.Service1Behavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Client代码:
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WinClientV1
{
public partial class Form1 : Form
{
MyServiceReference.MyServiceAClient proxy = new WinClientV1.MyServiceReference.MyServiceAClient();
public Form1()
{
InitializeComponent();
}
private void btnOperation1_Click(object sender, EventArgs e)
{
MessageBox.Show(proxy.Operation1());
}
private void btnOperation2_Click(object sender, EventArgs e)
{
MessageBox.Show(proxy.Operation2());
}
}
}
程序很简单,主要实现了调用服务并显示调用的是那个方法。
好了现在有新的需求来了,需要添加一个新的方法Operation3,我们通过继承来实现,
首先创建一个新的IMyServiceA2接口程序,使得他继承自IMyServiceA:
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace BusinessServices
{
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
[ServiceContract]
public interface IMyServiceA2 : IMyServiceA
{
[OperationContract]
string Operation3();
}
}
然后修改Service.cs,让他实现IMyServiceA2:
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace BusinessServices
{
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in App.config.
public class MyService : IMyServiceA2
{
public string Operation1()
{
return "IMyServiceA.Operation1() is invoked.";
}
public string Operation2()
{
return "IMyServiceA.Operation2() is invoked.";
}
public string Operation3()
{
return "IMyServiceA.Operation3() is invoked.";
}
}
}
然后修改app.config到IServiceA2
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service behaviorConfiguration="BusinessServices.Service1Behavior"
name="BusinessServices.MyService">
<endpoint address="" binding="wsHttpBinding" contract="BusinessServices.IMyServiceA2">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8731/WCF/Charlesliu" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="BusinessServices.Service1Behavior">
<!-- To avoid disclosing metadata information,
set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="True"/>
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
此时我们启动ServiceA2和WinClient1,发现老的客户端仍然可以正常使用。原因是因为A2是继承自A的,所以A2只是A的扩展,并没有改变A的契约,而且A2是包含了A的契约的,所以A的老客户端可以正常使用。
现在我们想要为A2发布一个新的客户端WinClientV2:
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WinClientV2
{
public partial class Form1 : Form
{
MyServiceReference.MyServiceA2Client proxy = new WinClientV2.MyServiceReference.MyServiceA2Client();
public Form1()
{
InitializeComponent();
}
private void btnOperation1_Click(object sender, EventArgs e)
{
MessageBox.Show(proxy.Operation1());
}
private void btnOperation2_Click(object sender, EventArgs e)
{
MessageBox.Show(proxy.Operation2());
}
private void btnOperation3_Click(object sender, EventArgs e)
{
MessageBox.Show(proxy.Operation3());
}
}
}
WinClientV2当然可以正常使用。
上面的例子实现了通过继承方式来扩展老的服务,并且不影响老的客户端的使用,在实际应用中这种情况非常多。上面的例子有一点要提一下:
Client1中的代理类中的Action如下:
http://tempuri.org/IMyServiceA/Operation1
http://tempuri.org/IMyServiceA/Operation12
Client2中的代理类中的Action如下:
http://tempuri.org/IMyServiceA/Operation1
http://tempuri.org/IMyServiceA/Operation2
http://tempuri.org/IMyServiceA2/Operation3
可以看出来Client实际上是同多Action来调用不同的Operation的。
上面使用继承方式实现的,也可以不用,另一种方法是一个Host提供2个服务,客户端各调各的。
using System.ServiceModel;
namespace BusinessServiceContracts
{
[ServiceContract(Name="ServiceAContract", Namespace = "http://www.thatindigogirl.com/samples/2006/06")]
public interface IServiceA
{
[OperationContract]
string Operation1();
[OperationContract]
string Operation2();
}
public class ServiceA : IServiceA
{
public string Operation1()
{
return "IServiceA.Operation1() invoked.";
}
public string Operation2()
{
return "IServiceA.Operation2() invoked.";
}
}
}
using System.ServiceModel;
namespace BusinessServiceContracts
{
[ServiceContract(Name="ServiceAContract2", Namespace = "http://www.thatindigogirl.com/samples/2006/08")]
public interface IServiceA2
{
[OperationContract]
string Operation1();
[OperationContract]
string Operation2();
[OperationContract]
string Operation3();
}
public class ServiceA2 : IServiceA2
{
public string Operation1()
{
return "IServiceA2.Operation1() invoked.";
}
public string Operation2()
{
return "IServiceA2.Operation2() invoked.";
}
public string Operation3()
{
return "IServiceA2.Operation3() invoked.";
}
}
}
值得注意的是app.config
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="serviceBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="BusinessServices.ServiceA" behaviorConfiguration="serviceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000"/>
</baseAddresses>
</host>
<endpoint address="ServiceA" contract="BusinessServiceContracts.IServiceA" binding="basicHttpBinding" />
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
</service>
<service name="BusinessServices.ServiceA2" behaviorConfiguration="serviceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8001"/>
</baseAddresses>
</host>
<endpoint address="ServiceA2" contract="BusinessServiceContracts.IServiceA2" binding="basicHttpBinding" />
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
</service>
</services>
</system.serviceModel>
</configuration>
关于数据契约的版本控制和上面的服务契约的版本控制相似:
• 非严格方法:
– 不要删除required的成员
– 添加新的non-required成员
– 不改变命名空间
• 形成独立的新版本:
– 根据业务逻辑和数据层的需要,更新业务逻辑对象(保留类型名称)
– 管理数据契约命名空间的版本
– 管理服务契约的版本
• 对于1.0版本的客户端:
– 在原始的数据契约命名空间上创建旧的业务逻辑对象(新的类型名称)的副本
– 保留1.0版本的服务端点(endpoint)
– 在调用业务逻辑层之间映射到2.0的版本上
1.0版本的数据契约
支持1.0版本和2.0版本的数据契约
将1.0版本映射到2.0版本的数据契约上
总结
• WCF缺省提供版本相容性支持
• 非严格的版本控制方法降低了开发耗费
• 严格的版本控制方法需要有远见和计划性
• 尽早地考虑契约版本控制策略以应对一些无法避免的变化 (完)