Friday, February 20, 2009

XML Request RESTful POSTing Client

Through much headache I have finally been able to create a simple WebRequest based client with which I can call my simple service via HTTP POST with XML in the HTTP Body! Horrah horrah!!

I found a million examples where you can pass your parameters via UriTemplate, simple URL parameters, JSON, and even putting the URL Parameters in the POST body replicating a Form POST. I'm sure I could have thrown together something where I could have parsed the XML from the input stream and generated the object that way (that's actually what I was doing for the URL Parameters in the POST body) but what's the point if you're pretty sure there's something that will do it for you??

So here you go, an example client that will post a simple XML-serialized object to a WCF Service that uses [WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Xml, ResponseFormat = WebMessageFormat.Xml)] but lets the system do the deserialization for you (you can't get it to not do the serialization for you, so we won't worry about that).

First lets define the contract for the service I'm sending to. It's basically a proxy for sending e-mail. It's a simple POST with Bare XML requests and responses:

[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Xml, ResponseFormat = WebMessageFormat.Xml)]
StatusResponse SendMessage(SendMessageRequest request);

SendMessageRequest is defined as (not in full, obviously):

[DataContract]
public class SendMessageRequest
{
[DataMember]
public String EMail;
[DataMember]
public String Message;
}

I used an XmlTextWriter to generate the envelope (http body):

StringWriter envelopeGenerator = new StringWriter();
XmlTextWriter xmlEnvilopeGenerator = new XmlTextWriter(envelopeGenerator);

xmlEnvilopeGenerator.WriteStartElement("SendMessageRequest");
xmlEnvilopeGenerator.WriteAttributeString("xmlns", "http://schemas.datacontract.org/2004/07/");
xmlEnvilopeGenerator.WriteAttributeString("i", "xmlns", "http://www.w3.org/2001/XMLSchema-instance");

xmlEnvilopeGenerator.WriteStartElement("EMail");
xmlEnvilopeGenerator.WriteString("email@email.com");
xmlEnvilopeGenerator.WriteEndElement();

xmlEnvilopeGenerator.WriteStartElement("Message");
xmlEnvilopeGenerator.WriteString("Test Message");
xmlEnvilopeGenerator.WriteEndElement();

xmlEnvilopeGenerator.WriteEndElement();

xmlEnvilopeGenerator.Flush();

String envelope = envelopeGenerator.ToString();

Now it's very important that you don't start/stop an XML Document around the root element. This will add the XML declaration, which screws up the parser and you'll end up with an HTTP 400 response from your service. The above code gives you the following envelope:

<SendMessageRequest xmlns="http://schemas.datacontract.org/2004/07/" d1p1:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:d1p1="xmlns"><EMail>email@email.com</EMail><Message>Test Message</Message></SendMessageRequest>

Creating that envelope is really the hard part. I tried a million different ways to represent the object in XML, learning along the way that the parser is very fickle. All the namespace attributes have to be correct, and that darn xml declaration screwed me up for quite some time. Lastly you just have to post the string (disclaimer, this is non-production test code, and obviously not robust. it is merely for example purposes and should not be used directly):

byte[] envelopeBytes = Encoding.ASCII.GetBytes(envelope);

Uri uri = new Uri("http://someuri");
WebRequest request = WebRequest.Create(uri);
request.Method = "POST";
request.ContentType = "text/xml";
request.ContentLength = envelopeBytes.Length;

using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(envelopeBytes, 0, envelopeBytes.Length);
requestStream.Close();

using (WebResponse response = request.GetResponse())
{
Console.WriteLine(new StreamReader(response.GetResponseStream()).ReadToEnd());
}
}

This returns a successful response:
<StatusResponse xmlns="http://schemas.datacontract.org/2004/07/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><ErrorMessage/><ResponseCode>1</ResponseCode><ResponseCodeValue>Success</ResponseCodeValue></StatusResponse>

Now you might ask, why write this client in the first place when you can simply create a web service proxy to interact with the service. Well, I've got customers who aren't .NET shops so I need to know exactly what messages they need to send and what responses they should expect.

I hope you found this useful.

Happy POSTing!

No comments:

Post a Comment