【问题标题】:WP8 HttpClient.PostAsync never returns resultWP8 HttpClient.PostAsync 从不返回结果
【发布时间】:2013-12-22 22:10:00
【问题描述】:

我有一个 Windows Phone 8 应用程序,我在其中调用 await HttpClient.PostAsync,但它从不返回结果。它只是坐在那里挂起。如果我从控制台应用程序运行完全相同的代码,它几乎会立即返回结果。完成这项工作的所有代码都驻留在可移植类库中。我将不胜感激您可以提供的任何帮助。我在此状态下发现的所有其他问题都可以使用 await client.PostAsync,我已经在这样做了。

我的类库中的代码是这样的:

public class Authenticator
{
    private const string ApiBaseUrl = "http://api.fitbit.com";
    private const string Callback = "http://myCallbackUrlHere";
    private const string SignatureMethod = "HMAC-SHA1";
    private const string OauthVersion = "1.0";
    private const string ConsumerKey = "myConsumerKey";
    private const string ConsumerSecret = "myConsumerSecret";
    private const string RequestTokenUrl = "http://api.fitbit.com/oauth/request_token";
    private const string AccessTokenUrl = "http://api.fitbit.com/oauth/access_token";
    private const string AuthorizeUrl = "http://www.fitbit.com/oauth/authorize";
    private string requestToken;
    private string requestTokenSecret;

    public string GetAuthUrlToken()
    {
        return GenerateAuthUrlToken().Result;
    }

    private async Task<string> GenerateAuthUrlToken()
    {
        var httpClient = new HttpClient { BaseAddress = new Uri(ApiBaseUrl) };
        var timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0);
        var oauthTimestamp = Convert.ToInt64(timeSpan.TotalSeconds).ToString(CultureInfo.InvariantCulture);
        var oauthNonce = DateTime.Now.Ticks.ToString(CultureInfo.InvariantCulture);

        var authHeaderValue = string.Format(
            "oauth_callback=\"{0}\",oauth_consumer_key=\"{1}\",oauth_nonce=\"{2}\"," +
            "oauth_signature=\"{3}\",oauth_signature_method=\"{4}\"," +
            "oauth_timestamp=\"{5}\",oauth_version=\"{6}\"",
            Uri.EscapeDataString(Callback),
            Uri.EscapeDataString(ConsumerKey),
            Uri.EscapeDataString(oauthNonce),
            Uri.EscapeDataString(this.CreateSignature(RequestTokenUrl, oauthNonce, oauthTimestamp, Callback)),
            Uri.EscapeDataString(SignatureMethod),
            Uri.EscapeDataString(oauthTimestamp),
            Uri.EscapeDataString(OauthVersion));

        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
            "OAuth",
            authHeaderValue);

        var content = new StringContent(string.Empty);
        var response = await httpClient.PostAsync(RequestTokenUrl, content);

        if (response.StatusCode != HttpStatusCode.OK)
        {
            throw new Exception("Request Token Step Failed");
        }

        var responseContent = await response.Content.ReadAsStringAsync();
        var responseItems = responseContent.Split(new[] { '&' });

        this.requestToken = responseItems[0];
        this.requestTokenSecret = responseItems[1];

        var url = string.Format("{0}?{1}&display=touch", AuthorizeUrl, this.requestToken);

        return url;
    }

    public string CreateSignature(string url, string nonce, string timestamp, string callback)
    {
        // code removed
        return signatureString;
    }

    private static byte[] StringToAscii(string s)
    {
        // code removed
        return retval;
    }
}

我有一个调用这个库的控制台应用程序,它可以正常工作:

public class Program
{
    public static void Main(string[] args)
    {
        var o = new Program();
        o.LinkToFitbit();
    }

    public void LinkToFitbit()
    {
        var authenticator = new Authenticator();

        // this works and returns the url immediately
        var url = authenticator.GetAuthUrlToken();

        // code removed
    }
}

当我从我的 WP8 应用程序运行时,它只是在库中的这一行时挂起:

var response = await httpClient.PostAsync(RequestTokenUrl, content);

这是我的 WP8 代码:

public partial class FitbitConnector : PhoneApplicationPage
{
    public FitbitConnector()
    {
        InitializeComponent();
        this.AuthenticateUser();
    }

    private void AuthenticateUser()
    {
        var authenticator = new Authenticator();
        var url = authenticator.GetAuthUrlToken();

        // code removed
    }
}

