WCF中继承与序列化的问题与解决方案

在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属性时需要注意以下几点:

  • 必须包含整个继承层次结构,以启用子类替换。添加子类不会自动添加其基类。
  • KnownType属性会降低应用程序性能。如果应用程序中到处使用替换,性能损失可能变得显著。
  • 如果不可避免地有很多基类/子类,Microsoft提供了其他替代方案,如ServiceKnownType属性和在应用程序配置文件的System.runtime.serialization部分编写预期替换类的列表。

.NET 4.0提供了一个更强大的工具DataContractResolver,它拦截序列化/反序列化过程,允许通过自定义代码更改行为。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485