Frode
Frode

Reputation: 356

.net client parse a soap fault as a protocol exception

Can anyone explain me why the soap response below is not deserialized into a FaultException in my C# client? I get a System.ServiceModel.ProtocolException with message "The remote server returned an unexpected response: (400) Bad Request", with a System.Net.WebException as a inner exception with message "The remote server returned an error: (400) Bad Request."

My client code uses several contracts defined svcutil.exe (Add service reference) I use a Custom binding for basic or digest authentication and soap1.2. In this request I use basic authenitcation.
Code for binding is below the response soap.

HTTP/1.1 400 Bad Request
Server: gSOAP/2.7
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 2087
Connection: close

<?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:tns1="http://www.onvif.org/ver10/topics" 
xmlns:tnsacti="http://www.acti.com/2009/event/topics"
xmlns:http="http://tempuri.org/http.xsd" 
xmlns:wsadis="http://schemas.xmlsoap.org/ws/2004/08/addressing" 
xmlns:wsbf="http://docs.oasis-open.org/wsrf/bf-2" 
xmlns:wstop="http://docs.oasis-open.org/wsn/t-1" 
xmlns:wsr="http://docs.oasis-open.org/wsrf/r-2" 
xmlns:tt="http://www.onvif.org/ver10/schema" 
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" 
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
xmlns:ns3="http://www.onvif.org/ver10/events/wsdl/PullPointSubscriptionBinding" 
xmlns:ns4="http://www.onvif.org/ver10/events/wsdl/EventBinding" 
xmlns:tev="http://www.onvif.org/ver10/events/wsdl" 
xmlns:ns5="http://www.onvif.org/ver10/events/wsdl/SubscriptionManagerBinding"
xmlns:ns6="http://www.onvif.org/ver10/events/wsdl/NotificationProducerBinding"
xmlns:ns7="http://www.onvif.org/ver10/events/wsdl/NotificationConsumerBinding"
xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2" 
xmlns:tds="http://www.onvif.org/ver10/device/wsdl" 
xmlns:tptz="http://www.onvif.org/ver20/ptz/wsdl"
xmlns:trt="http://www.onvif.org/ver10/media/wsdl"
xmlns:wsa="http://www.w3.org/2005/08/addressing"
xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery" 
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
xmlns:ter="http://www.onvif.org/ver10/error">
    <SOAP-ENV:Body>
        <SOAP-ENV:Fault SOAP-ENV:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
            <SOAP-ENV:Code>
                <SOAP-ENV:Value>SOAP-ENV:Sender</SOAP-ENV:Value>
            </SOAP-ENV:Code>
            <SOAP-ENV:Reason>
                <SOAP-ENV:Text xml:lang="en">Method 'GetImagingSettings' not implemented: method name or namespace not recognized
</SOAP-ENV:Text>
            </SOAP-ENV:Reason>
        </SOAP-ENV:Fault>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The C# code

private T GetClient(ISecurityTokenSelector securityToken, ref ChannelFactory<T> channelFactory) {
    if (ServiceAddress == null)
        throw new ArgumentNullException(string.Format("The service address to the {0} is null", typeof(T).Name));

    var endpointAddress = new EndpointAddress(ServiceAddress.ToString());
    //Gets custom binding
    var binding = BindingFactory.GetBinding(securityToken, false, false);
    channelFactory = new ChannelFactory<T>(binding, endpointAddress);

    if (securityToken is BasicUserNameSecurityTokenSelector) {
        channelFactory.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
        channelFactory.Credentials.UserName.UserName = UserName;
        channelFactory.Credentials.UserName.Password = Password;
    }
    if (securityToken is DigestSecurityTokenSelector) {
        // configure the username credentials on the channel factory 
        var credentials = new UsernameClientCredentials(new UsernameInfo(UserName, Password));

        // replace ClientCredentials with UsernameClientCredentials
        channelFactory.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
        channelFactory.Endpoint.Behaviors.Add(credentials);
    }
    return channelFactory.CreateChannel();
}

private Binding GetCustomBinding(ISecurityTokenSelector token, bool mtomEncoding, bool wsAddressing) {
    var binding = new CustomBinding(CreateBindingElements(mtomEncoding, wsAddressing, token));
    binding.CloseTimeout = TimeSpan.FromSeconds(30.0);
    binding.OpenTimeout = TimeSpan.FromSeconds(30.0);
    binding.SendTimeout = TimeSpan.FromMinutes(10.0);
    binding.ReceiveTimeout = TimeSpan.FromMinutes(3.0);
    return binding;
}

private static IEnumerable<BindingElement> CreateBindingElements(bool mtomEncoding, bool wsAddressing, ISecurityTokenSelector token) {
    if (token is DigestSecurityTokenSelector) {
        TransportSecurityBindingElement transportSecurity = new TransportSecurityBindingElement();
        transportSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new UsernameTokenParameters());

        // here you can require secure transport, in which case you'd probably replace HTTP with HTTPS as well
        transportSecurity.AllowInsecureTransport = true;
        transportSecurity.IncludeTimestamp = false;
        yield return transportSecurity;
    }
    var msgVer = wsAddressing ? MessageVersion.Soap12WSAddressing10 : MessageVersion.Soap12;
    if (mtomEncoding) {
        var encoding = new MtomMessageEncodingBindingElement(msgVer, Encoding.UTF8);
        encoding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
        encoding.MaxBufferSize = Int32.MaxValue;
        yield return encoding;
    } else {
        var encoding = new TextMessageEncodingBindingElement(msgVer, Encoding.UTF8);
        encoding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue; //100 * 1024 * 1024
        yield return encoding;
    }

    HttpTransportBindingElement transport = CreateTransportBindingElement(token);
    transport.MaxReceivedMessageSize = Int32.MaxValue; //100L * 1024L * 1024L
    transport.KeepAliveEnabled = false;
    transport.MaxBufferSize = Int32.MaxValue;
    //transport.ProxyAddress = null;
    //transport.BypassProxyOnLocal = true;
    //transport.UseDefaultWebProxy = false;
    transport.TransferMode = TransferMode.Buffered;
    if (token is BasicUserNameSecurityTokenSelector)
        transport.AuthenticationScheme = AuthenticationSchemes.Basic;

    yield return transport;
}

private static HttpTransportBindingElement CreateTransportBindingElement(ISecurityTokenSelector token) {
    if (token != null && token.UseTls) {
        var transport = new HttpsTransportBindingElement();
        transport.RequireClientCertificate = false;
        return transport;
    } else {
        return new HttpTransportBindingElement();
    }
}

Upvotes: 3

Views: 2236

Answers (1)

Deanna
Deanna

Reputation: 24293

The receiver is picking up on the 400 Bad Request before it gets to the content part of the reply.

The call stack shows this error is being raised at the HttpWebRequest level and passed up.

System.ServiceModel.ProtocolException: The remote server returned an unexpected response: (400) Bad Request. ---> System.Net.WebException: The remote server returned an error: (400) Bad Request. 
   at System.Net.HttpWebRequest.GetResponse() 
   at System.ServiceModel.Channels.HttpChannelFactory`1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout) 
   --- End of inner exception stack trace --- 

To get a FaultException, it needs to have received a 200 (or similar) status code.

Upvotes: 2

Related Questions