maxfie1d のブログ

マイクロソフト系技術ネタを中心に書きます。

C#と Microsoft Graph の API で Microsoft To-Do のタスクを取得する

Microsoft Graph の API ではいろんな情報が取得できて、 Outlook のタスクも取得できます。

Micorosoft To-Do のタスクは Outlook のタスクと同一

とのことなので、実はこのAPI経由で MS To-Do のタスクを取得できます。

一つ注意で、Outlook のタスクは v1.0API では取得できず、 betaのみになります。なので、公式で配布されているライブラリだと 対応していないので少し工夫が必要になります。

下準備

  1. UWP のプロジェクトを作成します
  2. Nuget で以下のパッケージを追加します
    • Microsoft.Identity.Client (プレリリースを含めるにチェックを入れないと表示されません)
    • Newtonsoft.Json

さらに Application Registration Portal(https://apps.dev.microsoft.com/)にアクセスして アプリケーションを登録していきます。

「Converged applications」の方の「Add an app」ボタンを押します。 適当にアプリ名を決めて、「Create」ボタンを押します。 f:id:maxfieldwalker:20180125100818p:plain

次のページに移ったら、

  • Application Id をメモする (abcdefgh-fbfb-4896-88f5-ee608a7ad6e2 みたいなの)
  • 「Add Platform」ボタンを押して、「Native Application」を追加
  • Microsoft Graph Permissions の Delegated Permissions の「Add」ボタンを押して「Tasks.Read」を追加

をして、最後にページ最後の「Save」ボタンを押します。

Your changes were saved. と出たら完了です。

コードを書いていく

MsGraphClient.cs をこんな感じで書きます。 applicationIdの部分は自分で取得したものに置き換えてください。

using Microsoft.Identity.Client;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;

namespace BlogProject1
{
    public class MSGraphClient
    {
        /// <summary>
        /// Application Registration Portal で表示される Application Id
        /// </summary>
        static string applicationId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

        /// <summary>
        /// アクセス範囲
        /// </summary>
        static string[] Scopes = { "User.Read", "Tasks.Read" };

        static PublicClientApplication IdentityClientApp = new PublicClientApplication(applicationId);
        static string TokenForUser = null;
        static DateTimeOffset Expiration;

        public static async Task<TasksResponse> GetTodos()
        {
            var response = await SendRequest("/me/outlook/tasks");
            string s = await response.Content.ReadAsStringAsync();
            var tasksResponse = JsonConvert.DeserializeObject<TasksResponse>(s);
            return tasksResponse;
        }

        static async Task<HttpResponseMessage> SendRequest(string path)
        {
            const string endpoint = "https://graph.microsoft.com/beta";
            string url = endpoint + path;
            using (var request = new HttpRequestMessage(new HttpMethod("GET"), url))
            {
                await AuthenticateRequest(request).ConfigureAwait(false);
                var client = new HttpClient();
                var response = await client.SendAsync(request).ConfigureAwait(false);
                return response;
            }
        }

        static async Task AuthenticateRequest(HttpRequestMessage request)
        {
            var token = await GetTokenForUserAsync();
            // 認証用トークンをヘッダに追加する
            request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token);
        }

        static async Task<string> GetTokenForUserAsync()
        {
            try
            {
                var authResult = await IdentityClientApp.AcquireTokenSilentAsync(Scopes, IdentityClientApp.Users.First());
                TokenForUser = authResult.AccessToken;
            }
            catch
            {
                // トークンを未取得か、5分以内に期限切れになるならトークンを取得する
                if (TokenForUser == null || Expiration <= DateTimeOffset.UtcNow.AddMinutes(5))
                {
                    var authResult = await IdentityClientApp.AcquireTokenAsync(Scopes);

                    TokenForUser = authResult.AccessToken;
                    Expiration = authResult.ExpiresOn;
                }
            }

            return TokenForUser;
        }
    }
}

JSONでデータが返ってくるので 型を定義しておくとデシリアライズしてくれます。

using Newtonsoft.Json;

namespace BlogProject1
{
    public class TasksResponse
    {
        [JsonProperty("@odata.nextLink")]
        public string NextLink { get; set; }


        [JsonProperty("value")]
        public TaskEntity[] tasks { get; set; }
    }

    public class TaskEntity
    {
        [JsonProperty("subject")]
        public string Subject { get; set; }
    }
}

画面にボタンを一つ作って、クリックハンドラを以下のようにします。

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var response = await MSGraphClient.GetTodos();
            var subjects = response.tasks.Select(x => x.Subject);
            foreach (var s in subjects)
            {
                Debug.WriteLine(s);
            }
        }

実行してみる

Todo の取得をしようとすると、初回のみMSアカウントにサインインするよう求められます。 メールとパスワードを入力してサインインします。 f:id:maxfieldwalker:20180125103111p:plain

アプリに権限を与えていいか聞かれます。ちゃんと登録したアプリ名が表示されています。 f:id:maxfieldwalker:20180125103424p:plain

認証が完了するとデバッグ出力のところに最近登録したTodoが表示されます。(一つもTodoがなければ何も表示されないので注意してください)

f:id:maxfieldwalker:20180125103724p:plain