Silverlight JSON WebClient wrapper/helper
June 06, 2010
This is a small class that uses the NewtonSoft JSON.NET library to make JSON requests to a URL. It handles both requests that require parameters, and those that just return results (for example a GetProducts() call).
I chose NewtonSoft over the default Microsoft JSON deserializer as it handles nested objects and doesn’t require attributes on your domain objects.
The class probably needs a few tweaks to be completely production-ready.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Builds and executes a JSON request to an ASP.NET web service or MVC controller method. | |
/// </summary> | |
/// <typeparam name="T">Use string if the request does not return any type.</typeparam> | |
public class JsonRequest<T> | |
{ | |
private Action<T> _completeAction; | |
/// <summary> | |
/// A container for the WebRequest async calls. | |
/// </summary> | |
private class State | |
{ | |
public HttpWebRequest Request { get; set; } | |
public Dictionary<string, string> Parameters { get; set; } | |
} | |
/// <summary> | |
/// The ASP.NET [WebMethod] attribute sticks the payload in a "d" property for some reason, so objects are deserialized | |
/// into this fake container to work around this. | |
/// </summary> | |
public class JsonContainer | |
{ | |
public object d { get; set; } | |
} | |
/// <summary> | |
/// Executes the JSON request, calling the completeAction delegate when done. | |
/// </summary> | |
/// <param name="url">The full URL to the ASP.NET web service.</param> | |
/// <param name="parameters">A name/value pair for the parameters to send to the URL. Use null if no parameters are required.</param> | |
/// <param name="completeAction">The delegate to call once the request completes. Use null if no action is required. The parameter | |
/// passed into this method can be the default value of T.</param> | |
public void Execute(string url, Dictionary<string, string> parameters, Action<T> completeAction) | |
{ | |
// Check the url | |
if (string.IsNullOrEmpty(url)) | |
throw new ArgumentNullException("The url parameter is null or empty"); | |
// Check the func | |
if (completeAction == null) | |
throw new ArgumentNullException("The completeAction parameter is null or empty"); | |
_completeAction = completeAction; | |
try | |
{ | |
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url); | |
request.ContentType = "application/json"; | |
request.Method = "POST"; | |
// Write to the body with the parameters, if there are some | |
if (parameters != null && parameters.Count > 0) | |
{ | |
State state = new State(); | |
state.Request = request; | |
state.Parameters = parameters; | |
request.BeginGetRequestStream(LoadRequestBegin, state); | |
} | |
else | |
{ | |
request.BeginGetResponse(LoadRequestComplete, request); | |
} | |
} | |
catch (WebException) | |
{ | |
// If the exception is caught here, the complete action isn't called by the WebClient's async methods. | |
if (_completeAction != null) | |
_completeAction(default(T)); | |
// For now, simply throw it back | |
throw; | |
} | |
} | |
/// <summary> | |
/// Writes parameters to the request body. | |
/// </summary> | |
/// <param name="result"></param> | |
private void LoadRequestBegin(IAsyncResult result) | |
{ | |
State state = (State)result.AsyncState; | |
HttpWebRequest request = state.Request; | |
using (Stream stream = request.EndGetRequestStream(result)) | |
{ | |
using (StreamWriter writer = new StreamWriter(stream)) | |
{ | |
string jsonParams = BuildParameters(state.Parameters); | |
writer.Write(jsonParams); | |
} | |
} | |
request.BeginGetResponse(LoadRequestComplete, request); | |
} | |
/// <summary> | |
/// Manually builds the request parameters as a JSON string. This may already be in the JSON.NET library. | |
/// </summary> | |
/// <param name="parameters"></param> | |
/// <returns></returns> | |
private string BuildParameters(Dictionary<string, string> parameters) | |
{ | |
List<string> list = new List<string>(); | |
foreach (string key in parameters.Keys) | |
{ | |
list.Add(string.Format("\"{0}\" : \"{1}\"", key, parameters[key])); | |
} | |
return "{" + string.Join(",", list.ToArray()) + "}"; | |
} | |
/// <summary> | |
/// Gets the response of the request and fires the provided Action. | |
/// </summary> | |
/// <param name="result"></param> | |
private void LoadRequestComplete(IAsyncResult result) | |
{ | |
T deserialized = default(T); | |
try | |
{ | |
HttpWebRequest request = (HttpWebRequest)result.AsyncState; | |
// Get the response | |
WebResponse response = request.EndGetResponse(result); | |
using (Stream stream = response.GetResponseStream()) | |
{ | |
using (StreamReader reader = new StreamReader(stream)) | |
{ | |
string json = reader.ReadToEnd(); | |
deserialized = Deserialize(json); | |
} | |
} | |
} | |
catch (WebException) | |
{ | |
// This is swallowing the exception - you will want to do something more meaningful with it | |
} | |
finally | |
{ | |
if (_completeAction != null) | |
_completeAction(deserialized); | |
} | |
} | |
/// <summary> | |
/// Deserializes the provided JSON to type T of the class. | |
/// </summary> | |
/// <param name="json"></param> | |
/// <returns></returns> | |
private T Deserialize(string json) | |
{ | |
try | |
{ | |
JsonSerializer serializer = new JsonSerializer(); | |
// I'm not sure if ASP MVC uses the 'd' payload, so this a check might be needed here for it. | |
JsonContainer container = (JsonContainer)serializer.Deserialize(new StringReader(json), typeof(JsonContainer)); | |
json = container.d.ToString(); | |
return (T)serializer.Deserialize(new StringReader(json), typeof(T)); | |
} | |
catch (InvalidCastException) | |
{ | |
// You might prefer this to throw instead of returning a null | |
return default(T); | |
} | |
} | |
} |
I'm Chris Small, a software engineer working in London. This is my tech blog. Find out more about me via Github, Stackoverflow, Resume