3

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?

GBegen
  • 6,107
  • 3
  • 31
  • 52

3 Answers3

1

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.

....
  <webServices>
    <protocols>
      <remove name="HttpSoap12"/>
    </protocols>
  </webServices>
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Bigmac
  • 51
  • 3
  • 6
0

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.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • Unfortunately, moving off of .Net 2.0 just isn't possible right now. I'm also pretty certain that WCF will come with its own new set of problems, similar to the ones we have already solved with custom extensions, etc., in the .asmx world. I would certainly like to have the luxury of exploring this in the long term, though. – GBegen Jul 01 '10 at 21:12
  • @GBegen: Microsoft would have been extremely foolish to not learn from the mistakes of ASMX when creating WCF. In particular, the extensibility model is _far_ cleaner and much more comprehensive. Amont other things, you can take over error processing for the entire service, and you could override the serialization of faults if needed. Except that it would probably not be needed, since WCF supports faults natively - a WCF client receiving a fault response with a fault named `SomeFault` receives a `FaultException` exception. – John Saunders Jul 02 '10 at 00:09
0

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);
Sebacote
  • 690
  • 6
  • 17