private async void MakeRequest <T>(string verb, string path, IDictionary <string, object> parameters, Action <Exception, HttpWebResponse> callback, bool skipAuth)
        {
            if (!path.StartsWith("/"))
            {
                path = "/" + path;
            }

            // get the token before generating the request url, as there may be a new ServiceRoot
            var token = skipAuth ? null : await Client.GetAccessToken();

            var url         = String.Format(CultureInfo.InvariantCulture, "{0}{1}", ServiceRoot, path);
            var requestType = HttpRequestType.HttpPostJson;
            IEnumerable <KeyValuePair <string, object> > files = null;

            switch (verb.ToUpperInvariant())
            {
            case "GET":
                // For redirects (specifically Azure Blob), if our authentication header is in there
                // it'll deny us access.  So for that case, we just need to add the access token to the parameters
                // collection so it doesn't get added as a header.
                //
                if (typeof(T) == typeof(BuddyFile) && !parameters.ContainsKey("accessToken"))
                {
                    parameters["accessToken"] = token;
                    token = null;
                }
                url        += "?" + GetUrlEncodedParameters(parameters);
                requestType = HttpRequestType.HttpGet;
                break;

            default:
                // do we have any file parameters.
                //
                files = from p in parameters where p.Value is BuddyFile select p;

                if (files.Any())
                {
                    // remove the files from the main array.
                    requestType = HttpRequestType.HttpPostMultipartForm;
                    var newParameters = new Dictionary <string, object>();
                    foreach (var fileKvp in files)
                    {
                        newParameters.Add(fileKvp.Key, fileKvp.Value);
                    }

                    foreach (var kvp in newParameters)
                    {
                        parameters.Remove(kvp.Key);
                    }

                    // get json for the remainder and make it into a file
                    var json = JsonConvert.SerializeObject(parameters, Formatting.None);

                    var jsonFile = new BuddyFile(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json)), "body", "application/json");

                    newParameters.Add(jsonFile.Name, jsonFile);
                    parameters = newParameters;
                }

                break;
            }


            HttpWebRequest wr = null;

            try
            {
                wr = (HttpWebRequest)WebRequest.Create(url);
            }
            catch (Exception ex)
            {
                callback(ex, null);
                return;
            }

            wr.Headers["BuddyPlatformSDK"] = SdkVersion;

            if (token != null && (parameters == null || !parameters.ContainsKey("accessToken")))
            {
                if (SharedSecret != null)
                {
                    string requestSig = GenerateSignatureForRequest(verb, path);
                    if (requestSig != null)
                    {
                        wr.Headers["Authorization"] = String.Format("Buddy {0} {1}", token, requestSig);
                    }
                }
                else
                {
                    wr.Headers["Authorization"] = String.Format("Buddy {0}", token);
                }
            }

            wr.Method = verb;

            Action getResponse = () =>
            {
                try
                {
                    int requestStatus = -1;
                    LogRequest(MethodName(verb, path), url, null);
                    wr.BeginGetResponse((async2) =>
                    {
                        try
                        {
                            lock (wr)
                            {
                                if (requestStatus == 1)
                                {
                                    throw new WebException("Request timed out.", WebExceptionStatus.RequestCanceled);
                                }
                                else
                                {
                                    HttpWebResponse response = (HttpWebResponse)wr.EndGetResponse(async2);
                                    callback(null, response);
                                }
                            }
                        }
                        catch (WebException ex)
                        {
                            callback(ex, null);
                        }
                    }, null);

                    // spin up a timer to check for timeout.  not all platforms
                    // support proper threadpool wait.
                    //
                    Action timeoutHandler = () =>
                    {
                        lock (wr)
                        {
                            if (requestStatus == -1)
                            {
                                requestStatus = 1;
                                wr.Abort();
                            }
                        }
                    };
#if NETFX_CORE
                    TimeSpan delay = TimeSpan.FromMilliseconds(TimeoutMilliseconds);

                    Windows.System.Threading.ThreadPoolTimer.CreateTimer(
                        (source) =>
                    {
                        timeoutHandler();
                    }, delay);
#else
                    new System.Threading.Timer((state) =>
                    {
                        timeoutHandler();
                    }, null, TimeoutMilliseconds, System.Threading.Timeout.Infinite);
#endif
                }
                catch (WebException wex)
                {
                    LogResponse(MethodName(verb, path), wex.ToString(), TimeSpan.Zero);
                    callback(wex, null);
                }
            };

            try
            {
                if (HttpRequestType.HttpGet == requestType)
                {
                    getResponse();
                }
                else
                {
                    wr.BeginGetRequestStream((async) =>
                    {
                        try
                        {
                            using (var rs = wr.EndGetRequestStream(async))
                            {
                                switch (requestType)
                                {
                                case HttpRequestType.HttpPostJson:
                                    wr.ContentType = "application/json";

                                    parameters = ConvertUnspecifiedDateTimes(parameters);

                                    var json = JsonConvert.SerializeObject(parameters, Formatting.None, new JsonSerializerSettings
                                    {
                                        DateTimeZoneHandling = DateTimeZoneHandling.Utc
                                    });
                                    byte[] jsonbytes = System.Text.Encoding.UTF8.GetBytes(json);

                                    rs.Write(jsonbytes, 0, jsonbytes.Length);
                                    LogRequest(MethodName(verb, path), url, json);
                                    break;

                                case HttpRequestType.HttpPostMultipartForm:
                                    HttpPostMultipart(wr, rs, parameters);
                                    break;
                                }
                                rs.Flush();
                            }

                            getResponse();
                        }
                        catch (WebException wex)
                        {
                            LogResponse(MethodName(verb, path), wex.ToString(), TimeSpan.Zero);

                            callback(wex, null);
                        }
                    }, null);
                }
            }
            catch (WebException wex)
            {
                LogResponse(MethodName(verb, path), wex.ToString(), TimeSpan.Zero);

                callback(wex, null);
            }
        }
        protected override Task <BuddyCallResult <T> > CallMethodAsyncCore <T>(
            string verb,
            string path,
            object parameters,
            bool skipAuth)
        {
            // TODO: Refactor to use async/await instead of
            // TCS/callbacks.
            var tcs = new TaskCompletionSource <BuddyCallResult <T> >();

            DateTime start = DateTime.Now;

            StartRequest();

            Action <Exception, BuddyCallResult <T> > finishMethodCall = (ex, bcr) =>
            {
                EndRequest();
                if (ex == null)
                {
                    tcs.TrySetResult(bcr);
                    return;
                }

                WebException    webEx    = ex as WebException;
                HttpWebResponse response = null;
                var             err      = "UnknownServiceError";

                bcr.Message = ex.ToString();
                if (webEx != null)
                {
                    err = "InternetConnectionError";

                    if (webEx.Response != null)
                    {
                        response    = (HttpWebResponse)webEx.Response;
                        bcr.Message = response.StatusDescription;

                        bcr.StatusCode = (int)response.StatusCode;
                        if (bcr.StatusCode >= 400)
                        {
                            err = "UnknownServiceError";
                        }
                    }
                    else
                    {
                        bcr.Message = webEx.Status.ToString();
                    }
                }

                bcr.Error = err;
                LogResponse(verb + " " + path, bcr.Message, DateTime.Now.Subtract(start), response);


                OnServiceException(ex);
                tcs.TrySetResult(bcr);
            };

            var d = ParametersToDictionary(parameters);

            MakeRequest <T>(verb, path, d, async(ex, response) =>
            {
                var bcr = new BuddyCallResult <T>();

                var isFile = typeof(BuddyFile).Equals(typeof(T));


                if (response == null && ex != null && ex is WebException)
                {
                    response = (HttpWebResponse)((WebException)ex).Response;
                }

                if ((response == null) && ex != null)
                {
                    finishMethodCall(ex, bcr);
                    return;
                }
                else if (response != null)
                {
                    bcr.StatusCode = (int)response.StatusCode;
                    if (!isFile || (bcr.StatusCode >= 400 && response.ContentType.Contains("application/json")))
                    {
                        string body = null;
                        try
                        {
                            using (var responseStream = response.GetResponseStream())
                            {
                                body = await new StreamReader(responseStream).ReadToEndAsync();
                            }
                        }
                        catch (Exception rex)
                        {
                            finishMethodCall(rex, bcr);
                            return;
                        }

                        LogResponse(MethodName(verb, path), body, DateTime.Now.Subtract(start), response);

                        //json parse
                        try
                        {
                            var envelope = JsonConvert.DeserializeObject <JsonEnvelope <T> >(body, new JsonSerializerSettings
                            {
                                DateTimeZoneHandling = DateTimeZoneHandling.Local
                            });

                            if (envelope == null)
                            {
                                // fall through
                            }
                            else if (envelope.error != null)
                            {
                                bcr.Error       = envelope.error;
                                bcr.ErrorNumber = envelope.errorNumber;
                                bcr.Message     = envelope.message;
                            }
                            else
                            {
                                // special case dictionary.
                                if (typeof(IDictionary <string, object>).IsAssignableFrom(typeof(T)))
                                {
                                    object obj = envelope.result;
                                    IDictionary <string, object> d2 = (IDictionary <string, object>)obj;
                                    obj             = (obj == null) ? new Dictionary <string, object>(DotNetDeltas.InvariantComparer(true)) : new Dictionary <string, object>(d2, DotNetDeltas.InvariantComparer(true));
                                    envelope.result = (T)obj;
                                }
                                bcr.Result = envelope.result;
                            }

                            if (envelope != null)
                            {
                                bcr.RequestID = envelope.request_id;
                            }
                        }
                        catch (Exception pex)
                        {
                            LogMessage(pex.ToString());
                            bcr.Error   = "BadJsonResponse";
                            bcr.Message = "Couldn't parse JSON: \r\n" + body;
                        }
                    }
                    else
                    {
                        bcr = new BuddyCallResult <T>();

                        if (bcr.StatusCode < 400)
                        {
                            var file   = new BuddyFile(response.GetResponseStream(), null, response.ContentType);
                            bcr.Result = (T)(object)file;
                        }
                        bcr.StatusCode = (int)response.StatusCode;
                    }


                    try
                    {
                        finishMethodCall(null, bcr);
                    }
                    catch (Exception ex3)
                    {
                        finishMethodCall(ex3, bcr);
                    }
                }
            }, skipAuth);

            return(tcs.Task);
        }