I’ve been involved in a project where I’m interfacing with a partner Web Service that asynchronously processes messages. Messages are sent to the Web Service and then at some point in the future (hours or days later) the host application sends a SOAP message back to our server at URL we provide.
The Web Service on the host consists basically of a single endpoint - it receives only a very complex message structure which serves the entire system. The message hold both the input data and the result data that is sent back, so effectively there's a single message entity to encompasses the entire messaging in the service.
The message in question is defined by the main Web Service – there’s WSDL and with some tweaking of the WSDL I was able to call the Web Service and pass this very complex hierarchical object to the Web Service. So far so good.
The Host Web Service is not a .NET service – it looks like a homegrown implementation, but I can’t really tell exactly. In any case the WSDL for the service has allowed me to pick up this very complex type in my client application. Sending the message seems to work just fine using standard a .NET Web Reference interface. Through this interface I can pick up the entire message type, which is great because I'll need it on the inbound end to receive messages. The WSDL provides this interface luckily so no manual type creation!
So now my problem is that I need to implement the receiving Web Service but I can’t get ASMX to generate the structure necessary to accept this SOAP message sent by this non-.NET host application server. What I’ve done so far is use the types that the WSDL import for the Web Reference imported and then use that as an input parameter for my own Service method:
[WebMethod(MessageName="NotifyRequest")]
public string ReceiveMessage(OmniCellProvider.OmniConnectService.MetaOMNIConnect MetaOMNIConnect)
{
return MetaOMNIConnect.OMNIConnectMessage.BulkMessageHeader.RecipientList[0].PartnerID;
}
When the call comes in though, the inbound parameter is null, which is caused by the mismatch in the message formats I assume.
The problem is that the service definition of the host service doesn’t match the signature that .NET ASMX services generate. Basically the Host service sends the message without a top level message root element. The body root node is the top level object, which has caused some problems in a variety of places (for COM interop, because .NET marks the top level ‘object’ as a SoapHeader type which is not ComVisible for example).
Here’s the top level of the message as sent by the host server:
<?xml version="1.0" encoding="UTF-8" ?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<MetaOMNIConnect xmlns="http://www.omnimoving.com/OMNIConnect">
<OMNIConnectMessage>
<BulkMessageHeader>
<Sender>
…
And this is what the .NET Web Service expects:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<NotifyRequest xmlns="http://www.omnimoving.com/OMNIConnect">
<MetaOMNIConnect>
<OMNIConnectMessage>
<BulkMessageHeader>
…
Notice that the .NET Service has a root node above the actual message object (MetaOMNIConnect).
Is there any way to force the .NET Web Service to expose its interface so that it matches the calling service?
In the meantime I’ve worked around this issue with a combination of an ASP.NET HTTP Handler and XML Serialization. Rather than using a Web Service, I just have the handler pick up the file, then read the input buffer directly and deserialize it with an XmlReader. Here’s the rough test code I hacked together for this:
public void ProcessRequest(HttpContext context)
{
HttpRequest Request = context.Request;
HttpResponse Response = context.Response;
string XmlString = null;
XmlReader xr = XmlReader.Create(Request.InputStream);
while (xr.Read())
{
if (xr.NodeType == XmlNodeType.Element && xr.Name == "MetaOMNIConnect")
{
MetaOMNIConnect Message = this.DeSerializeMessage(xr);
// *** Echo back a value from the result
XmlString = Message.RateResponse.ServiceQuote.Rate.Value.ToString();
break;
}
}
Response.ContentType = "text/plain";
Response.Write(XmlString);
}
protected MetaOMNIConnect DeSerializeMessage(XmlReader xr)
{
object Instance = null;
XmlSerializer serializer = null;
try
{
// Create an instance of the XmlSerializer specifying type and namespace.
serializer = new XmlSerializer(typeof(MetaOMNIConnect));
Instance = serializer.Deserialize(xr);
}
catch (Exception ex)
{
string ErrorMessage = ex.Message;
return null;
}
return Instance as MetaOMNIConnect;
}
This works, but it’s not exactly proper SOA implementation <bg>… And I should count myself lucky there's only a single message that is actually sent here, otherwise this would get much uglier.
When it really comes down to it, I would like to use a proper Web Service on our Server to handle this for me, but I’m not inclined to hand code my own WSDL and service definition for this monster message type definition.
So the questions are:
- Is there a way to modify the ASMX input message format so it fits the message schema sent by the host service?
- If not, what would be the recommended approach to make this happen?
- Does WCF help in this respect at all? I know on the client end WCF provides a lot more control over the SOAP messages sent – I suspect there might be more control on the hosting end as well.
I’m a little out of my range on this one, so I appreciate any feedback provided even if it’s just some general thoughts on this subject.
Other Posts you might also like