WCF:返回具有基础对象的合同的派生对象(DataContractResolver)

发布于 2024-12-20 01:56:25 字数 755 浏览 1 评论 0 原文

我有 WCF 派生/基础合同问题。我有一个返回 BaseThing 对象的服务器接口/合约:

[OperationContract]
BaseThing Get_base_thing();

实现此对象的服务器有一个 DerivedThing (派生自 BaseThing),并且想要将其作为 BaseThing 返回。 如何告诉 WCF 我只想传输 DerivedThingBaseThing 部分?

如果在 Get_base_thing 中,我只需返回对 DerivedThing 的引用,然后我得到一个 SerializationException 服务器端。

我想我需要定义一个 DataContractResolver,我查看了 MSDN 文章 使用数据契约解析器但这并不是 100% 清楚(至少对我来说)。

我的 DataContractResolver 应该如何告诉 WCF 仅传输我传递给它的派生对象的基本部分?

有没有什么方法可以通过 KnownType 属性更简单地做到这一点?

I have have a WCF derived/base contract issue. I have a server interface/contract that returns a BaseThing object:

[OperationContract]
BaseThing Get_base_thing();

The server that implements this has a DerivedThing (derived from BaseThing) and wants to return this as a BaseThing. How to tell WCF that I only want to transport the BaseThing part of DerivedThing?

If in Get_base_thing I just return a reference to a DerivedThing then I get a SerializationException server side.

I think I need to define a DataContractResolver and I looked at the MSDN article Using a Data Contract Resolver but this is not 100% clear (to me a least).

How should my DataContractResolver look to tell WCF to only transport the base part of the derived object I pass it?

Is there some way to do this more simply just with KnownType attribue?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

窗影残 2024-12-27 01:56:25

KnownType 无法解决此问题。

听起来好像您在服务器上使用的对象模型和您正在使用的服务契约之间存在严重分歧。似乎有 3 种可能的解决方案:

1) 正如您所确定的数据契约解析器,使其在所有操作中自动执行。有很多示例,包括这个: http://blogs.msdn.com/b/youssefm/archive/2009/06/05/introducing-a-new-datacontractserializer-feature-the-datacontractresolver.aspx

2) 调整您的对象模型以更好地匹配您的服务合同。也就是说,使用包含而不是继承来管理 BaseThing-DerivedThing 关系。这样,您就可以在服务器上使用 DerivedThing,并且只需通过网络返回 DerivedThing.BaseThing 即可。如果BaseThing需要从客户端传输到服务器,这也会工作得更好。

3)使用诸如 AutoMapper 之类的显式转换,因此在您的操作中很明显,服务器上使用的对象与暴露给外部世界的对象之间存在差异。

KnownType will not resolve this issue.

It sounds as if you have a serious divergence between the object model you're using at the server and the service contracts you're using. There seems to be 3 possible solutions:

1) Data Contract Resolver as you've identified to make it automatic across all your operations. There are a number of examples out there including this one: http://blogs.msdn.com/b/youssefm/archive/2009/06/05/introducing-a-new-datacontractserializer-feature-the-datacontractresolver.aspx.

2) Align your object model to better match your service contracts. That is, use containment rather than inheritance to manage the BaseThing-DerivedThing relationship. That way you work with DerivedThing at the server and simply return DerivedThing.BaseThing over the wire. If BaseThing needs to get transmitted from client to server, this will also work better.

3) Use explicit conversion using something like AutoMapper so it is obvious in your operations that there is a divergence between the objects being used at the server and those exposed to the outside world.

琴流音 2024-12-27 01:56:25

发布后我还发现了这个相同的问题 如何将派生类型序列化为基类。马克对我来说未被接受的第二个答案是解决这个问题的最简单的方法。即:
装饰派生类 [DataContract(Name="BaseClass")]
请注意,此解决方案意味着派生将作为该对象的所有每种传输情况的基础进行传输。对我来说,这不是问题,如果是的话,您需要走 DataContractResolver 路线。

关于 DataContractResolver 路线的一些注释:
1. 这使您能够在某些调用中将派生作为派生传递,但在其他调用上作为派生传递 - 如果您需要这样做 - 如果不使用 about Name= 方法。
2. 我使用 datacontractrsolver 文章中的 DeserializeAsBaseResolver 时遇到异常,因为knownTypeResolver 返回 false。为了解决这个问题,我忽略了该调用的返回值,并始终从 TryResolveType 返回 true。这似乎有效。
3. 我最初认为,因为我们将序列化为基础,所以我不需要派生类上的 [DataContract]。那是错误的。该对象被序列化为派生对象并反序列化为基础对象 - 因此您必须使用 [DataContract] 装饰派生对象,但不要将任何字段标记为 [DataMembers] 以避免它们被不必要的序列化。
4. 如果您有命令行主机和服务主机,那么您需要代码在两者中插入合同解析器。我发现将其作为静态放在我的解析器中很有用。
5. 请注意,调用 cd.Operations.Find("Get_gateway_data") 中的“Get_gateway_data”字符串是返回相关对象的合约方法的名称。您需要为您想要此行为的每个呼叫执行此操作。

