Google Calendar API for UWP Windows 10 Application One or more errors occurred

前端 未结 2 451
失恋的感觉
失恋的感觉 2021-01-16 14:44

I\'m trying to use Google Calendar API v3, but i have problems while running the codes, it always gives me that error :

An exception of type \'System

相关标签:
2条回答
  • 2021-01-16 15:29

    I have the Google .NET Client working in my UWP app. The trick is that you have to put it in a .NET Standard 2.0 Class Library, expose the API services you need, and then reference that library from your UWP app.

    Also, you have to handle the getting the auth token yourself. It's not that much work and the Drive APIs and Calendar APIs work just fine (the only ones I've tried). You can see that I pass in a simple class that contains the auth token and other auth details to a method called Initialize.

    Here is the single class I used in the .NET Standard 2.0 class library:

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

    In order to get the Auth token from google, you have to use custom schemes. Register your app as an 'iOS' app on the google services console and put in a URI scheme (something unique). Then add this scheme to your UWP manifest under Declarations->Protocol. Handle it in your 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);
            }
        }
    

    That GoogleExternalAuthWait comes from some magical code I found about how to create an asynchronous ManualResetEvent. https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-1-asyncmanualresetevent/ It looks like this (I only converted it to generic).

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

    This is how you start the Google Authorization. What happens is it launches an external browser to begin the google signing process and then wait (that's what the AsyncManualResetEvent does). When you're done, Google will launch a URI using your custom scheme. You should get a message dialog saying the browser is trying to open an app... click ok and the AsyncManualResetEvent continues and finishes the auth process. You'll need to make a class that contains all the auth info to pass to your class library.

    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;
            }
        }
    
    0 讨论(0)
  • 2021-01-16 15:30

    The Google API Client Library for .NET does not support UWP by now. So we can't use Google.Apis.Calendar.v3 Client Library in UWP apps now. For more info, please see the similar question: Universal Windows Platform App with google calendar.

    To use Google Calendar API in UWP, we can call it through REST API. To use the REST API, we need to authorize requests first. For how to authorize requests, please see Authorizing Requests to the Google Calendar API and Using OAuth 2.0 for Mobile and Desktop Applications.

    After we have the access token, we can call Calendar API like following:

    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
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题