【问题讨论】:

    标签: c# .net windows-phone-8 oauth dotnet-httpclient


    【解决方案1】:

    这一行阻塞了 UI 线程:

    public string GetAuthUrlToken()
    {
        return GenerateAuthUrlToken().Result;
    }
    

    await httpClient.PostAsync()后面的代码需要在UI线程中执行,但是因为被阻塞而无法执行。

    所以,替换这个:

    private void AuthenticateUser()
    {
        var authenticator = new Authenticator();
        var url = authenticator.GetAuthUrlToken();
    
        // code removed
    }
    

    有了这个:

    private async void AuthenticateUser()
    {
        var authenticator = new Authenticator();
        var url = await authenticator.GenerateAuthUrlToken();
    
        // code removed
    }
    

    请注意,我正在使用 asyncawait。您需要公开GenerateAuthUrlToken()。你可以删除GetAuthUrlToken()

    简而言之,Task&lt;T&gt;.Result 不是异步的。

    【讨论】:

    • 非常感谢!就是这样。我对 async/await 还是很陌生。
    【解决方案2】:

    你能发布 CreateSignature 和 StringToAscii 代码吗?我被困在基本的 oAuth 上。

    我可以获取请求令牌,但在执行访问令牌请求时得到“无效的 OAuth 签名”。

    【讨论】:

    • 在下面查看我的完整答案。太长了,无法发表评论。
    【解决方案3】:

    Nexus,给你。我创建了一个 OauthHelper 类,我用它来构建我需要的部分。看看下面。我希望这会有所帮助。

    public static class OauthHelper
    {
        public const string ConsumerKey = "MyKey";
        public const string ConsumerSecret = "MySecret";
        public const string UriScheme = "https";
        public const string HostName = "api.somePlace.com";
        public const string RequestPath = "/services/api/json/1.3.0";
        public const string OauthSignatureMethod = "HMAC-SHA1";
        public const string OauthVersion = "1.0";
    
        public static string BuildRequestUri(Dictionary<string, string> requestParameters)
        {
            var url = GetNormalizedUrl(UriScheme, HostName, RequestPath);
            var allParameters = new List<QueryParameter>(requestParameters.Select(entry => new QueryParameter(entry.Key, entry.Value)));
            var normalizedParameters = NormalizeParameters(allParameters);
            var requestUri = string.Format("{0}?{1}", url, normalizedParameters);
    
            return requestUri;
        }
    
        public static AuthenticationHeaderValue CreateAuthorizationHeader(
            string oauthToken,
            string oauthNonce,
            string oauthTimestamp,
            string oauthSignature)
        {
            var normalizedUrl = GetNormalizedUrl(UriScheme, HostName, RequestPath);
            return CreateAuthorizationHeader(oauthToken, oauthNonce, oauthTimestamp, oauthSignature, normalizedUrl);
        }
    
        public static AuthenticationHeaderValue CreateAuthorizationHeader(
            string oauthToken,
            string oauthNonce,
            string oauthTimestamp,
            string oauthSignature,
            string realm)
        {
            if (string.IsNullOrWhiteSpace(oauthToken))
            {
                oauthToken = string.Empty;
            }
    
            if (string.IsNullOrWhiteSpace(oauthTimestamp))
            {
                throw new ArgumentNullException("oauthTimestamp");
            }
    
            if (string.IsNullOrWhiteSpace(oauthNonce))
            {
                throw new ArgumentNullException("oauthNonce");
            }
    
            if (string.IsNullOrWhiteSpace(oauthSignature))
            {
                throw new ArgumentNullException("oauthSignature");
            }
    
            var authHeaderValue = string.Format(
                "realm=\"{0}\"," +
                "oauth_consumer_key=\"{1}\"," +
                "oauth_token=\"{2}\"," +
                "oauth_nonce=\"{3}\"," +
                "oauth_timestamp=\"{4}\"," +
                "oauth_signature_method=\"{5}\"," +
                "oauth_version=\"{6}\"," +
                "oauth_signature=\"{7}\"",
                realm,
                Uri.EscapeDataString(ConsumerKey),
                Uri.EscapeDataString(oauthToken),
                Uri.EscapeDataString(oauthNonce),
                Uri.EscapeDataString(oauthTimestamp),
                Uri.EscapeDataString(OauthSignatureMethod),
                Uri.EscapeDataString(OauthVersion),
                Uri.EscapeDataString(oauthSignature));
    
            var authHeader = new AuthenticationHeaderValue("OAuth", authHeaderValue);
    
            return authHeader;
        }
    
        public static string CreateSignature(
            string httpMethod,
            string oauthToken,
            string oauthTokenSecret,
            string oauthTimestamp,
            string oauthNonce,
            Dictionary<string, string> requestParameters)
        {
            // get normalized url
            var normalizedUrl = GetNormalizedUrl(UriScheme, HostName, RequestPath);
    
            return CreateSignature(
                httpMethod,
                oauthToken,
                oauthTokenSecret,
                oauthTimestamp,
                oauthNonce,
                requestParameters,
                normalizedUrl);
        }
    
        public static string CreateSignature(
            string httpMethod,
            string oauthToken,
            string oauthTokenSecret,
            string oauthTimestamp,
            string oauthNonce,
            Dictionary<string, string> requestParameters,
            string realm)
        {
            if (string.IsNullOrWhiteSpace(httpMethod))
            {
                throw new ArgumentNullException("httpMethod");
            }
    
            if (string.IsNullOrWhiteSpace(oauthToken))
            {
                oauthToken = string.Empty;
            }
    
            if (string.IsNullOrWhiteSpace(oauthTokenSecret))
            {
                oauthTokenSecret = string.Empty;
            }
    
            if (string.IsNullOrWhiteSpace(oauthTimestamp))
            {
                throw new ArgumentNullException("oauthTimestamp");
            }
    
            if (string.IsNullOrWhiteSpace(oauthNonce))
            {
                throw new ArgumentNullException("oauthNonce");
            }
    
            var allParameters = new List<QueryParameter>
            {
                new QueryParameter("oauth_consumer_key", ConsumerKey),
                new QueryParameter("oauth_token", oauthToken),
                new QueryParameter("oauth_nonce", oauthNonce),
                new QueryParameter("oauth_timestamp", oauthTimestamp),
                new QueryParameter("oauth_signature_method", OauthSignatureMethod),
                new QueryParameter("oauth_version", OauthVersion)
            };
            allParameters.AddRange(requestParameters.Select(entry => new QueryParameter(entry.Key, entry.Value)));
    
            // sort params
            allParameters.Sort(new QueryParameterComparer());
    
            // concat all params
            var normalizedRequestParameters = NormalizeParameters(allParameters);
    
            // create base string
            var signatureBase = string.Format(
                "{0}&{1}&{2}",
                UrlEncode(httpMethod.ToUpperInvariant()),
                UrlEncode(realm),
                UrlEncode(normalizedRequestParameters));
    
            var signatureKey = string.Format(
                "{0}&{1}",
                UrlEncode(ConsumerSecret),
                UrlEncode(oauthTokenSecret));
    
            // hash the base string
            var hmacsha1 = new HMACSHA1(StringToAscii(signatureKey));
            var signatureString = Convert.ToBase64String(hmacsha1.ComputeHash(StringToAscii(signatureBase)));
    
            return signatureString;
        }
    
        public static string GenerateNonce()
        {
            var ts = new TimeSpan(DateTime.Now.Ticks);
            var ms = ts.TotalMilliseconds.ToString().Replace(".", string.Empty);
            var nonce = ms;
            return nonce;
        }
    
        public static string GenerateTimeStamp()
        {
            var timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0);
            var timestamp = Convert.ToInt64(timeSpan.TotalSeconds).ToString(CultureInfo.InvariantCulture);
            return timestamp;
        }
    
        private static string GetNormalizedUrl(string uriScheme, string hostName, string requestPath)
        {
            var normalizedUrl = string.Format(
                "{0}://{1}{2}",
                uriScheme.ToLowerInvariant(),
                hostName.ToLowerInvariant(),
                requestPath);
    
            return normalizedUrl;
        }
    
        private static string NormalizeParameters(IList<QueryParameter> parameters)
        {
            var result = new StringBuilder();
    
            for (var i = 0; i < parameters.Count; i++)
            {
                var p = parameters[i];
    
                result.AppendFormat("{0}={1}", p.Name, p.Value);
    
                if (i < parameters.Count - 1)
                {
                    result.Append("&");
                }
            }
    
            return result.ToString();
        }
    
        private static byte[] StringToAscii(string s)
        {
            var retval = new byte[s.Length];
            for (var ix = 0; ix < s.Length; ++ix)
            {
                var ch = s[ix];
                if (ch <= 0x7f)
                {
                    retval[ix] = (byte)ch;
                }
                else
                {
                    retval[ix] = (byte)'?';
                }
            }
    
            return retval;
        }
    
        private static string UrlEncode(string value)
        {
            const string Unreserved = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
    
            var result = new StringBuilder();
    
            foreach (char symbol in value)
            {
                if (Unreserved.IndexOf(symbol) != -1)
                {
                    result.Append(symbol);
                }
                else
                {
                    result.Append('%' + string.Format("{0:X2}", (int)symbol));
                }
            }
    
            return result.ToString();
        }
    }
    
    public class QueryParameter
    {
        public QueryParameter(string name, string value)
        {
            this.Name = name;
            this.Value = value;
        }
    
        public string Name { get; private set; }
    
        public string Value { get; private set; }
    }
    
    public class QueryParameterComparer : IComparer<QueryParameter>
    {
        public int Compare(QueryParameter x, QueryParameter y)
        {
            return x.Name == y.Name
                ? string.Compare(x.Value, y.Value)
                : string.Compare(x.Name, y.Name);
        }
    }
    

    【讨论】:

      【解决方案4】:

      由于您使用的是.Result.Waitawait,这最终会在您的代码中导致死锁

      您可以在async 方法中使用ConfigureAwait(false)防止死锁

      像这样:

      var response = await httpClient.PostAsync(RequestTokenUrl, content).ConfigureAwait(false);
      

      您可以尽可能使用ConfigureAwait(false) 来表示不要阻塞异步代码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-04
        • 2013-11-15
        • 2021-11-10
        相关资源
        最近更新 更多