Как принять коллекцию базового типа в WCF

Настройка

У меня есть служба WCF, которая предоставляет базовый тип (например, животное), а также несколько производных типов (например, лев, тигр и медведь). Другой тип (например, Zoo) включает свойство, которое является коллекцией базового типа. Базовый тип является конкретным, не абстрактным, поэтому для коллекции вполне допустимо содержать экземпляры базового типа и/или производных типов (в любой комбинации). Например:

[DataContract, KnownType(typeof(Lion)),
KnownType(typeof(Tiger)), KnownType(typeof(Bear))]
public class Animal
{
    [DataMember]
    public string Species { get; set; }
}

[DataContract]
public class Lion : Animal { }


[DataContract]
public class Tiger : Animal { }


[DataContract]
public class Bear : Animal { }

[DataContract]
public class Zoo 
{
    [DataMember]
    public List<Animal> Animals { get; set; }
}

Одна из моих сервисных операций принимает этот тип в качестве параметра, например:

[ServiceContract]
public interface IZooService
{
    [OperationContract]
    void SetZoo(Zoo zoo);
}

Все это хорошо, и сгенерированный WSDL выглядит для меня совершенно нормально. Он содержит все типы и правильно указывает, что производные типы наследуются от базового типа. Итак, я должен иметь возможность вызывать свою службу, используя сообщение SOAP, например следующее:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:z="http://zoo.org">
   <soapenv:Header/>
   <soapenv:Body>
      <z:SetZoo>
         <z:Zoo>
            <z:Animals>
               <z:Animal>
                  <z:Species>Crocodile</z:Species>
               </z:Animal>
               <z:Tiger>
                  <z:Species>Bengal</z:Species>
               </z:Tiger>
               <z:Bear>
                  <z:Species>Grizzly</z:Species>
               </z:Bear>
            </z:Animals>
         </z:Zoo>
      </z:SetZoo>
   </soapenv:Body>
</soapenv:Envelope>

В приведенном выше сообщении SOAP коллекция Animals содержит один экземпляр базового типа Animal, экземпляр производного типа Tiger и экземпляр производного типа Bear. WCF должен иметь возможность успешно десериализовать это сообщение.

Проблема

WCF не генерирует никаких исключений, когда получает указанное выше сообщение. Вместо этого он просто полностью игнорирует производные типы (Tiger и Bear), и к тому времени, когда десериализованное сообщение передается моему коду, коллекция Animals содержит только запись Crocodile, поскольку она относится к базовому типу.

Итак, у меня есть два вопроса... Во-первых, почему WCF не десериализует экземпляры производных типов в коллекции. И, во-вторых, поскольку WCF явно что-то не нравится в этом SOAP-сообщении, почему он не генерирует исключение? Такого рода тихий провал очень беспокоит.


person James Messinger    schedule 18.08.2009    source источник


Ответы (1)


Хорошо... Я понял проблему. Оказывается, синтаксис SOAP для этого сценария требует небольшой дополнительной работы, чтобы получить правильный результат. Поскольку коллекция Animals определена как массив типов Animal, все дочерние элементы в сообщении SOAP должны быть элементами, даже если они фактически являются экземплярами производного типа. Фактический тип экземпляра определяется атрибутом "type", который является частью пространства имен экземпляра XMLSchema. Итак, мое SOAP-сообщение должно было выглядеть так:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://zoo.org">
<soapenv:Header/>
<soapenv:Body>
  <z:SetZoo>
     <z:Zoo>
        <z:Animals>
           <z:Animal>
              <z:Species>Crocodile</z:Species>
           </z:Animal>
           <z:Animal i:type="z:Tiger">
              <z:Species>Bengal</z:Species>
           </z:Animal>
           <z:Animal i:type="z:Bear">
              <z:Species>Grizzly</z:Species>
           </z:Animal>
        </z:Animals>
     </z:Zoo>
  </z:SetZoo>
</soapenv:Body>
</soapenv:Envelope>

Конечно, это по-прежнему не решает другую мою проблему, а именно то, что десериализатор WCF должен генерировать исключение, если он не понимает сообщение SOAP. Он не должен просто молча игнорировать части сообщения!

person James Messinger    schedule 18.08.2009
comment
Отлично, спасибо! Я чуть не взорвался, пытаясь создать правильное сообщение с помощью пользовательского интерфейса Soap... - person juarola; 04.03.2012
comment
Я думаю, что десериализатор WCF, не игнорирующий части сообщения, вызывает проблемы. Это заставит вас всегда держать клиент и сервер в тесном контакте, что не обязательно является желательной чертой. - person Jeff; 11.12.2012