此方法的最终代码:

public class DeserializeAsBaseResolver : DataContractResolver {

    public static void Install(ServiceHost service_host) {
        // Setup DataContractResolver for GatewayProcessing to GatewayData resolution:
        ContractDescription cd = service_host.Description.Endpoints[0].Contract;
        OperationDescription myOperationDescription = cd.Operations.Find("Get_gateway_data");
        DataContractSerializerOperationBehavior serializerBehavior = myOperationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
        if (serializerBehavior == null) {
            serializerBehavior = new DataContractSerializerOperationBehavior(myOperationDescription);
            myOperationDescription.Behaviors.Add(serializerBehavior);
        }
        serializerBehavior.DataContractResolver = new DeserializeAsBaseResolver();
    }

    public override bool TryResolveType(Type type, Type declaredType,
                                        DataContractResolver knownTypeResolver,
                                        out XmlDictionaryString typeName,
                                        out XmlDictionaryString typeNamespace) {

        bool ret = knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
        //return ret; // ret = false which causes an exception.
        return true;
    }

    public override Type ResolveName(string typeName, string typeNamespace,
                                    Type declaredType, DataContractResolver knownTypeResolver) {

        return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null) ?? declaredType;
    }

主机代码(服务或命令行):

using (ServiceHost service_host = new ServiceHost(typeof(GatewayServer))) {

// Setup DataContractResolver for GatewayProcessing to GatewayData resolution:
DeserializeAsBaseResolver.Install(service_host);

// Open the host and start listening for incoming messages.
try { service_host.Open(); }

After posting I also found this SO identical question How to serialize a derived type as base. The unaccepted second answer by marc for me is the easiest way to resolve this issue. That is:
Decorate the derived class with [DataContract(Name="BaseClass")]
Note that this solution means that derived will transport as base for all every case of transport of this object. For me that was not an issue if it is then you need to go the DataContractResolver route.

Some notes on the DataContractResolver route:
1. This enables you to pass the derived as derived on some calls but as base on other - if you need to do that - if not use about Name= approach.
2. I get an exception using the DeserializeAsBaseResolver from the datacontractrsolver article as it stands because the knownTypeResolver returns false. To fix that I ignor the return value of that call and always return true from TryResolveType. That seems to work.
3. I initially thought that because we were serializing as base that I didnt need [DataContract] on the derived class. That was wrong. The object is serialized as the derived object and derserialized as a base object - so you must decorate the derived with [DataContract] but don't mark any fields as [DataMembers] to avoid them being unnecessarily serialize.
4. If you have a command line host and a service host then you need the code to insert the contract resolver in both. I found it useful to put this as a static in my resolver.
5. Note that that the "Get_gateway_data" string in the call to cd.Operations.Find("Get_gateway_data") is the name of the contract method that returns the object concerned. You will need to do this for each call that you want this behaviour.

Final code for this approach:

public class DeserializeAsBaseResolver : DataContractResolver {

    public static void Install(ServiceHost service_host) {
        // Setup DataContractResolver for GatewayProcessing to GatewayData resolution:
        ContractDescription cd = service_host.Description.Endpoints[0].Contract;
        OperationDescription myOperationDescription = cd.Operations.Find("Get_gateway_data");
        DataContractSerializerOperationBehavior serializerBehavior = myOperationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
        if (serializerBehavior == null) {
            serializerBehavior = new DataContractSerializerOperationBehavior(myOperationDescription);
            myOperationDescription.Behaviors.Add(serializerBehavior);
        }
        serializerBehavior.DataContractResolver = new DeserializeAsBaseResolver();
    }

    public override bool TryResolveType(Type type, Type declaredType,
                                        DataContractResolver knownTypeResolver,
                                        out XmlDictionaryString typeName,
                                        out XmlDictionaryString typeNamespace) {

        bool ret = knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
        //return ret; // ret = false which causes an exception.
        return true;
    }

    public override Type ResolveName(string typeName, string typeNamespace,
                                    Type declaredType, DataContractResolver knownTypeResolver) {

        return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null) ?? declaredType;
    }

Host code (service or command line):

using (ServiceHost service_host = new ServiceHost(typeof(GatewayServer))) {

// Setup DataContractResolver for GatewayProcessing to GatewayData resolution:
DeserializeAsBaseResolver.Install(service_host);

// Open the host and start listening for incoming messages.
try { service_host.Open(); }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文