Generic Json HTTP Request
Ok, you are asked to create an application to send some product codes encoded as JSON to a web API and receive an id code in the response. So you create your class which holds the data to send, and a trivial class to receive the response. The data is encoded to JSON using .NET's DataContractJsonSerializer
, encoded again to a byte[]
array and passed to your new (HttpWebRequest
).
All's good, you receive a response, and using the DataContractJsonSerializer.ReadObject
method, the response stream is serialized into your trivial class.
You give yourself a pat on the back and have that warm fuzzy feeling inside you. The world is right again.
Now For A Reality Check
Another request comes in; can your app send a different set of data, oh and the response is going to be an address with 5 lines, and latitude and longitude points.
Gulp ... your trivial class isn't going to be able to hold all that data in the response, and you are going to need to re-write the code which handled sending the request and receiving the response.
A Generic Request
First, let's re-write our web request class using Generics, a heavily talked about subject. This means we can pass any class to receive the response without the need to replicate code.
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Runtime.Serialization.Json;
using System.Security.Cryptography.X509Certificates;
namespace HTTPRequestDemo
{
public class HTTPRequest<T> where T : class
{
public delegate void CallbackHandler(object sender, T t);
private CallbackHandler callbackHandler;
private readonly string host;
private readonly string method;
const string type = @"application/json";
public HTTPRequest(string h, string m, CallbackHandler callback)
{
host = h;
method = m;
callbackHandler = callback;
}
public void Post(byte[] bytes, string uri)
{
ServicePointManager.ServerCertificateValidationCallback +=
new System.Net.Security.RemoteCertificateValidationCallback(Accept);
var len = bytes.Length;
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Proxy = null;
request.Credentials = CredentialCache.DefaultCredentials;
request.ContentLength = len;
request.ContentType = type;
request.Accept = type;
request.Method = method;
request.Host = host;
var dataStream = request.GetRequestStream();
dataStream.Write(bytes, 0, len);
dataStream.Close();
try
{
var t = Read((HttpWebResponse)request.GetResponse());
callbackHandler(this, t);
}
catch (Exception)
{
callbackHandler(this, default(T));
}
}
private T Read(HttpWebResponse resp)
{
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(T));
T e;
using (MemoryStream memoryStream = new MemoryStream())
{
e = (T)jsonSerializer.ReadObject(resp.GetResponseStream());
memoryStream.Close();
}
resp.Close();
return e;
}
private bool Accept(object sender, X509Certificate cert, X509Chain chn, SslPolicyErrors ssl)
{
return true;
}
}
}
And to consume this class in your application. Firstly, you'll need a label to hold some data from the response, you choose, and a delegate to stop any cross thread contamination.
delegate void UpdateCrossThreadCallback(object sender, object text);
And a method to receive the response.
private void ReceiveResponse(object sender, object e)
{
if (label1.InvokeRequired)
{
UpdateCrossThreadCallback d = new UpdateCrossThreadCallback(ReceiveResponse);
this.Invoke(d, new object[] { sender, e });
}
else
{
var result = (MyResponseClass)e;
label1.Text = result.id;
}
}
Now, maybe on a click event of a button.
var ASCIIEncoder = new ASCIIEncoding();
byte[] bits = ASCIIEncoder.GetBytes(jsonString);
var request = new HTTPRequest<MyResponseClass>("web.api.co.uk", "POST", ReceiveResponse);
Task.Factory.StartNew(() => request.Post(bits, uri));