Metadata de-serialization fails with web reference
Statement
==================
I have a console application, which consumes a java web service. The web service metadata is downloaded by Add Web Reference option.
1. Console application is able to form request and send to java web service.
2. Java web service is able to receive and process the request.
3. Java web service is also able to send the response payload over wire.
4. However, client application is not able to read the response (.net object appears to have null value even though payload indicates it has necessary data).
Hence, challenge is with client side de-serialization.
Troubleshooting
===================
We need to track the request/ response payload to troubleshoot this type of issues. For this, we can utilize Microsoft network monitor tool or Fiddler tool and read through http request and response.
Client
static void Main(string[] args)
{
MyDevices proxy = new MyDevices();
string desc = null;
myDevicesResponseListResponse[] respArray = null;
try
{
var res = proxy.getDevices(new string[] { "0123456789" }, out desc, out respArray); //but res is null, though payload response is captured in traffic
Console.WriteLine(res);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.ReadLine();
}
Service
https://www.myservice1.com/great/MyDevices?wsdl
Request
<soap:Envelope xmlns:soap="<www.w3.org/2003/05/soap-envelope>" xmlns:xsi="<www.w3.org/2001/XMLSchema-instance>" xmlns:xsd="<www.w3.org/2001/XMLSchema>">
<soap:Body>
<getDevices xmlns="<www.abc.com/services>">
<imei>0123456789</imei>
</getDevices>
</soap:Body>
</soap:Envelope>
Response: Actual – validation failed
<soapenv:Envelope xmlns:soapenv="<www.w3.org/2003/05/soap-envelope>">
<soapenv:Body>
<getDevicesResponse xmlns="<abcservice.directed.com>">
<returnCode>97</returnCode>
</getDevicesResponse>
</soapenv:Body>
</soapenv:Envelope>
Response: validation success
<soapenv:Envelope xmlns:soapenv="<www.w3.org/2003/05/soap-envelope>">
<soapenv:Body>
<getDevicesResponse xmlns="<www.abc.com/services>">
<returnCode>97</returnCode>
</getDevicesResponse>
</soapenv:Body>
</soapenv:Envelope>
Note:
One of the ways to validate response payload against schema is to validate payload on mock service response editor.
From the comparison, it is clear that difference is with namespace.
If we will be able to modify the response namespace in payload and update with the expected value, .net objects should be able to de-serialize them. In web reference world, we have to write custom SoapExtension to suffice this objective.
Step-1
Create a class name TraceExtension, and add the following code:
using System.Web.Services.Protocols;
public class TraceExtension: SoapExtension
{
Stream oldStream;
Stream newStream;
string filename;
public override Stream ChainStream(Stream stream)
{
oldStream = stream;
newStream = new MemoryStream();
return newStream;
}
public override object GetInitializer(Type serviceType)
{
return null;
}
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return null;
}
public override void Initialize(object initializer)
{
}
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.AfterDeserialize:
break;
case SoapMessageStage.AfterSerialize:
ModifyRequestStream(message);
break;
case SoapMessageStage.BeforeDeserialize:
ModifyResponseStream(message);
break;
case SoapMessageStage.BeforeSerialize:
break;
}
}
private void ModifyRequestStream(SoapMessage message)
{
newStream.Position = 0;
//WriteOutput(message);
newStream.Position = 0;
Copy(newStream, oldStream);
}
private void WriteOutput(SoapMessage message)
{
string soapString = (message is SoapServerMessage) ? "SoapResponse" : "SoapRequest";
}
private void ModifyResponseStream(SoapMessage message)
{
Copy(oldStream, newStream);
var doc = WriteInput(message);
newStream.Position = 0;
doc.Save(newStream);
newStream.Position = 0;
}
private XmlDocument WriteInput(SoapMessage message)
{
string soapString = (message is SoapServerMessage) ? "SoapRequest" : "SoapResponse";
XmlDocument doc = new XmlDocument();
XmlWriter newWriter = XmlWriter.Create(newStream);
newWriter.Flush();
newStream.Position = 0;
doc.Load(newStream);
string xml = doc.OuterXml;
xml = xml.Replace("<abcservice.directed.com>", "<www.abc.com/services>"); //update logic for namespace
doc.LoadXml(xml);
return doc;
}
void Copy(Stream from, Stream to)
{
TextReader reader = new StreamReader(from);
TextWriter writer = new StreamWriter(to);
writer.WriteLine(reader.ReadToEnd());
writer.Flush();
}
}
Step-2
Go to your config file app.config and update that you are using soap extension class:
<system.web>
<webServices>
<soapExtensionTypes>
<add type="MyConsoleApplication.TraceExtension, MyConsoleApplication" priority="0" group="High" />
<!--Give fully qualified name for SOAP extension, so that it can be readable-->
</soapExtensionTypes>
</webServices>
</system.web>
Now, run the client application, it should be able to de-serialize the response payload.
In future if you want to handle the same in service side, please update the logic in ProcessMessage() method. It just requires to follow the reverse approach of client side.
I hope this helps!