在Windows Communication Foundation (WCF)中处理继承关系和序列化时,可能会遇到一些挑战。本文将介绍这些问题的背景、原因以及如何通过使用KnownType属性和DataContractResolver来解决这些问题。
假设有一个WCF应用程序,其中服务端(Factory)向客户端(Factory Client)发送信息。服务合同定义了两个方法:TryService用于测试WCF通信,GetResult用于创建并发送一个包含BaseElement对象的字典。
[ServiceContract]
public interface IFactory
{
[OperationContract]
string TryService(string echo);
[OperationContract]
Result GetResult();
}
服务实现类Factory实现了IFactory接口,GetResult方法创建了一个包含BaseElement的字典并返回。
public class Factory : IFactory
{
public string TryService(string echo)
{
return echo;
}
public Result GetResult()
{
var baseElem = new BaseElement
{
BaseName = "BaseElement"
};
return new Result
{
Elements = new Dictionary
{
{ "1", baseElem }
}
};
}
}
BaseElement类是一个简单的数据契约类,包含一个BaseName属性。
[DataContract]
public class BaseElement
{
[DataMember]
public string BaseName { get; set; }
}
服务配置定义了一个基本的HTTP绑定,客户端实现FactoryClient类,通过ChannelFactory创建通道并调用服务方法。
class FactoryClient : IFactory
{
public FactoryClient()
{
var myBinding = new BasicHttpBinding();
var endpoint = new EndpointAddress("http://localhost/services/FactoryService");
ChannelFactory = new ChannelFactory<IFactory>(myBinding, endpoint);
}
public string TryService(string echo)
{
var client = ChannelFactory.CreateChannel();
var response = client.TryService(echo);
((ICommunicationObject)client).Close();
return response;
}
public Result GetResult()
{
var client = ChannelFactory.CreateChannel();
var response = client.GetResult();
((ICommunicationObject)client).Close();
return response;
}
public ChannelFactory<IFactory> ChannelFactory { get; set; }
}
客户端程序创建FactoryClient实例,调用TryService和GetResult方法,并打印结果。
class Program
{
static void Main(string[] args)
{
FactoryClient factory = new FactoryClient();
var tt = factory.TryService("Bill");
Console.WriteLine("Service says: " + tt);
var res = factory.GetResult();
foreach (var item in res.Elements)
{
Console.WriteLine("Key :" + item.Key + " Value: " + item.Value);
}
Console.ReadLine();
}
}
现在,引入一个新的类SuperElement,它继承自BaseElement,并添加了一个SuperName属性。在GetResult方法中返回SuperElement实例时,客户端调用GetResult方法会抛出异常。
[DataContract]
public class SuperElement : BaseElement
{
[DataMember]
public string SuperName { get; set; }
}
这是因为WCF在序列化和反序列化时是按值传递对象,而不是按引用传递。客户端期望得到的是一个BaseElement类型的字典,但服务端发送的是一个包含SuperElement的字典,导致客户端无法正确反序列化。
.NET Framework 3.0引入了KnownType属性,用于解决此类问题。在BaseElement类上使用KnownType属性,告诉WCF服务/客户端,如果遇到SuperElement实例,也可以进行序列化/反序列化。
[DataContract]
[KnownType(typeof(SuperElement))]
public class BaseElement
{
[DataMember]
public string BaseName { get; set; }
}
使用KnownType属性后,序列化和反序列化将根据子类类型(SuperElement)进行,而不是基类(BaseElement)。这样,客户端就可以接受子类类型的实例。
使用KnownType属性时需要注意以下几点:
.NET 4.0提供了一个更强大的工具DataContractResolver,它拦截序列化/反序列化过程,允许通过自定义代码更改行为。