Почему атрибут XmlRoot игнорируется в WCF и как это преодолеть? - PullRequest
8 голосов
/ 06 января 2010

Мы наблюдали, что когда мы предоставляем сервис WCF, который использует классы, украшенные различными атрибутами сериализации xml, несмотря на тот факт, что мы используем атрибут XmlSerializerFormat в интерфейсе, любой атрибут XmlRoot любого параметра операции полностью игнорируется. Пространство имен параметров всегда соответствует пространству службы, а не тому, что мы указываем.

Это вызывает у нас проблемы, поскольку он не имеет обратной совместимости с ASMX, а также потому, что мы используем BizTalk, и нам необходимо более жестко контролировать форму обмениваемого XML.

Несколько вопросов тогда -

  1. Кто-нибудь знает, что является обоснованием за этим решением?
  2. Кто-нибудь знает как это происходит? Я был под впечатления, что WCF, с Атрибут XmlSerializerFormat, использует XmlSerialiser для сериализации типы, которые предлагают XmlRoot следует учитывать, как давай это не тот случай? (это только в связи с тем, что, принимая конверт SOAP во внимание, параметр не является корневым?)
  3. Большинство главное - кто-нибудь знает, если есть способ «заставить проблему» - то есть получить параметры для пространство имен по нашему выбору?

Я видел эту запись, но я не думаю, что она имеет отношение к моему вопросу -

По запросу Вагнера Сильвейры - контракты, которые я использовал для проверки этого, -

[ServiceContract(Namespace = "http://servicecontract"),
 XmlSerializerFormat(Style = OperationFormatStyle.Document)]
public interface ITestService
{
    [OperationContract]
    MyOtherType MyTestMethod(MyType obj);
}

// Composite class for DCS and XMLS
[Serializable, XmlType, XmlRoot(Namespace = "http://datacontract")] 
public class MyType
{
    [XmlAttribute]
    public string StringValue { get; set; }
}

// Composite class for DCS and XMLS
[Serializable, XmlType, XmlRoot(Namespace = "http://datacontract")]
public class MyOtherType
{
    [XmlAttribute]
    public string OtherStringValue { get; set; }
}

Ответы [ 2 ]

3 голосов
/ 16 января 2010

Я предполагаю, что вы используете SOAP в качестве формата сообщения. В этом случае сериализуемый объект не является корнем XML, как мыльный конверт. Поэтому имеет смысл, что XmlRoot будет игнорироваться. По умолчанию WCF создаст для вас контракт на сообщение и назовет ответ, и он имеет пространство имен службы. Что вы можете сделать, это создать свой собственный договор на сообщение , чтобы получить полный контроль над SOAP.

Создайте следующие два класса:

[MessageContract]
public class MyTestMethodRequest
{
    [MessageBodyMember( Namespace = "http://datacontract" )]
    public MyType MyType;
}

[MessageContract]
public class MyTestMethodResponse
{
    [MessageBodyMember( Namespace = "http://datacontract" )]
    public MyOtherType MyOtherType;
}

Затем измените подпись вашей сервисной операции на следующую.

[OperationContract]
public MyTestMethodResponse MyTestMethod( MyTestMethodRequest request )
{
    return new MyTestMethodResponse {
        MyOtherType = new MyOtherType {
            OtherStringValue = "bar"
        }
    };
}

Теперь, если вы пример SOAP-сообщений, вы должны увидеть следующее:

Запрос

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Action xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none"
            s:mustUnderstand="1">http://servicecontract/TestService/MyTestMethod</Action>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <MyTestMethodRequest xmlns="http://servicecontract">
      <MyType StringValue="foo" xmlns="http://datacontract" />
    </MyTestMethodRequest>
  </s:Body>
</s:Envelope>

Ответ

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header />
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <MyTestMethodResponse xmlns="http://servicecontract">
      <MyOtherType OtherStringValue="bar" xmlns="http://datacontract" />
    </MyTestMethodResponse>
  </s:Body>
