API Календаря Google для приложения Windows 10 UWP Произошла одна или несколько ошибок

Я пытаюсь использовать 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".

Спасибо за внимание.

1 ответ

Решение

Клиентская библиотека Google API для.NET на данный момент не поддерживает UWP. Поэтому мы не можем сейчас использовать клиентскую библиотеку Google.Apis.Calendar.v3 в приложениях UWP. Для получения дополнительной информации см. Аналогичный вопрос: Универсальное приложение для платформы Windows с календарем Google.

Чтобы использовать Google Calendar API в UWP, мы можем вызвать его через REST API. Чтобы использовать REST API, нам нужно сначала авторизовать запросы. Инструкции по авторизации запросов см. В разделе " Авторизация запросов к API Календаря Google" и " Использование OAuth 2.0 для мобильных и настольных приложений".

После того, как у нас есть токен доступа, мы можем вызвать 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
        }
    }
}

В моем приложении 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);
    }

}
}

Чтобы получить токен Auth от Google, вы должны использовать пользовательские схемы. Зарегистрируйте свое приложение как приложение "iOS" в консоли служб Google и вставьте схему 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;
        }
    }
Другие вопросы по тегам