Making HTTP Post with Silverlight a little easier
In my earlier post, I talked about how to go about making an HTTP Post in Silverlight. The whole process is a little complicated to say the least. Silverlight does not come with an utility class like WebClient that makes making a HTTP Get a little easier. So, I created a class called ‘HTTPPostHelper’ that does for HTTP Post what WebClient does for HTTP Get. The ‘HTTPPostHelper’ class listed below, makes the whole process a lot easier. To use the class, do the following
- Create an instance of the HTTPPostHelper class with the type of the input class (T) and the type of the returned class (K) as the generic arguments.
- Listen to the ‘PostCompleted’ event that gets called when the Post operation completes successfully. The EventArgs of the event contains an instance of the return class type.
- Listen to the ‘PostFailed’ event that gets called when the Post operation fails. The EventArgs of the event contains the exception information.
- Call the ‘Post’ method on the HTTPPostHelper with the input object and the URL address to make the Post operation happen.
- The input object gets serialized by default using the DataContractSerializer. But this can be overridden by setting the Serializer property of the HTTPPostHelper with an appropriate delagate that serializes the input object to a stream.
- The output from the HTTP Post operation is deserialized by default using the DataContractSerializer. But this too can be overridden by the Deserializer property of the HTTPPostHelper.
- For example, if you want to send and receive JSON data to the HTTP endpoint, just override the default Serializer & Deserializer with delegates that use DataContractJsonSerializer instead or your own custom binary serializer if need be.
- The ‘ContentType’ used for the Post operation can also set before calling the Post method.
- The class handles all the thread hopping internally. The ‘PostCompleted’ and the ‘PostFailed’ events get raised on the same thread from which they were called from.
public class HTTPPostHelper<T, K>
{
SynchronizationContext syncContext;
HttpWebRequest _request;
public string ContentType
{
get;
set;
}
public event EventHandler<EventArgs<K>> PostCompleted;
public event EventHandler<ExceptionEventArgs> PostFailed;
public Action<T, Stream> Serializer = null;
public Func<Stream, K> Deserializer = null;
public HTTPPostHelper()
{
Serializer = DefaultSerializer;
Deserializer = DefaultDeserializer;
ContentType = “application/stream”;
}
void DefaultSerializer(T input, Stream output)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(output, input);
output.Flush();
}
K DefaultDeserializer(Stream input)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(K));
K result = (K)serializer.ReadObject(input);
return result;
}
public void Post(Uri address, T input)
{
syncContext = SynchronizationContext.Current;
_request = (HttpWebRequest)WebRequest.Create(address);
_request.Method = “POST”;
_request.ContentType = “application/stream”;
_request.BeginGetRequestStream(new AsyncCallback(PostRequestReady), new HttpWebRequestData<T>()
{
Request = _request,
Data = input
});
}
private void PostRequestReady(IAsyncResult asyncResult)
{
HttpWebRequestData<T> requestData = asyncResult.AsyncState as HttpWebRequestData<T>;
HttpWebRequest request = requestData.Request;
Stream requestStream = request.EndGetRequestStream(asyncResult);
Serializer(requestData.Data, requestStream);
requestStream.Close();
request.BeginGetResponse(new AsyncCallback(PostResponseCallback), request);
}
private void PostResponseCallback(IAsyncResult ar)
{
try
{
HttpWebRequest request = ar.AsyncState as HttpWebRequest;
HttpWebResponse response = request.EndGetResponse(ar) as HttpWebResponse;
Stream responseStream = response.GetResponseStream();
K urls = DefaultDeserializer(responseStream);
syncContext.Post(PostExtractResponse, new HttpWebResponseData<K>()
{
Response = response,
Data = urls
});
}
catch (Exception ex)
{
syncContext.Post(RaiseException, ex);
}
}
private void RaiseException(object state)
{
Exception ex = state as Exception;
if (PostFailed != null)
PostFailed(this, new ExceptionEventArgs(ex));
}
private void PostExtractResponse(object state)
{
HttpWebResponseData<K> responseData = state as HttpWebResponseData<K>;
if (PostCompleted != null)
PostCompleted(this, new EventArgs<K>(responseData.Data));
}
}
public class ExceptionEventArgs : EventArgs
{
public Exception ExceptionInfo { get; set; }
public ExceptionEventArgs(Exception ex)
{
ExceptionInfo = ex;
}
}
public class EventArgs<T> : EventArgs
{
private T eventData;
public EventArgs(T eventData)
{
this.eventData = eventData;
}
public T EventData
{
get
{
return eventData;
}
}
}
Interesting – I just wrote a database-based message queue using a similar approach. Obviously my goal was to persist classes using the DataContractSerializer rather than dealing with HTTP POSTs but I see a lot of similarities. Except my code is more verbose 🙂 Thanks for sharing.
Not to ask a stupid question, but Silverlight 3 obviates much of the code in this helper, because WebClient added UploadStringAsync() support, right?
[http://msdn.microsoft.com/en-us/library/ms144240(v=VS.95).aspx]