【问题标题】:Google Calendar API for UWP Windows 10 Application One or more errors occurred适用于 UWP Windows 10 应用程序的 Google Calendar API 发生一个或多个错误
【发布时间】:2017-03-25 03:57:06
【问题描述】:

我正在尝试使用 Google Calendar API v3,但在运行代码时遇到问题,它总是给我这个错误:

“System.AggregateException”类型的异常发生在 mscorlib.ni.dll 中,但未在用户代码中处理 附加信息:发生了一个或多个错误。

我不知道为什么会这样,它也应该可以工作。这是它的屏幕截图:

我的代码也是:

 UserCredential credential;
                credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                   new Uri("ms-appx:///Assets/client_secrets.json"),
                    Scopes,
                    "user",
                    CancellationToken.None).Result;


            // Create Google Calendar API service.
            var service = new CalendarService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = ApplicationName,
            });

        var calendarService = new CalendarService(new BaseClientService.Initializer
        {
            HttpClientInitializer = credential,
            ApplicationName = "Windows 10 Calendar sample"
        });
        var calendarListResource = await calendarService.CalendarList.List().ExecuteAsync();

如果您至少可以帮助通过 REST API 调用它,那也很好,但您必须考虑到它是 UWP,所以它还有另一种方法可以让它工作。 正如我已经通过 REST API 尝试过的,但我总是得到“请求错误代码 400”。

感谢您的关注。

