对支持面向服务的架构来说,数据契约版本化会随着时间推移称为面向服务的一个重要方面。随着时间推移,比如创建了新的服务,它生成了一个数据契约的新版本,通过添加额外的信息。而不是重编译所有之前使用老的数据契约版本的客户端和服务端,你可能希望它们可以平滑的升级以便于可以共享公共数据,这也正是DataContractSerializer 要做的事情。如果有额外的数据,DataContractSerializer 将会抛弃额外的信息。但这并不是在所有情况下都能正常工作。如果数据被接受后又发送回给客户端,忽略任何额外数据意味着可能会丢失信息。一个例子是一个新的客户端发送数据给一个将信息存储在一个数据库中以用来在未来的某个时刻访问的旧服务。在这种情况下,如果客户端发送给服务端过程中有任何额外信息,它将在数据发送回给客户端时丢失。这也是IExentsibleDataObject接口要解决的问题。它提供一个接口给不知道数据契约的外部数据。它通过将反序列化过程中的未知数据存储到一个ExtensibleDataObject类中实现的。
DataContractSerializer默认行为就是忽略任何未知数据,除非IExentsibleDataObject接口在契约上实现。这里有一个在一个Employee类上使用的两个数据契约。第一个在列表6.21中显示的数据契约有三个字段: FirstName, LastName和EmployeeId.第二个在列表6.22中显示的数据契约是第一个数据契约新版本,它添加了一个额外字段,SSN。
提示 确定实现了IExentsibleDataObject
可以不通过使用svcutil.exe或者添加服务引用来共享你的数据契约。这是通过向包含你的数据契约的程序集添加一个引用实现的。在这种情况相爱,确定数据契约实现了IExtensibleDataObject或者它们不支持回环序列化。
列表6.21 初始的Employee 契约
using System.Runtime.Serialization;
namespace EssentialWCF
{
[DataContract]
public class Employee
{
private int employeeID;
private string firstName;
private string lastName;
public Employee()
{
}
public Employee(int employeeID, string firstName, string lastName)
{
this.employeeID = employeeID;
this.firstName = firstName;
this.lastName = lastName;
}
[DataMember]
public int EmployeeID
{
get { return employeeID; }
set { employeeID = value; }
}
[DataMember]
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
[DataMember]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
}
列表6.22显示了Employee契约的一个包含了一个额外字段SSN的新版本,它实现了employee的社会安全账号。
列表6.22 新的Employee契约
using System.Runtime.Serialization;
namespace EssentialWCF
{
[DataContract]
public class Employee
{
private int employeeID;
private string firstName;
private string lastName;
private string ssn;
public Employee()
{
}
public Employee(int employeeID, string firstName, string lastName, string ssn)
{
this.employeeID = employeeID;
this.firstName = firstName;
this.lastName = lastName;
this.ssn = ssn;
}
[DataMember]
public int EmployeeID
{
get { return employeeID; }
set { employeeID = value; }
}
[DataMember]
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
[DataMember]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
[DataMember]
public string SSN
{
get { return ssn; }
set { ssn = value; }
}
}
}
列表6.21和列表6.22中的数据契约是不同的。你可能会认为使用原来数据契约的服务奖不会接受使用一个新的数据契约的客户端的通信请求。事实上,所有事情都工作的很好。原因是新数据契约中的所有字段都在原始数据契约中存在。这意味着服务端需要的所有信息都存在。这个时候发生的就是服务端忽略掉额外的数据。这可以通过下面列表6.23中显示的UpdateEmployee服务显示出来。服务生成了一个Employee实例,做一些额外的工作,然后把同样的Employee实例返回给客户端。
列表6.23 Employee更新服务
using System.ServiceModel;
namespace EssentialWCF
{
[ServiceContract]
public interface IEmployeeInformation
{
[OperationContract]
Employee UpdateEmployee(Employee employee);
}
public class EmployeeInformation : IEmployeeInformation
{
public EmployeeInformation()
{
}
public Employee UpdateEmployee(Employee emp)
{
//Pretend to do something here...
//Not really important for this demo.
//We return the employee instance back to the client.
return emp;
}
}
}
相应的客户端代码在列表6.24显示
列表6.24 Employee更新客户端
namespace Client
{
class Program
{
static void Main(string[] args)
{
Employee e = new Employee() { EmployeeID = 123456, FirstName = "Daniel", LastName = "Dong", SSN = "000-00-0000" };
Console.WriteLine("{0} {1}, {2}, {3}", new object[]{e.FirstName, e.LastName, e.EmployeeID, e.SSN});
using (EmployeeInformationClient client = new EmployeeInformationClient())
{
e = client.UpdateEmployee(e);
}
Console.WriteLine("{0} {1}, {2}, {3}", new object[] { e.FirstName, e.LastName, e.EmployeeID, e.SSN });
Console.WriteLine("Press [ENTER] to exit.");
Console.ReadLine();
}
}
}
服务端返回的结果中不包含SSN字段。这意味着由于数据契约版本不兼容导致我们不可以发送我们的数据契约到服务端并返回。所以如何才能修改我们的服务使其接受未知数据并恰当的返回?幸运的是,WCF提供了一个方法来接收并存储未知数据。我们可以改变我们的服务端的数据契约来允许它不了解的额外的数据。为了实现这个你必须在数据契约上实现IExtensibleDataObject接口,默认是通过svcutil.exe或者添加服务引用在生成客户端代理时完成的。列表6.25显示了支持IExtensibleDataObject接口的初始Employee契约。
列表6.25 使用IExentsibleDataObject的初始数据契约
using System.Runtime.Serialization;
namespace EssentialWCF
{
[DataContract]
public class Employee : IExtensibleDataObject
{
private ExtensionDataObject extensionData;
private int employeeID;
private string firstName;
private string lastName;
public Employee()
{
}
public Employee(int employeeID, string firstName, string lastName)
{
this.employeeID = employeeID;
this.firstName = firstName;
this.lastName = lastName;
}
[DataMember]
public int EmployeeID
{
get { return employeeID; }
set { employeeID = value; }
}
[DataMember]
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
[DataMember]
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
#region IExtensibleDataObject Members
public ExtensionDataObject ExtensionData
{
get
{
return extensionData;
}
set
{
extensionData = value;
}
}
#endregion
}
}
通过这个改变现在客户端可以从服务端接收SSN成员。假设这个行为是在一个面向服务架构中期望的结果,你可能为了最佳实践在所有的数据契约上实现IExtensibleDataObject接口。