</s:Envelope>
1 голос
/ 15 января 2010

Я не знаю, почему WCF игнорирует XmlRoot, поэтому я не могу ответить на эту часть вашего вопроса. Но у меня есть пара способов решить эту проблему.

  1. сначала с WSDL.
    Если у вас есть определенный набор пространств имен XML, которые вы хотите применить к сообщениям, которые отправляются и принимаются, используйте WSDL и схему XML, чтобы явно указать их.

    Затем сгенерируйте код-заглушку на стороне сервера или прокси-код на стороне клиента непосредственно из этого WSDL с помощью инструмента svcutil.exe .

  2. использовать пользовательский ServiceHost
    Другой доступный вариант, описанный в по этой ссылке , заключается в использовании пользовательского ServiceHost, который отменяет решение WCF игнорировать атрибуты XmlRoot или XmlType для типов сообщений.


Если вы решите использовать WSDL-First подход, WSDL должен выглядеть следующим образом:

<?xml version="1.0" encoding="utf-8" ?>

<definitions
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    targetNamespace="urn:The-Service-namespace"
    xmlns:tns="urn:The-Service-namespace"
    xmlns:s="http://www.w3.org/2001/XMLSchema"
    xmlns:n0="urn:The-Request-namespace"
    xmlns:n1="urn:The-Response-namespace"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    elementFormDefault= "unqualified"
  >

    <types>
      <s:schema targetNamespace="urn:The-Request-namespace" >
        <s:complexType name="Type1">
          <s:sequence>
            <s:element name="x" minOccurs="1" maxOccurs="1" type="s:string"/>
          </s:sequence>
        </s:complexType>
        <s:element name="Type1" type="n0:Type1" />
      </s:schema>


      <s:schema targetNamespace="urn:The-Response-namespace" >
        <s:complexType name="Type2">
          <s:sequence>
            <s:element name="x" minOccurs="1" maxOccurs="1" nillable="false" type="s:string"/>
            <s:element name="y" minOccurs="1" maxOccurs="1" nillable="false" type="s:int"/>
            <s:element name="z" minOccurs="1" maxOccurs="1" nillable="false" type="s:boolean" />
          </s:sequence>
        </s:complexType>
        <s:element name="Type2" type="n1:Type2" />
      </s:schema>

    </types>



<message name="RequestMessage">
   <part name="inPart1" element="n0:Type1" />
</message>
<message name="ResponseMessage">
   <part name="outPart1" element="n1:Type2" />
</message>



<portType name="PortTypeName">
  <operation name="Method1">
      <input message="tns:RequestMessage" />
      <output message="tns:ResponseMessage" />
   </operation>
</portType>



<binding name="InterfaceName" type="tns:PortTypeName">
    <soap:binding
       transport="http://schemas.xmlsoap.org/soap/http"
       style="rpc" />

    <operation name="Method1">
        <soap:operation soapAction="" style="document" />
        <input>  <soap:body use="literal" /> </input>
        <output> <soap:body use="literal" /> </output>
    </operation>
</binding>

</definitions>

Этот WSDL очень прост - он определяет одну операцию с одним сообщением запроса и одним ответным сообщением.

Обратите внимание, что есть три пространства имен xml:

  • урна: The-Service-имен
    используется для элемента, который упаковывает запрос и ответ - первый элемент внутри
  • урна: The-Request-имен
    используется для элемента, обернутого в эту оболочку запроса, которая десериализуется в экземпляр Type1.
  • урна: The-Response-имен
    используется для элемента, обернутого в эту оболочку ответа, которая десериализуется в экземпляр Type2.

Если ваш интерфейс веб-сервисов более сложный, имеет больше операций и, следовательно, больше типов сообщений с запросами и ответами, вы можете добавить больше пространств имен, если хотите, для всех этих дополнительных типов.

...