C#を使ってGoogle APIにアクセスする

Google APIには各種言語用クライアントライブラリが用意されていて、もちろんC#用のクライアントライブラリも存在します。ただ、情報が少なく、詰まるところもあったので、使い方をまとめておきます。環境は.NET Core 2.1およびVisual Studio Codeです。作成するアプリの形式は、コンソールアプリです。

# リンク
 - [C#クライアントライブラリのサイト](https://developers.google.com/api-client-library/dotnet/)
 - [C#クライアントライブラリのリポジトリ](https://github.com/google/google-api-dotnet-client/)
 - [サンプル集](https://github.com/google/google-api-dotnet-client-samples)
 - [APIリファレンス](https://developers.google.com/api-client-library/dotnet/reference/1.10.0/) ※バージョンが古いので注意

リポジトリを見ると、メンテンナンスモードと書かれているので不安になってしまいますが、FAQのページを見ると、「メンテナンスモード=成熟し、完成しているので機能追加はしない」とのことです。バグは修正されますし、新しいAPIが追加されれば対応すると明言されているので大丈夫そうです。

# 事前準備
 1. [Google Cloud Console](https://console.cloud.google.com/)にアクセスして、プロジェクトを作成します。
 2. サイドバーから「APIとサービス」を選択し、利用したいAPIを有効化します。
 3. 同じく「APIとサービス」から、認証情報を作成します。認証情報の種類については、記事（[Google APIの認証の種類](https://qiita.com/masaha03/items/fab8c8411a020ff2bd42)）を書きましたので、参考にしてください。作成したら、鍵ファイルをダウンロードしましょう。

# セットアップ
まずはプロジェクトを作成し、依存関係を追加します。ここでは例として、Calendar APIのクライアントライブラリを追加します。

```bash
dotnet new console
dotnet add package Google.Apis.Calendar.v3
```

以下のページで、サポートされているAPI一覧があります。バージョンナンバーをクリックすると、詳細が見られます。

 - [Supported Google APIs](https://developers.google.com/api-client-library/dotnet/apis/)

なお、クライアントライブラリ共通のAPIは`Google.Apis`パッケージ、認証のAPIは`Google.Apis.Auth`パッケージに独立しており、依存関係としてインストールされます。複数APIのクライアントライブラリを追加する場合は、これらを明示的に追加しておいたほうが、バージョン管理が楽かもしれません。

# 鍵ファイルの追加
ダウンロードした鍵ファイルを、プロジェクトのルートに置きます。間違って公開しないよう、注意しましょう。本番では、環境変数で渡すなど、セキュアな方法を使ってください。

# 認可の取得
C#のクライアントライブラリでは、`ICredential`を実装したクラスが、取得した認可情報を保持することになっています。この`ICredential`のインスタンスを使って各APIにアクセスすることになります。

## サービスアカウントの場合

```cs
using (var stream = new FileStream("service_account_key.json", FileMode.Open, FileAccess.Read))
{
    ICredential credential = GoogleCredential.FromStream(stream)
         .CreateScoped(new[] { CalendarService.Scope.CalendarReadonly }).UnderlyingCredential;
}
```

`service_account_key.json`が、サービスアカウントの鍵ファイルです。

scopeは、認可を得たい権限です。APIごとに設定されています。Calendar APIの場合は、[calendar、calendar.readonly](https://developers.google.com/calendar/auth)の二つのscopeがあります。C#のクライアントライブラリでは、各API用クライアントライブラリに定数として登録されています。

## OAuth2認証の場合

```cs
using (var stream = new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
{
    string credPath = "token"; // tokenを保存するディレクトリ
    Task<UserCredential> credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
        GoogleClientSecrets.Load(stream).Secrets,
        new[] { CalendarService.Scope.CalendarReadonly },
        "user", CancellationToken.None, new FileDataStore(credPath, true));  // 第二引数をtrueにすると、カレントディレクトリからの相対パスに保存される
}
```

`client_secret.json`が、OAuth2認証の鍵ファイルです。

実行すると、おなじみのGoogleのOAuth認可ページがブラウザで開きます。ユーザーアクションを待つ必要があるので、Taskが返ります。`UserCredential`は、もちろん`ICredential`を実装したクラスです。

# サンプル
あとは、取得した`ICredential`のインスタンスを使って、各APIにアクセスするだけです。
以下は、Calendar APIにアクセスするサンプルです。リポジトリは[こちら](https://github.com/masaha03/csharp-googleapi)です。

```cs
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;

namespace csharp_googleapi
{
    class Program
    {
        static void Main(string[] args)
        {
            new Program().Run().Wait();
        }

        private ICredential GetServiceAccountCredential(string[] scopes)
        {
            using (var stream = new FileStream("service_account_key.json", FileMode.Open, FileAccess.Read))
            {
                return GoogleCredential.FromStream(stream)
                     .CreateScoped(scopes).UnderlyingCredential;
            }
        }

        private Task<UserCredential> GetUserCredential(string[] scopes)
        {
            using (var stream = new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
            {
                string credPath = "token.json";
                return GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.Load(stream).Secrets,
                    scopes,
                    "user", CancellationToken.None, new FileDataStore(credPath, true));
            }
        }

        private async Task Run()
        {
            var scopes = new[] { CalendarService.Scope.CalendarReadonly };
            // ICredential credential = GetServiceAccountCredential(scopes);
            ICredential credential = await GetUserCredential(scopes);
            GetCalendar(credential, "(カレンダーID)"); // アクセスしたいカレンダーのID
        }

        private void GetCalendar(ICredential credential, string calendarId)
        {
            var service = new CalendarService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "Get Calendar Sample",
            });

            EventsResource.ListRequest request = service.Events.List(calendarId);
            request.TimeMin = DateTime.Now;
            request.ShowDeleted = false;
            request.SingleEvents = true;
            request.MaxResults = 10;
            request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;

            // List events.
            Events events = request.Execute();
            Console.WriteLine("Upcoming events:");
            if (events.Items != null && events.Items.Count > 0)
            {
                foreach (var eventItem in events.Items)
                {
                    string when = eventItem.Start.DateTime.ToString();
                    if (String.IsNullOrEmpty(when))
                    {
                        when = eventItem.Start.Date;
                    }
                    Console.WriteLine("{0} ({1})", eventItem.Summary, when);
                }
            }
            else
            {
                Console.WriteLine("No upcoming events found.");
            }
        }
    }
}