适用于UWP Windows 10应用程序的Google Calendar API发生了一个或多个错误

时间:2016-11-11 00:07:19

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

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

  

类型' System.AggregateException'的例外情况发生在mscorlib.ni.dll但未在用户代码中处理   附加信息:发生了一个或多个错误。

我不知道它为什么会这样做,它也应该有效。这是一个截图: Exception

我的代码也是:

 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"。

感谢您的关注。

2 个答案:

答案 0 :(得分:5)

Google API客户端库for .NET目前不支持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

获得访问令牌后,我们可以调用Calendar 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
        }
    }
}

答案 1 :(得分:0)

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

此外,您还必须自己获取auth令牌。不需要太多工作,并且Drive API和Calendar API可以正常工作(我尝试过的唯一方法)。您可以看到,我将一个包含auth令牌和其他auth详细信息的简单类传递给名为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令牌,您必须使用自定义方案。在google服务控制台上将您的应用注册为“ 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;
        }
    }