Reputation: 11658
I'm very new to WCF and the related technologies. (Using WCF 4.0, by the way.)
Here are some snippets from the WSDL file for a web service that I need to interface with.
<wsdl:binding name="MPGWCSTAOperations_v1_1SoapBinding" type="impl:MPGWCSTAOperations">
<wsdlsoap:binding transport="http://schemas.xmlsoap.org/soap/http" />
...
<wsdl:operation name="MonitorStartLine">
<wsdlsoap:operation soapAction="urn:v1_1.csta.ws.mpgw.gintel.com/MonitorStart" />
<wsdl:input name="MonitorStartLineRequest">
<wsdlsoap:body use="literal" />
</wsdl:input>
<wsdl:output name="MonitorStartLineResponse">
<wsdlsoap:body use="literal" />
</wsdl:output>
</wsdl:operation>
...
</wsdl:binding>
<wsdl:portType name="MPGWCSTAOperations">
...
<wsdl:operation name="MonitorStartLine" parameterOrder="monitorStartLine">
<wsdl:input name="MonitorStartLineRequest" message="impl:MonitorStartLineRequest" />
<wsdl:output name="MonitorStartLineResponse" message="impl:MonitorStartLineResponse" />
</wsdl:operation>
....
</wsdl:portType>
<wsdl:message name="MonitorStartLineResponse" />
My understanding is that the MonitorStartLine operation is defined to return a response message MonitorStartLineResponse, which is defined to be an empty message.
I've used Visual Studio's Project - Add Service Reference facility to generate C# proxy code for this.
Then I do something like this:
MPGWCSTAOperationsClient cstaOperationsClient = new MPGWCSTAOperationsClient();
MonitorStartLine monitorStartLine = new MonitorStartLine();
monitorStartLine.pnis = new string[] {"0000032"};
cstaOperationsClient.MonitorStartLine(monitorStartLine);
This results in the following exception:
System.ServiceModel.CommunicationException was unhandled
HResult=-2146233087
Message=Error in deserializing body of reply message for operation 'MonitorStartLine'.
End element 'Body' from namespace 'http://schemas.xmlsoap.org/soap/envelope/' expected.
Found element 'monitorStartLineResponse' from namespace 'urn:v1_1.csta.ws.mpgw.gintel.com'. Line 1, position 296.
Source=mscorlib
Using Fiddler I'm seeing the response as follows:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Powered-By: Servlet 2.5; JBoss-5.0/JBossWeb-2.1
Content-Type: text/xml;charset=utf-8
Transfer-Encoding: chunked
Date: Wed, 16 Oct 2013 22:01:44 GMT
149
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<monitorStartLineResponse xmlns="urn:v1_1.csta.ws.mpgw.gintel.com"/>
</soapenv:Body>
</soapenv:Envelope>
Which looks to me like it conforms to the WSDL.
I'm thinking that I can probably just ignore the error (the server will never be the wiser), but I'd prefer to fix the problem if possible.
Upvotes: 2
Views: 5278
Reputation: 11658
Here's the workaround that I implemented. (It is debatable as to whether it's better to implement a workaround or to just ignore the exception - I prefer to do it this way.)
What I'm doing is modifying the response message before WCF invokes SOAP processing on it. I'm simply removing the XML element that WCF/SOAP don't think should be there.
/// <summary>
/// This class is used to provide a workaround for a problem due to the (censored) server sending
/// responses encoded in SOAP which do not, at least according to WCF standards, conform to the
/// WSDL specifications published for the server.
/// </summary>
public class MessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message requestMessage, IClientChannel clientChannel)
{
return null; // Method not needed
}
public void AfterReceiveReply(ref Message replyMessage, object correlationState)
{
if (replyMessage.IsFault)
return; // Avoid distortion of SOAP fault messages
string messageBody;
using (MemoryStream memoryStream = new MemoryStream())
{
using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream))
{
replyMessage.WriteMessage(xmlWriter);
xmlWriter.Flush();
messageBody = Encoding.UTF8.GetString(memoryStream.ToArray());
}
}
messageBody = messageBody.Replace(
"<monitorStartLineResponse xmlns=\"urn:v1_1.csta.ws.mpgw.gintel.com\" />", "");
using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)))
{
using (XmlDictionaryReader xmlDictionaryReader =
XmlDictionaryReader.CreateTextReader(memoryStream, new XmlDictionaryReaderQuotas()))
{
Message newMessage =
Message.CreateMessage(xmlDictionaryReader, int.MaxValue, replyMessage.Version);
newMessage.Properties.CopyProperties(replyMessage.Properties);
replyMessage = newMessage;
}
}
}
}
/// <summary>
/// Class needed to inject the above MessageInspector class into the WCF processing of messages.
/// </summary>
public class InjectInspectorBehavior : IEndpointBehavior
{
public void Validate(ServiceEndpoint serviceEndpoint)
{
// Method not needed
}
public void AddBindingParameters(ServiceEndpoint serviceEndpoint,
BindingParameterCollection bindingParameters)
{
// Method not needed
}
public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint,
EndpointDispatcher endpointDispatcher)
{
// Method not needed
}
public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new MessageInspector());
}
}
This is put into operation by adding the second line shown here:
_cstaOperationsClient = new MPGWCSTAOperationsClient();
_cstaOperationsClient.Endpoint.Behaviors.Add(new InjectInspectorBehavior());
The above code is largely based on code found in these two locations:
http://blogs.msdn.com/b/kaevans/archive/2008/01/08/modify-message-content-with-wcf.aspx
Replacing content of WCF Message
Upvotes: 4
Reputation: 161831
The message conforms to the WSDL, but in a place where the WSDL specification is ambiguous. .NET is expecting nothing within the Body
element, because no message parts were defined. The sender is sending an empty monitorStartLineResponse
element instead, as though a single message part were specified, with an element named monitorStartLineResponse
.
Because of areas where the WSDL specification is ambiguous, the Web Services Interoperability Organization was formed. The WS-I Basic Profile 1.1 specification was developed to specify a subset of WSDL which is guaranteed to be interoperable across platforms.
This WSDL does not conform to WS-I BP 1.1.
In fact, from reading the WSDL 1.1 spec (section 3.4, soap:operation), I see that this will be taken to be a "document-literal binding" because there are no "style" attributes saying otherwise.
In section 4.4.1, Bindings and Parts, R2213, the WS-I BP 1.1 spec says:
R2213 In a doc-literal description where the value of the parts attribute of
soapbind:body
is an empty string, the corresponding ENVELOPE MUST have no element content in thesoap:Body
element.
That is what .NET is expecting, but it's not what .NET is receiving.
Upvotes: 4