Why does the XmlRoot attribute gets ignored in WCF and how to overcome this

后端 未结 2 1915
悲哀的现实
悲哀的现实 2020-12-16 04:07

We\'ve observed that when we expose a WCF service which uses classes decorated with various xml serialisation attributes, despite the fact that we use the XmlSerializerForma

相关标签:
2条回答
  • I assume you're using SOAP as the message format. In this case, the object you're serializing is not the root of the XML, the soap envelope is. So it makes sense that the XmlRoot would be ignored. By default WCF will create a message contract for you and name the response and it has the namespace of the service. What you can do is create your own message contract to have full control over SOAP.

    Create the following two classes:

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

    Then change the signature of your service operation to the following.

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

    Now if you example the SOAP messages you should see the following:

    Request

    <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>
    

    Response

    <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>
    
    0 讨论(0)
  • 2020-12-16 04:55

    I don't know why WCF ignores XmlRoot, so I can't answer that part of your question. But I do have a couple ways to solve the problem.

    1. start with WSDL first.
      If you have a particular set of XML namespaces you would like to apply to the messages that get sent and receieved, use WSDL and XML Schema to explicitly specify them.

      Then, generate the Server-side stub code, or the client-side proxy code, directly from that WSDL via the svcutil.exe tool.

    2. use a custom ServiceHost
      The other option open to you, described at this link, is to use a custom ServiceHost that overrides WCF's decision to disregard the XmlRoot or XmlType attributes on message types.


    If you choose to go for the WSDL-First approach, the WSDL should look like this:

    <?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>
    

    This WSDL is very simple - it defines a single operation, with a single request message and a single response message.

    Notice there are three xml namespaces:

    • urn:The-Service-namespace
      used for the element that wraps the request and response - the first element inside the <SOAP:body>
    • urn:The-Request-namespace
      used for the element wrapped inside that request wrapper, which gets deserialized into an instance of Type1.
    • urn:The-Response-namespace
      used for the element wrapped inside that response wrapper, which gets deserialized into an instance of Type2.

    If your web services interface is more complicated, has more operations and consequently more request and response message types, you can add more namespaces, if you like, for all those additional types.

    0 讨论(0)
提交回复
热议问题