简体   繁体   中英

System.ServiceModel.CommunicationException when parsing WCF ProtocolException

I am trying to use the recommended code from this page http://blogs.msdn.com/b/nathana/archive/2011/03/31/deciphering-a-soap-fault-with-a-400-status-code.aspx as follows:

static FaultException ParseProtocolExecption(ProtocolException ex)
{
 try
 {
     System.IO.Stream stream = (ex.InnerException as WebException).Response.GetResponseStream();
     System.Xml.XmlReader xmr = System.Xml.XmlReader.Create(stream);
     Message message = Message.CreateMessage(xmr, (int)stream.Length, MessageVersion.Soap12);
     MessageFault mf = MessageFault.CreateFault(message, (int)stream.Length);
     FaultException fe = new FaultException(mf);
     message.Close();
     return fe;
 }
 catch (Exception)
 {
     return new FaultException(ex.Message);
 }
}

I am using VS 2012 with .NET 4.5 using WCF. When the app gets a 400 Bad Request and it passes the ProtocolException to ParseProtocolException, it throws an exception on this line:

Message message = Message.CreateMessage(xmr, (int)stream.Length, MessageVersion.Soap12);

with the System.ServiceModel.CommunicationException: "The size necessary to buffer the XML content exceeded the buffer quota."

The stream.Length = 2,704 bytes, which is not very big. I tried the solution suggested on this site http://blogs.msdn.com/b/drnick/archive/2007/08/07/increasing-the-maximum-fault-size.aspx . However, even with the MaxFaultSize = 1 Mb, it gets the same errror.

Instead of this line:

System.Xml.XmlReader xmr = System.Xml.XmlReader.Create(stream);

I've tried this:

xmr = XmlDictionaryReader.CreateTextReader(stream, XmlDictionaryReaderQuotas.Max);

which sets all the quotas to their maximum value (Int32.MaxValue); but, I still get the same error on the CreateMessage call.

A sample response stream from the System.Net.WebException is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa5="http://www.w3.org/2005/08/addressing" xmlns:c14n="http://www.w3.org/2001/10/xml-exc-c14n#" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:xmime5="http://www.w3.org/2005/05/xmlmime" xmlns:xop="http://www.w3.org/2004/08/xop/include" xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl" xmlns:tds2="http://www.onvif.org/ver10/schema" xmlns:tds3="http://docs.oasis-open.org/wsn/b-2" xmlns:tds4="http://docs.oasis-open.org/wsrf/bf-2" xmlns:tds5="http://docs.oasis-open.org/wsn/t-1" xmlns:timg="http://www.onvif.org/ver20/imaging/wsdl" xmlns:trt="http://www.onvif.org/ver10/media/wsdl" xmlns:tan="http://www.onvif.org/ver20/analytics/wsdl" xmlns:tds6="http://www.canon.com/ns/networkcamera/onvif/va/schema" xmlns:tmd="http://www.onvif.org/ver10/deviceIO/wsdl" xmlns:tev="http://www.onvif.org/ver10/events/wsdl" xmlns:tds9="http://docs.oasis-open.org/wsrf/r-2" xmlns:tds="http://www.onvif.org/ver10/device/wsdl" xmlns:ter="http://www.onvif.org/ver10/error">
  <SOAP-ENV:Header></SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <SOAP-ENV:Fault>
      <SOAP-ENV:Code>
        <SOAP-ENV:Value>SOAP-ENV:Sender</SOAP-ENV:Value>
        <SOAP-ENV:Subcode>
          <SOAP-ENV:Value>ter:NotAuthorized</SOAP-ENV:Value>
        </SOAP-ENV:Subcode>
      </SOAP-ENV:Code>
      <SOAP-ENV:Reason>
        <SOAP-ENV:Text xml:lang="en">Sender Not Authorized</SOAP-ENV:Text>
      </SOAP-ENV:Reason>
      <SOAP-ENV:Node>http://www.w3.org/2003/05/soap-envelope/node/ultimateReceiver</SOAP-ENV:Node>
      <SOAP-ENV:Role>http://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver</SOAP-ENV:Role>
      <SOAP-ENV:Detail>The action requested requires authorization and the sender is not authorized</SOAP-ENV:Detail>
    </SOAP-ENV:Fault>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Using an async web crawler that I wrote in F# Interactive, I found that some of the namespace url's were not resolvable. I corrected the erroneous ones and then ran the crawler again to sum the lengths of the namespace pages. The total is 715,965 bytes, which is much less than the Int32.MaxValue of all the quotas in XmlDictionaryReaderQuotas. Perhaps the XmlDictionaryReader has a bug, or the error it returns is not the real problem?

I finally got the Message creation to work by removing the namespace definitions that were not actually used in the SOAP-ENV:Body (ie, keeping only the xmlns:ter used in the Subcode element). But, of course, this doesn't really solve the problem, because the service is generating the SOAP fault; and I cannot change the service implementation (it's a 3rd party device - an Onvif camera).

Morevover, I can't make the quotas any greater; so, how else to handle this exception?

The solution is to trap the CommunicationException and then do an alternate parse of the XML in the stream that doesn't require namespace resolution:

    public static FaultException ParseProtocolException(System.ServiceModel.ProtocolException ex) {
        var stream = (ex.InnerException as System.Net.WebException).Response.GetResponseStream();
        try {
            var xmr = XmlReader.Create(stream);
            var message = Message.CreateMessage(xmr, (int)stream.Length, MessageVersion.Soap12);
            var mf = MessageFault.CreateFault(message, (int)stream.Length);
            message.Close();
            return new FaultException(mf);
        } catch (CommunicationException) {    // If CreateMessage has a problem parsing the XML,
            // then this error will be thrown.  Most likely, there is an unresolvable namespace reference.
            // Do an alternate parse
            stream.Seek(0, System.IO.SeekOrigin.Begin);
            var soapFault = GetSoapFault(stream);
            return new FaultException(soapFault.Reason);
        }
    }

The catch resets the stream to the beginning and then uses the following to get the specifics of the SOAP Fault using an XmlReader:

    private struct SoapFault {
        public string Subcode;
        public string Reason;
        public string Detail;
    }
    private static string GetTextChild(XmlReader xmr, string childName) {
      return xmr.ReadToDescendant(childName) ? 
          xmr.ReadString() : System.String.Empty;
    }
    private static SoapFault GetSoapFault(System.IO.Stream s) {
        var xr = XmlReader.Create(s);
        var fault = new SoapFault();
        if (xr.ReadToFollowing("SOAP-ENV:Subcode")) {
            fault.Subcode = GetTextChild(xr, "SOAP-ENV:Value");
            if (xr.ReadToFollowing("SOAP-ENV:Reason")) {
                fault.Reason = GetTextChild(xr, "SOAP-ENV:Text");
                if (xr.ReadToFollowing("SOAP-ENV:Detail"))
                    fault.Detail = GetTextChild(xr, "SOAP-ENV:Text");
            }
        }
        return fault;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM