WCF: возврат производного объекта для договора с базовым объектом (DataContractResolver) - PullRequest
1 голос
/ 06 декабря 2011

У меня возникла проблема с производным / базовым контрактом WCF.У меня есть серверный интерфейс / контракт, который возвращает объект BaseThing:

[OperationContract]
BaseThing Get_base_thing();

Сервер, который реализует это, имеет DerivedThing (полученный из BaseThing) и хочет вернуть его как BaseThing. Как сообщить WCF, что я хочу только передать BaseThing часть DerivedThing?

Если в Get_base_thing я просто возвращаю ссылку на DerivedThing, тогда я получаюSerializationException серверная сторона.

Я думаю, что мне нужно определить DataContractResolver, и я посмотрел статью MSDN Использование Data Contract Resolver , но это не на 100% ясно (по крайней мере, мне).

Как мой DataContractResolver должен сказать WCF, чтобы он транспортировал только базовую часть производного объекта, который я передаю?

Есть ли способ сделать это проще, просто с помощью KnownTypeAttribué

Ответы [ 2 ]

3 голосов
/ 07 декабря 2011

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, чтобы в ваших операциях было очевидно, что существует расхождение между объектами, используемыми на сервере, и объектами, открытыми для внешнего мира.

1 голос
/ 07 декабря 2011

После публикации я также нашел этот SO идентичный вопрос Как сериализовать производный тип как base .Второй неприемлемый ответ от меня для меня - самый простой способ решить эту проблему.То есть:
Украсьте производный класс с помощью [DataContract(Name="BaseClass")]
. Обратите внимание, что это решение означает, что производная будет транспортироваться как база для всех случаев транспортировки этого объекта.Для меня это не было проблемой, если это так, тогда вам нужно идти по маршруту DataContractResolver.

Некоторые примечания по маршруту DataContractResolver:
1. Это позволяет вам передавать производное как производное по некоторым вызовам, нокак основание для другого - если вам нужно это сделать - если не использовать о Name = подход.
2. Я получаю исключение, используя DeserializeAsBaseResolver из статьи datacontractrsolver в том виде, в каком она есть, потому что knownTypeResolver возвращает false.Чтобы исправить это, я игнорирую возвращаемое значение этого вызова и всегда возвращаю true из TryResolveType.Кажется, это работает.
3. Сначала я подумал, что, поскольку мы сериализовали в качестве базы, мне не требовался [DataContract] для производного класса.Это было неправильно.Объект сериализуется как производный объект и десериализуется как базовый объект - поэтому вы должны декорировать производное с помощью [DataContract], но не отмечать какие-либо поля как [DataMembers], чтобы избежать их излишней сериализации.
4. Если выЕсли у вас есть хост командной строки и сервисный хост, то вам нужен код для вставки преобразователя контрактов в оба.Я нашел полезным поместить это в качестве статического в мой преобразователь.
5. Обратите внимание, что строка "Get_gateway_data" в вызове cd.Operations.Find("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(); }
...