Top

Tags


Roadkill .NET Wiki

Google ads

Recommended reading


Search

Sunday
Jun062010

Silverilght JSON WebClient wrapper/helper

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.

/// <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);
        }
    }
}

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>
« Online XPath Tester | Main | Online Javascript Minifier »