【问题讨论】:

    标签: c# uwp google-calendar-api windows-10-universal


    【解决方案1】:

    我的 UWP 应用中有 Google .NET 客户端。诀窍是您必须将其放入 .NET Standard 2.0 类库中,公开您需要的 API 服务,然后从您的 UWP 应用程序中引用该库。

    此外,您必须自己处理获取身份验证令牌。这不是很多工作,Drive API 和 Calendar API 工作得很好(我唯一尝试过的)。您可以看到,我将一个包含身份验证令牌和其他身份验证详细信息的简单类传递给了一个名为 Initialize 的方法。

    这是我在 .NET Standard 2.0 类库中使用的单个类:

    namespace GoogleProxy
    {
    public class GoogleService
    {
        public CalendarService calendarService { get; private set; }
    
        public DriveService driveService { get; private set; }
    
    
        public GoogleService()
        {
    
        }
    
        public void Initialize(AuthResult authResult)
        {
            var credential = GetCredentialForApi(authResult);
            var baseInitializer = new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = "{your app name here}" };
    
            calendarService = new Google.Apis.Calendar.v3.CalendarService(baseInitializer);
            driveService = new Google.Apis.Drive.v3.DriveService(baseInitializer);
        }
    
        private UserCredential GetCredentialForApi(AuthResult authResult)
        {
            var initializer = new GoogleAuthorizationCodeFlow.Initializer
            {
                ClientSecrets = new ClientSecrets
                {
                    ClientId = "{your app client id here}",
                    ClientSecret = "",
                },
                Scopes = new string[] { "openid", "email", "profile",  "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar.events.readonly", "https://www.googleapis.com/auth/drive.readonly" },
            };
    
            var flow = new GoogleAuthorizationCodeFlow(initializer);
    
            var token = new TokenResponse()
            {
                AccessToken = authResult.AccessToken,
                RefreshToken = authResult.RefreshToken,
                ExpiresInSeconds = authResult.ExpirationInSeconds,
                IdToken = authResult.IdToken,
                IssuedUtc = authResult.IssueDateTime,
                Scope = "openid email profile https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events.readonly https://www.googleapis.com/auth/drive.readonly",
                TokenType = "bearer" };
    
            return new UserCredential(flow, authResult.Id, token);
        }
    
    }
    }
    

    为了从 google 获取 Auth 令牌,您必须使用自定义方案。在谷歌服务控制台上将您的应用程序注册为“iOS”应用程序并放入 URI 方案(独特的东西)。然后将此方案添加到声明->协议下的 UWP 清单中。在您的 App.xaml.cs 中处理它:

    protected override void OnActivated(IActivatedEventArgs args)
        {
            base.OnActivated(args);
            if (args.Kind == ActivationKind.Protocol)
            {
                ProtocolActivatedEventArgs protocolArgs = (ProtocolActivatedEventArgs)args;
                Uri uri = protocolArgs.Uri;
                Debug.WriteLine("Authorization Response: " + uri.AbsoluteUri);
                locator.AccountsService.GoogleExternalAuthWait.Set(uri.Query);
            }
        }
    

    GoogleExternalAuthWait 来自我发现的一些关于如何创建异步 ManualResetEvent 的神奇代码。 https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-1-asyncmanualresetevent/ 是这个样子的(我只转通用的)。

        public class AsyncManualResetEvent<T>
    {
        private volatile TaskCompletionSource<T> m_tcs = new TaskCompletionSource<T>();
    
        public Task<T> WaitAsync() { return m_tcs.Task; }
    
        public void Set(T TResult) { m_tcs.TrySetResult(TResult); }
    
        public bool IsReset => !m_tcs.Task.IsCompleted;
    
        public void Reset()
        {
            while (true)
            {
                var tcs = m_tcs;
                if (!tcs.Task.IsCompleted ||
                    Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<T>(), tcs) == tcs)
                    return;
            }
        }
    }
    

    这就是您启动 Google 授权的方式。发生的情况是它启动一个外部浏览器以开始 google 签名过程,然后等待(这就是 AsyncManualResetEvent 所做的)。完成后,Google 将使用您的自定义方案启动一个 URI。您应该会收到一个消息对话框,说明浏览器正在尝试打开应用程序...单击确定,AsyncManualResetEvent 继续并完成身份验证过程。您需要创建一个包含所有身份验证信息的类以传递给您的类库。

    private async Task<AuthResult> AuthenticateGoogleAsync()
        {
            try
            {
                var stateGuid = Guid.NewGuid().ToString();
                var expiration = DateTimeOffset.Now;
                var url = $"{GoogleAuthorizationEndpoint}?client_id={WebUtility.UrlEncode(GoogleAccountClientId)}&redirect_uri={WebUtility.UrlEncode(GoogleRedirectURI)}&state={stateGuid}&scope={WebUtility.UrlEncode(GoogleScopes)}&display=popup&response_type=code";
    
                var success = Windows.System.Launcher.LaunchUriAsync(new Uri(url));
    
                GoogleExternalAuthWait = new AsyncManualResetEvent<string>();
    
                var query = await GoogleExternalAuthWait.WaitAsync();
    
                var dictionary = query.Substring(1).Split('&').ToDictionary(x => x.Split('=')[0], x => Uri.UnescapeDataString(x.Split('=')[1]));
                if (dictionary.ContainsKey("error"))
                {
                    return null;
                }
                if (!dictionary.ContainsKey("code") || !dictionary.ContainsKey("state"))
                {
                    return null;
                }
                if (dictionary["state"] != stateGuid)
                    return null;
    
                string tokenRequestBody = $"code={dictionary["code"]}&redirect_uri={Uri.EscapeDataString(GoogleRedirectURI)}&client_id={GoogleAccountClientId}&access_type=offline&scope=&grant_type=authorization_code";
    
                StringContent content = new StringContent(tokenRequestBody, Encoding.UTF8, "application/x-www-form-urlencoded");
    
    
                // Performs the authorization code exchange.
                using (HttpClientHandler handler = new HttpClientHandler())
                {
                    handler.AllowAutoRedirect = true;
                    using (HttpClient client = new HttpClient(handler))
                    {
                        HttpResponseMessage response = await client.PostAsync(GoogleTokenEndpoint, content);
                        if (response.IsSuccessStatusCode)
                        {
    
                            var stringResponse = await response.Content.ReadAsStringAsync();
                            var json = JObject.Parse(stringResponse);
                            var id = DecodeIdFromJWT((string)json["id_token"]);
                            var oauthToken = new AuthResult()
                            {
                                Provider = AccountType.Google,
                                AccessToken = (string)json["access_token"],
                                Expiration = DateTimeOffset.Now + TimeSpan.FromSeconds(int.Parse((string)json["expires_in"])),
                                Id = id,
                                IdToken = (string)json["id_token"],
                                ExpirationInSeconds = long.Parse((string)json["expires_in"]),
                                IssueDateTime = DateTime.Now,
                                RefreshToken = (string)json["refresh_token"]
                            };
                            return oauthToken;
                        }
                        else
                        {
                            return null;
                        }
    
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
                return null;
            }
        }
    

    【讨论】:

      【解决方案2】:

      .NET 的 Google API 客户端库目前不支持 UWP。所以我们现在不能在 UWP 应用程序中使用Google.Apis.Calendar.v3 Client Library。更多信息,请查看类似问题:Universal Windows Platform App with google calendar

      要在 UWP 中使用 Google Calendar API,我们可以通过 REST API 调用它。要使用 REST API,我们需要先授权请求。如何授权请求请见Authorizing Requests to the Google Calendar APIUsing OAuth 2.0 for Mobile and Desktop Applications

      获得访问令牌后,我们可以像下面这样调用日历 API:

      var clientId = "{Your Client Id}";
      var redirectURI = "pw.oauth2:/oauth2redirect";
      var scope = "https://www.googleapis.com/auth/calendar.readonly";
      var SpotifyUrl = $"https://accounts.google.com/o/oauth2/auth?client_id={clientId}&redirect_uri={Uri.EscapeDataString(redirectURI)}&response_type=code&scope={Uri.EscapeDataString(scope)}";
      var StartUri = new Uri(SpotifyUrl);
      var EndUri = new Uri(redirectURI);
      
      // Get Authorization code
      WebAuthenticationResult WebAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, StartUri, EndUri);
      if (WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
      {
          var decoder = new WwwFormUrlDecoder(new Uri(WebAuthenticationResult.ResponseData).Query);
          if (decoder[0].Name != "code")
          {
              System.Diagnostics.Debug.WriteLine($"OAuth authorization error: {decoder.GetFirstValueByName("error")}.");
              return;
          }
      
          var autorizationCode = decoder.GetFirstValueByName("code");
      
      
          //Get Access Token
          var pairs = new Dictionary<string, string>();
          pairs.Add("code", autorizationCode);
          pairs.Add("client_id", clientId);
          pairs.Add("redirect_uri", redirectURI);
          pairs.Add("grant_type", "authorization_code");
      
          var formContent = new Windows.Web.Http.HttpFormUrlEncodedContent(pairs);
      
          var client = new Windows.Web.Http.HttpClient();
          var httpResponseMessage = await client.PostAsync(new Uri("https://www.googleapis.com/oauth2/v4/token"), formContent);
          if (!httpResponseMessage.IsSuccessStatusCode)
          {
              System.Diagnostics.Debug.WriteLine($"OAuth authorization error: {httpResponseMessage.StatusCode}.");
              return;
          }
      
          string jsonString = await httpResponseMessage.Content.ReadAsStringAsync();
          var jsonObject = Windows.Data.Json.JsonObject.Parse(jsonString);
          var accessToken = jsonObject["access_token"].GetString();
      
      
          //Call Google Calendar API
          using (var httpRequest = new Windows.Web.Http.HttpRequestMessage())
          {
              string calendarAPI = "https://www.googleapis.com/calendar/v3/users/me/calendarList";
      
              httpRequest.Method = Windows.Web.Http.HttpMethod.Get;
              httpRequest.RequestUri = new Uri(calendarAPI);
              httpRequest.Headers.Authorization = new Windows.Web.Http.Headers.HttpCredentialsHeaderValue("Bearer", accessToken);
      
              var response = await client.SendRequestAsync(httpRequest);
      
              if (response.IsSuccessStatusCode)
              {
                  var listString = await response.Content.ReadAsStringAsync();
                  //TODO
              }
          }
      }
      

      【讨论】:

      • 感谢您的回复,但我的 WebAuthenticationResult 出现错误,这是一张图片:picture here 是否有类似“WebBrowser”或 OpenQA.Selenium.Remote DriverOne for Chrome 或 FireFox ?如果不是这个错误我该怎么办? @Jay Zuo
      • @GameHackerPM 您看到的对话框更像是加载登录页面的“WebBrowser”。如果您看到此错误,请确保您使用的StartUri 是正确的。有时如果 url 不正确,您可能会收到此错误。如果 url 正确,您可以检查您的网络连接或稍后重试,就像错误提示的那样。这应该是偶尔的情况。
      • 佐,是的,你是对的,我明白了。只是你发送的数据有点问题,我只是改变了:[" var redirectURI = "pw.oauth2:/oauth2redirect"; "] 到 [" var redirectURI = "localhost/urn:ietf:wg:oauth:2.0:oob"; "],这使得它工作得很好。非常感谢您的帮助!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-03-10
      • 1970-01-01
      • 1970-01-01
      • 2017-04-19
      • 2016-10-16
      • 2017-08-15
      相关资源
      最近更新 更多