2013-12-22 73 views
1

我有一個Windows Phone 8應用程序,我在呼叫等待HttpClient.PostAsync,它永遠不會返回結果。它只是坐在那裏,掛起。如果我從控制檯應用程序運行完全相同的代碼,它幾乎立即返回結果。所有做這項工作的代碼都駐留在一個可移植的類庫中。我將不勝感激您的任何幫助。我發現在這個狀態下使用的所有其他問題都等待client.PostAsync,我已經在這樣做。WP8 HttpClient.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 
    } 
} 

回答

4

這條線被阻塞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<T>.Result不是異步的。

+0

非常感謝!就是這樣。我在異步/等待方面還是比較新的。 –

1

你可以發佈CreateSignature和StringToAscii代碼嗎?我被困在基本的oAuth中。

我可以獲取請求令牌,但在執行訪問令牌請求時收到「無效的OAuth簽名」。

+0

請參閱下面的完整答案。作爲評論太久沒有留下。 –

+0

謝謝。我會讓你知道它是否適合我。 – NoobDeveloper

1

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 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~"; 

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

由於您使用.Result.Waitawait這最終會在你的代碼引起僵局

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

這樣的:

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

可以使用ConfigureAwait(false)都儘量不要堵住異步代碼。