3
votes

ASP.Net 2.0 Web Services automatically create both SOAP 1.1 and SOAP 1.2 bindings. Our web service, however, has SOAP extensions and custom exception handling that make the assumption that only the SOAP 1.1 binding is used (for example, the SOAP extension uses the HTTP SOAPAction header to control behavior).

I am looking to correct the code that makes these assumptions and make it work with either SOAP 1.1 or SOAP 1.2 properly. I am running into a bit of a problem in the generation of elements for our SOAP faults.

Consider the following web method implementation:

[WebMethod]
public void
ThrowsSoapException()
{
    throw new SoapException("This is a SOAP exception.", SoapException.ServerFaultCode);
}

Invoking this via SOAP 1.1 yields the following result:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <soap:Fault>
         <faultcode>soap:Server</faultcode>
         <faultstring>This is a SOAP exception.</faultstring>
         <detail/>
      </soap:Fault>
   </soap:Body>
</soap:Envelope>

Invoking via SOAP 1.2 yields the following result:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <soap:Fault>
         <soap:Code>
            <soap:Value>soap:Receiver</soap:Value>
         </soap:Code>
         <soap:Reason>
            <soap:Text xml:lang="en">This is a SOAP exception.</soap:Text>
         </soap:Reason>
         <soap:Detail/>
      </soap:Fault>
   </soap:Body>
</soap:Envelope>

In both these cases there is an empty detail element as a child of the <soap:Fault> element, but it has a different qualified name, either <detail> or <soap:Detail>.

Now consider the following code that tries to create a SOAPException with a detail element.

[WebMethod]
public void
ThrowsSoapExceptionWithDetail()
{
    XmlDocument doc = new XmlDocument();
    XmlNode detail =
        doc.CreateNode(XmlNodeType.Element, SoapException.DetailElementName.Name, SoapException.DetailElementName.Namespace);
    XmlNode custom =
        doc.CreateNode(XmlNodeType.Element, "custom", "http://example.com/xml/namespace/blah");
    custom.InnerXml = "Detail value";
    detail.AppendChild(custom);
    throw new SoapException("This is a SOAP exception with a detail element.", SoapException.ServerFaultCode, Context.Request.Url.AbsoluteUri, detail);
}

The SOAP 1.1 response is:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <soap:Fault>
         <faultcode>soap:Server</faultcode>
         <faultstring>This is a SOAP exception with a detail element.</faultstring>
         <faultactor>http://localhost/simplewebservice/service1.asmx</faultactor>
         <detail>
            <custom xmlns="http://example.com/xml/namespace/blah">Detail value</custom>
         </detail>
      </soap:Fault>
   </soap:Body>
</soap:Envelope>

and the SOAP 1.2 response is:

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <soap:Fault>
         <soap:Code>
            <soap:Value>soap:Receiver</soap:Value>
         </soap:Code>
         <soap:Reason>
            <soap:Text xml:lang="en">This is a SOAP exception with a detail element.</soap:Text>
         </soap:Reason>
         <soap:Node>http://localhost/simplewebservice/service1.asmx</soap:Node>
         <detail>
            <custom xmlns="http://example.com/xml/namespace/blah">Detail value</custom>
         </detail>
      </soap:Fault>
   </soap:Body>
</soap:Envelope>

The SOAP 1.2 response now has the wrong qualified name for the detail element. It should be <soap:Detail>, but instead is merely <detail>, same as the SOAP 1.1 response.

It seems that the ASP.Net 2.0 framework has done quite a bit to transform a SOAPException into the appropriate form for the SOAP version, but neglected to properly handle the detail element. Additionally, they don't seem to have exposed the correct SOAP 1.2 qualified name for the detail element as was done with the SoapException.DetailElementName property.

So, what is the correct way to add a detail element to a SOAP fault response that works for both SOAP 1.1 and SOAP 1.2? Do I need to detect the SOAP version myself and hard-code the SOAP 1.2 qualified name for the detail element?

3

3 Answers

1
votes

No idea about the coding part of your question, sorry, but a workaround wound be to disable the soap1.2 in your web.config, if you haven't already look that way.

....

0
votes

The following is not meant to be snide:

The fix to this problem is to use WCF.

This looks like a bug in ASMX web services handling of the detail element in a SOAP 1.2 fault. It obviously makes no sense for the qualified name of the element to change based on whether or not the element has a value.

You could report this error on Connect, but since only critical ASMX bugs are being fixed, that's unlikely to help you.

I doubt that WCF has this problem, since it fully supports SOAP Faults.

0
votes

This is a late answer, but since I've ran into the issue myself I thought I might as well put my solution here, which is essentially to pass along the version of the SOAP protocol to the method that builds the detail section of the fault message.

The WebService class from which an ASMX web service class is derived exposes the SoapVersion property, which contains the version of the SOAP protocol used to make the current SOAP request.

A detail section appropriate for the current SOAP version can then be built easily.

using System.Xml.Linq;

XNamespace detailElementNamespace;
string detailElementName;
if (soapVersion == SoapProtocolVersion.Soap12)
{
    detailElementNamespace = "http://www.w3.org/2003/05/soap-envelope";
    detailElementName = "Detail";
}
else
{
    detailElementNamespace = "";
    detailElementName = "detail";
}

var document = new XmlDocument();
document.LoadXml(
    new XElement(detailElementNamespace + detailElementName,
        new XElement("MySection",
            new XElement("MyCode", "..."),
            new XElement("MyDescription", "...")
            )).ToString());

throw new SoapException(
    "MESSAGE",
    SoapException.ClientFaultCode,
    "ACTOR",
    document.DocumentElement);