Microsoft アプリケーションを使用して Blazor WebAssembly アプリをGraph
このチュートリアルでは、Microsoft Graph API を使用してユーザーの予定表情報を取得するクライアント側の Blazor WebAssembly アプリを構築する方法について説明します。
ヒント
完了したチュートリアルをダウンロードする場合は、リポジトリをダウンロードまたは複製GitHubできます。 アプリ ID とシークレットを使用してアプリを構成する手順については、デモ フォルダーの README ファイルを参照してください。
前提条件
このチュートリアルを開始する前に、開発マシン に .NET SDK がインストールされている必要があります。 SDK がない場合は、前のリンクにアクセスしてダウンロード オプションを確認してください。
また、Outlook.com 上のメールボックスを持つ個人用 Microsoft アカウント、または Microsoft の仕事用または学校用のアカウントを持っている必要があります。 Microsoft アカウントをお持ちでない場合は、無料アカウントを取得するためのオプションが 2 つご利用できます。
- 新しい 個人用 Microsoft アカウントにサインアップできます。
- 開発者プログラムにサインアップして、Microsoft 365サブスクリプションをMicrosoft 365できます。
注意
このチュートリアルは、.NET SDK バージョン 5.0.302 で記述されています。 このガイドの手順は、他のバージョンでも動作しますが、テストされていない場合があります。
フィードバック
このチュートリアルに関するフィードバックは、リポジトリのGitHubしてください。
Blazor WebAssembly アプリを作成する
まず、Blazor WebAssembly アプリを作成します。
プロジェクトを作成するディレクトリでコマンド ライン インターフェイス (CLI) を開きます。 次のコマンドを実行します。
dotnet new blazorwasm --auth SingleOrg -o GraphTutorial
この
--auth SingleOrg
パラメーターを使用すると、生成されたプロジェクトに、認証用の構成が含Microsoft ID プラットフォーム。プロジェクトが作成されると、現在のディレクトリを GraphTutorial ディレクトリに変更し、CLI で次のコマンドを実行して動作します。
dotnet watch run
ブラウザーを開き、を参照します
https://localhost:5001
。 すべてが機能している場合は、"Hello, world! メッセージ。
重要
localhost の証明書が信頼されていないという警告を受け取った場合は、.NET Core CLI を使用して開発証明書をインストールして信頼できます。 特定のオペレーティング システムの手順については、「ASP.NET Coreで HTTPS を適用する」を参照してください。
NuGet パッケージを追加する
次に進む前に、後で使用NuGet追加のパッケージをインストールします。
- Microsoft.Graph: Microsoft Graph を呼び出すためのものです。
- タイム ゾーン識別子を IANA 識別子Windows変換する TimeZoneConverter。
依存関係をインストールするには、CLI で次のコマンドを実行します。
dotnet add package Microsoft.Graph --version 4.1.0 dotnet add package TimeZoneConverter
アプリを設計する
このセクションでは、アプリケーションの基本的な UI 構造を作成します。
テンプレートによって生成されたサンプル ページを削除します。 次のファイルを削除します。
- ./Pages/Counter.razor
- ./Pages/FetchData.razor
- ./Shared/SurveyPrompt.razor
- ./wwwroot/sample-data/weather.json
./wwwroot/index.htmlを開 き、終了タグの直前に次 のコード を追加
</body>
します。<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
これにより、 ブートストラップ javascript ファイルが追加されます。
./wwwroot/css/app.css を開 き、次のコードを追加します。
.nav-profile-photo { width: 36px; }
./Shared/NavMenu.razor を開き、 その内容を次に置き換えます。
<div class="top-row pl-4 navbar navbar-dark"> <a class="navbar-brand" href="">GraphTutorial</a> <button class="navbar-toggler" @onclick="ToggleNavMenu"> <span class="navbar-toggler-icon"></span> </button> </div> <div class="@NavMenuCssClass" @onclick="ToggleNavMenu"> <ul class="nav flex-column"> <li class="nav-item px-3"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> <span class="oi oi-home" aria-hidden="true"></span> Home </NavLink> </li> <li class="nav-item px-3"> <NavLink class="nav-link" href="calendar"> <span class="oi oi-calendar" aria-hidden="true"></span> Calendar </NavLink> </li> </ul> </div> @code { private bool collapseNavMenu = true; private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; private void ToggleNavMenu() { collapseNavMenu = !collapseNavMenu; } }
./Pages/Index.razor を開き、その内容を次に置き換えます。
@page "/" <div class="jumbotron"> <h1>Blazor Client-side Graph Tutorial</h1> <p class="lead">This sample app shows how to use the Microsoft Graph API to access a user's data from a Blazor client-side app</p> <AuthorizeView> <Authorized> <h4>Welcome @context.User.Identity.Name!</h4> <p>Use the navigation bar on the left to get started.</p> </Authorized> <NotAuthorized> <a class="btn btn-primary btn-large" href="authentication/login">Click here to sign in</a> </NotAuthorized> </AuthorizeView> </div>
./Shared/LoginDisplay.razor を開き、 その内容を次に置き換えます。
@using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @inject NavigationManager Navigation @inject SignOutSessionStateManager SignOutManager <AuthorizeView> <Authorized> <a class="text-decoration-none" data-toggle="dropdown" href="#" role="button"> <img src="/img/no-profile-photo.png" class="nav-profile-photo rounded-circle align-self-center mr-2"> </a> <div class="dropdown-menu dropdown-menu-right"> <h5 class="dropdown-item-text mb-0">@context.User.Identity.Name</h5> <p class="dropdown-item-text text-muted mb-0">placeholder@contoso.com</p> <div class="dropdown-divider"></div> <button class="dropdown-item" @onclick="BeginLogout">Log out</button> </div> </Authorized> <NotAuthorized> <a href="authentication/login">Log in</a> </NotAuthorized> </AuthorizeView> @code{ private async Task BeginLogout(MouseEventArgs args) { await SignOutManager.SetSignOutState(); Navigation.NavigateTo("authentication/logout"); } }
img という名前の ./wwwroot ディレクトリに新しい ディレクトリを作成 します。 このディレクトリに、選択した名前 のno-profile-photo.pngを 追加します。 この画像は、ユーザーが Microsoft サーバーで写真を持ってない場合に、ユーザーの写真Graph。
ヒント
これらのスクリーンショットで使用されている画像は、次のページからGitHub。
すべての変更を保存し、ページを更新します。
ポータルでアプリを登録する
この演習では、管理者センターを使用して新Azure AD Web アプリケーション登録Azure Active Directoryします。
ブラウザーを開き、Azure Active Directory 管理センターへ移動します。 個人用アカウント (別名: Microsoft アカウント)、または 職場/学校アカウント を使用してログインします。
左側のナビゲーションで [Azure Active Directory] を選択し、それから [管理] で [アプリの登録] を選択します。
[新規登録] を選択します。 [アプリケーションを登録] ページで、次のように値を設定します。
Blazor Graph Tutorial
に [名前] を設定します。- [サポートされているアカウントの種類] を [任意の組織のディレクトリ内のアカウントと個人用の Microsoft アカウント] に設定します。
- [ リダイレクト URI] で、最初のドロップダウンを [単一ページ アプリケーション (SPA) に 設定し、値をに設定します
https://localhost:5001/authentication/login-callback
。
[登録] を選択します。 [Blazor Graph チュートリアル] ページで、アプリケーション (クライアント) ID の値をコピーして保存します。次の手順で必要になります。
Azure AD 認証を追加する
この演習では、前の演習からアプリケーションを拡張して、アプリケーションの認証をサポートAzure AD。 これは、Microsoft Graph API を呼び出すのに必要な OAuth アクセス トークンを取得するために必要です。
./wwwroot/appsettings.json を開きます。 プロパティを追加
GraphScopes
し、次に一Authority
致する値ClientId
と値を更新します。{ "AzureAd": { "Authority": "https://login.microsoftonline.com/common", "ClientId": "YOUR_APP_ID_HERE", "ValidateAuthority": true }, "GraphScopes": "User.Read;MailboxSettings.Read;Calendars.ReadWrite" }
アプリ
YOUR_APP_ID_HERE
登録のアプリケーション ID に置き換える。重要
git などのソース管理を使用している場合は、アプリ ID が誤って漏洩しないように、 appsettings.json ファイルをソース管理から除外する良い時期です。
値に含まれるスコープを確認
GraphScopes
します。- User.Read を使用すると、アプリケーションはユーザーのプロファイルと写真を取得できます。
- MailboxSettings.Read を使用すると、アプリケーションはユーザーの優先タイム ゾーンを含むメールボックス設定を取得できます。
- Calendars.ReadWrite を使用すると、アプリケーションはユーザーの予定表に対して読み取りおよび書き込みを行います。
サインインの実装
この時点で、.NET Core プロジェクト テンプレートにサインインを有効にするコードが追加されています。 ただし、このセクションでは、Microsoft Graph からユーザーの ID に情報を追加することで、エクスペリエンスを向上させるコードを追加します。
./Pages/Authentication.razor を開き、 その内容を次に置き換えます。
@page "/authentication/{action}" @using Microsoft.AspNetCore.Components.WebAssembly.Authentication <RemoteAuthenticatorView Action="@Action" LogInFailed="LogInFailedFragment" /> @code{ [Parameter] public string Action { get; set; } private static RenderFragment LogInFailedFragment(string message) { return builder => { builder.OpenElement(0, "div"); builder.AddAttribute(1, "class", "alert alert-danger"); builder.AddAttribute(2, "role", "alert"); builder.OpenElement(3, "p"); builder.AddContent(4, "There was an error trying to log you in."); builder.CloseElement(); if (!string.IsNullOrEmpty(message)) { builder.OpenElement(5, "p"); builder.AddContent(6, $"Error: {message}"); builder.CloseElement(); } builder.CloseElement(); }; } }
これにより、ログインが認証プロセスによって返されるエラー メッセージを表示できない場合に、既定のエラー メッセージが置き換わります。
プロジェクトのルートに新しいディレクトリを作成します。Graph。
GraphUserAccountFactory.cs という 名前の ./Graph ディレクトリに新しいファイルを作成し、次のコードを追加します。
using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; using Microsoft.Extensions.Logging; using Microsoft.Graph; namespace GraphTutorial.Graph { // Extends the AccountClaimsPrincipalFactory that builds // a user identity from the identity token. // This class adds additional claims to the user's ClaimPrincipal // that hold values from Microsoft Graph public class GraphUserAccountFactory : AccountClaimsPrincipalFactory<RemoteUserAccount> { private readonly IAccessTokenProviderAccessor accessor; private readonly ILogger<GraphUserAccountFactory> logger; public GraphUserAccountFactory(IAccessTokenProviderAccessor accessor, ILogger<GraphUserAccountFactory> logger) : base(accessor) { this.accessor = accessor; this.logger = logger; } public async override ValueTask<ClaimsPrincipal> CreateUserAsync( RemoteUserAccount account, RemoteAuthenticationUserOptions options) { // Create the base user var initialUser = await base.CreateUserAsync(account, options); // If authenticated, we can call Microsoft Graph if (initialUser.Identity.IsAuthenticated) { try { // Add additional info from Graph to the identity await AddGraphInfoToClaims(accessor, initialUser); } catch (AccessTokenNotAvailableException exception) { logger.LogError($"Graph API access token failure: {exception.Message}"); } catch (ServiceException exception) { logger.LogError($"Graph API error: {exception.Message}"); logger.LogError($"Response body: {exception.RawResponseBody}"); } } return initialUser; } private async Task AddGraphInfoToClaims( IAccessTokenProviderAccessor accessor, ClaimsPrincipal claimsPrincipal) { // TEMPORARY: Get the token and log it var result = await accessor.TokenProvider.RequestAccessToken(); if (result.TryGetToken(out var token)) { logger.LogInformation($"Access token: {token.Value}"); } } } }
このクラスは AccountClaimsPrincipalFactory クラスを拡張し、メソッドをオーバーライド
CreateUserAsync
します。 ここでは、このメソッドはデバッグ目的でのみアクセス トークンをログに記録します。 この演習では、後で Microsoft Graph呼び出しを実装します。./Program.cs を 開き、ファイル
using
の上部に次のステートメントを追加します。using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using GraphTutorial.Graph;
Inside
Main
では、既存の呼び出しを次builder.Services.AddMsalAuthentication
の呼び出しに置き換える。builder.Services.AddMsalAuthentication<RemoteAuthenticationState, RemoteUserAccount>(options => { var scopes = builder.Configuration.GetValue<string>("GraphScopes"); if (string.IsNullOrEmpty(scopes)) { Console.WriteLine("WARNING: No permission scopes were found in the GraphScopes app setting. Using default User.Read."); scopes = "User.Read"; } foreach(var scope in scopes.Split(';')) { Console.WriteLine($"Adding {scope} to requested permissions"); options.ProviderOptions.DefaultAccessTokenScopes.Add(scope); } builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); }) .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount, GraphUserAccountFactory>();
このコードの動作を検討します。
GraphScopes
appsettings.json から値を読み込み、MSAL プロバイダーによって使用される既定のスコープに各スコープを追加します。- 既存のアカウント ファクトリを GraphUserAccountFactory クラスに置き換 える。
変更内容を保存し、アプリを再起動します。 [ログイン ] リンクを使用 してログインします。 要求されたアクセス許可を確認して承諾します。
アプリがウェルカム メッセージで更新されます。 ブラウザーの開発者ツールにアクセスし、[コンソール] タブ を確認 します。アプリはアクセス トークンをログに記録します。
ユーザーの詳細情報を取得する
ユーザーがログインすると、Microsoft Graph からそのユーザーの情報を入手できます。 このセクションでは、Microsoft Graphの情報を使用して、ユーザーの ClaimsPrincipal にクレームを追加します。
GraphClaimsPrincipalExtensions.cs という 名前の ./Graph ディレクトリに新しいファイルを作成し、次のコードを追加します。
using Microsoft.Graph; using System; using System.IO; using System.Security.Claims; namespace GraphTutorial { public static class GraphClaimTypes { public const string DateFormat = "graph_dateformat"; public const string Email = "graph_email"; public const string Photo = "graph_photo"; public const string TimeZone = "graph_timezone"; public const string TimeFormat = "graph_timeformat"; } // Helper methods to access Graph user data stored in // the claims principal public static class GraphClaimsPrincipalExtensions { public static string GetUserGraphDateFormat(this ClaimsPrincipal claimsPrincipal) { var claim = claimsPrincipal.FindFirst(GraphClaimTypes.DateFormat); return claim == null ? null : claim.Value; } public static string GetUserGraphEmail(this ClaimsPrincipal claimsPrincipal) { var claim = claimsPrincipal.FindFirst(GraphClaimTypes.Email); return claim == null ? null : claim.Value; } public static string GetUserGraphPhoto(this ClaimsPrincipal claimsPrincipal) { var claim = claimsPrincipal.FindFirst(GraphClaimTypes.Photo); return claim == null ? null : claim.Value; } public static string GetUserGraphTimeZone(this ClaimsPrincipal claimsPrincipal) { var claim = claimsPrincipal.FindFirst(GraphClaimTypes.TimeZone); return claim == null ? null : claim.Value; } public static string GetUserGraphTimeFormat(this ClaimsPrincipal claimsPrincipal) { var claim = claimsPrincipal.FindFirst(GraphClaimTypes.TimeFormat); return claim == null ? null : claim.Value; } // Adds claims from the provided User object public static void AddUserGraphInfo(this ClaimsPrincipal claimsPrincipal, User user) { var identity = claimsPrincipal.Identity as ClaimsIdentity; identity.AddClaim( new Claim(GraphClaimTypes.DateFormat, user.MailboxSettings.DateFormat)); identity.AddClaim( new Claim(GraphClaimTypes.Email, user.Mail ?? user.UserPrincipalName)); identity.AddClaim( new Claim(GraphClaimTypes.TimeZone, user.MailboxSettings.TimeZone)); identity.AddClaim( new Claim(GraphClaimTypes.TimeFormat, user.MailboxSettings.TimeFormat)); } // Converts a photo Stream to a Data URI and stores it in a claim public static void AddUserGraphPhoto(this ClaimsPrincipal claimsPrincipal, Stream photoStream) { var identity = claimsPrincipal.Identity as ClaimsIdentity; if (photoStream != null) { // Copy the photo stream to a memory stream // to get the bytes out of it var memoryStream = new MemoryStream(); photoStream.CopyTo(memoryStream); var photoBytes = memoryStream.ToArray(); // Generate a date URI for the photo var photoUri = $"data:image/png;base64,{Convert.ToBase64String(photoBytes)}"; identity.AddClaim( new Claim(GraphClaimTypes.Photo, photoUri)); } } } }
このコードでは、ClaimsPrincipal クラスの拡張メソッドを実装し、Microsoft のオブジェクトオブジェクトの値を使用してクレームを取得および設定Graphします。
BlazorAuthProvider.cs という名前の ./Graph ディレクトリに新しいファイルを作成し、次のコードを追加します。
using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; using Microsoft.Graph; namespace GraphTutorial.Graph { public class BlazorAuthProvider : IAuthenticationProvider { private readonly IAccessTokenProviderAccessor accessor; public BlazorAuthProvider(IAccessTokenProviderAccessor accessor) { this.accessor = accessor; } // Function called every time the GraphServiceClient makes a call public async Task AuthenticateRequestAsync(HttpRequestMessage request) { // Request the token from the accessor var result = await accessor.TokenProvider.RequestAccessToken(); if (result.TryGetToken(out var token)) { // Add the token to the Authorization header request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); } } } }
このコードは、Microsoft.AspNetCore.Components.WebAssembly.Authentication パッケージによって提供される IAccessTokenProviderAccessor を使用してアクセス トークンを取得する Microsoft Graph SDK の認証プロバイダーを実装します。
GraphClientFactory.cs という名前の ./Graph ディレクトリに新しいファイルを作成し、次のコードを追加します。
using System.Net.Http; using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; using Microsoft.Extensions.Logging; using Microsoft.Graph; namespace GraphTutorial.Graph { public class GraphClientFactory { private readonly IAccessTokenProviderAccessor accessor; private readonly HttpClient httpClient; private readonly ILogger<GraphClientFactory> logger; private GraphServiceClient graphClient; public GraphClientFactory(IAccessTokenProviderAccessor accessor, HttpClient httpClient, ILogger<GraphClientFactory> logger) { this.accessor = accessor; this.httpClient = httpClient; this.logger = logger; } public GraphServiceClient GetAuthenticatedClient() { // Use the existing one if it's there if (graphClient == null) { // Create a GraphServiceClient using a scoped // HttpClient graphClient = new GraphServiceClient(httpClient); // Configure the auth provider graphClient.AuthenticationProvider = new BlazorAuthProvider(accessor); } return graphClient; } } }
このクラスは、BlazorAuthProvider で構成された GraphServiceClient を作成します。
./Program.cs を 開き、新しい HttpClient の BaseAddress を に変更 します
"https://graph.microsoft.com"
。// HttpClient for passing into GraphServiceClient constructor builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://graph.microsoft.com") });
行の前に次のコードを追加
await builder.Build().RunAsync();
します。builder.Services.AddScoped<GraphClientFactory>();
これにより、 GraphClientFactory をスコープ付きサービスとして追加し、依存関係の挿入によって利用できます。
./Graph/GraphUserAccountFactory.cs を開き、次のプロパティをクラスに追加します。
private readonly GraphClientFactory clientFactory;
GraphClientFactory パラメーターを取得し、プロパティに割り当てるコンストラクターを更新
clientFactory
します。public GraphUserAccountFactory(IAccessTokenProviderAccessor accessor, GraphClientFactory clientFactory, ILogger<GraphUserAccountFactory> logger) : base(accessor) { this.accessor = accessor; this.clientFactory = clientFactory; this.logger = logger; }
既存の
AddGraphInfoToClaims
関数を、以下の関数で置き換えます。private async Task AddGraphInfoToClaims( IAccessTokenProviderAccessor accessor, ClaimsPrincipal claimsPrincipal) { var graphClient = clientFactory.GetAuthenticatedClient(); // Get user profile including mailbox settings // GET /me?$select=displayName,mail,mailboxSettings,userPrincipalName var user = await graphClient.Me .Request() // Request only the properties used to // set claims .Select(u => new { u.DisplayName, u.Mail, u.MailboxSettings, u.UserPrincipalName }) .GetAsync(); logger.LogInformation($"Got user: {user.DisplayName}"); claimsPrincipal.AddUserGraphInfo(user); // Get user's photo // GET /me/photos/48x48/$value var photo = await graphClient.Me .Photos["48x48"] // Smallest standard size .Content .Request() .GetAsync(); claimsPrincipal.AddUserGraphPhoto(photo); }
このコードの動作を検討します。
- ユーザー のプロファイルを取得します。
- 返されるプロパティ
Select
を制限するために使用されます。
- 返されるプロパティ
- ユーザー の写真を取得します。
- 具体的には、ユーザーの写真の 48x48 ピクセル バージョンを要求します。
- ClaimsPrincipal に情報を追加します。
- ユーザー のプロファイルを取得します。
./Shared/LoginDisplay.razor を開き、 次の変更を行います。
/img/no-profile-photo.png
を@(context.User.GetUserGraphPhoto() ?? "/img/no-profile-photo.png")
に置き換えます。placeholder@contoso.com
を@context.User.GetUserGraphEmail()
に置き換えます。
... <img src="@(context.User.GetUserGraphPhoto() ?? "/img/no-profile-photo.png")" class="nav-profile-photo rounded-circle align-self-center mr-2"> ... <p class="dropdown-item-text text-muted mb-0">@context.User.GetUserGraphEmail()</p> ...
変更内容をすべて保存し、アプリを再起動します。 アプリにログインします。 アプリが更新され、トップ メニューにユーザーの写真が表示されます。 ユーザーの写真を選択すると、ユーザーの名前、電子メール アドレス、および [ログアウト] ボタンが表示されたドロップダウン メニューが開 きます。
予定表ビューを取得する
このセクションでは、Microsoft Graphをアプリケーションに組み込み、現在の週のユーザーの予定表を表示します。
予定表ビューを取得する
Calendar.razor という名前の ./Pages ディレクトリに新しいファイルを作成し、次のコードを追加します。
@page "/calendar" @using Microsoft.Graph @using TimeZoneConverter @inject GraphTutorial.Graph.GraphClientFactory clientFactory <AuthorizeView> <Authorized> <!-- Temporary JSON dump of events --> <code>@graphClient.HttpProvider.Serializer.SerializeObject(events)</code> </Authorized> <NotAuthorized> <RedirectToLogin /> </NotAuthorized> </AuthorizeView> @code{ [CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; } private GraphServiceClient graphClient; private IList<Event> events = new List<Event>(); private string dateTimeFormat; }
セクション内に次のコードを追加
@code{}
します。protected override async Task OnInitializedAsync() { // Get the user var user = (await authenticationStateTask).User; var graphTimeZone = user.GetUserGraphTimeZone(); dateTimeFormat = $"{user.GetUserGraphDateFormat()} {user.GetUserGraphTimeFormat()}"; // Calculate the start and end of the current week in user's time zone var startOfWeek = GetUtcStartOfWeekInTimeZone(DateTime.Today, graphTimeZone); var endOfWeek = startOfWeek.AddDays(7); graphClient = clientFactory.GetAuthenticatedClient(); // Specifies the start and end of the view on the calendar // Translates to: ?startDateTime=""&endDateTime="" var viewOptions = new List<QueryOption> { new QueryOption("startDateTime", startOfWeek.ToString("o")), new QueryOption("endDateTime", endOfWeek.ToString("o")) }; var eventPage = await graphClient.Me .CalendarView .Request(viewOptions) // Send user time zone in request so date/time in // response will be in preferred time zone .Header("Prefer", $"outlook.timezone=\"{graphTimeZone}\"") // Get max 50 per request .Top(50) // Only return fields app will use .Select(e => new { e.Subject, e.Organizer, e.Start, e.End }) // Order results chronologically .OrderBy("start/dateTime") .GetAsync(); events = eventPage.CurrentPage; } private DateTime GetUtcStartOfWeekInTimeZone(DateTime today, string timeZoneId) { // Time zone returned by Graph could be Windows or IANA style // TimeZoneConverter can take either TimeZoneInfo userTimeZone = TZConvert.GetTimeZoneInfo(timeZoneId); // Assumes Sunday as first day of week int diff = System.DayOfWeek.Sunday - today.DayOfWeek; // create date as unspecified kind var unspecifiedStart = DateTime.SpecifyKind(today.AddDays(diff), DateTimeKind.Unspecified); // convert to UTC return TimeZoneInfo.ConvertTimeToUtc(unspecifiedStart, userTimeZone); }
このコードの動作を検討します。
- ユーザーに追加されたカスタム クレームから、現在のユーザーのタイム ゾーン、日付形式、および時刻形式を取得します。
- ユーザーの優先タイム ゾーンの現在の週の開始と終了を計算します。
- Microsoft から現在の週の予定表Graphを取得します。
- このヘッダーには、
Prefer: outlook.timezone
指定したタイム ゾーンGraphプロパティstart``end
を返すヘッダーが含まれています。 - 応答で
Top(50)
最大 50 のイベントを要求するために使用されます。 - アプリで
Select(u => new {})
使用されるプロパティを要求する場合に使用します。 - 開始時刻で
OrderBy("start/dateTime")
結果を並べ替える場合に使用します。
- このヘッダーには、
変更内容をすべて保存し、アプリを再起動します。 [予定表] ナビゲーション アイテム を選択します。 アプリは、Microsoft サービスから返されるイベントの JSON 表記を表示Graph。
結果の表示
これで、JSON ダンプを、より使い方の良いものに置き換える事が可能です。
セクション内に次の関数を追加
@code{}
します。private string FormatIso8601DateTime(string iso8601DateTime) { // Load into a DateTime var dateTime = DateTime.Parse(iso8601DateTime); if (!string.IsNullOrWhiteSpace(dateTimeFormat)) { // Format it using the user's settings return dateTime.ToString(dateTimeFormat); } // Fallback to return original value return iso8601DateTime; }
このコードは、ISO 8601 の日付文字列を受け取り、ユーザーの優先する日付と時刻の形式に変換します。
要素内の
<code>
要素を次に<Authorized>
置き換える。<Authorized> <h1 class="mb-3">Calendar</h1> <a href="/newevent" class="btn btn-light btn-sm mb-3">New event</a> <table class="table"> <thead> <tr> <th>Organizer</th> <th>Subject</th> <th>Start</th> <th>End</th> </tr> </thead> <tbody> @foreach(var calendarEvent in events) { <tr> <td>@calendarEvent.Organizer.EmailAddress.Name</td> <td>@calendarEvent.Subject</td> <td>@FormatIso8601DateTime(calendarEvent.Start.DateTime)</td> <td>@FormatIso8601DateTime(calendarEvent.End.DateTime)</td> </tr> } </tbody> </table> </Authorized>
これにより、Microsoft が返すイベントのテーブルが作成Graph。
変更内容を保存し、アプリを再起動します。 これで、[ 予定表] ページはイベントのテーブルをレンダリングします。
新しいイベントを作成する
このセクションでは、ユーザーの予定表にイベントを作成する機能を追加します。
NewEvent.razor という 名前の ./Pages ディレクトリに新しいファイルを作成し、次のコードを追加します。
@page "/newevent" @using Microsoft.Graph @inject GraphTutorial.Graph.GraphClientFactory clientFactory <AuthorizeView> <Authorized> @if (!string.IsNullOrEmpty(status)) { <div class="alert @(isError ? "alert-danger" : "alert-success")">@status</div> } <form> <div class="form-group"> <label>Subject</label> <input @bind="subject" class="form-control" /> </div> <div class="form-group"> <label>Attendees</label> <input @bind="attendees" class="form-control" /> </div> <div class="form-row"> <div class="col"> <div class="form-group"> <label>Start</label> <input @bind="start" type="datetime" class="form-control" /> </div> </div> <div class="col"> <div class="form-group"> <label>End</label> <input @bind="end" type="datetime" class="form-control" /> </div> </div> </div> <div class="form-group"> <label>Body</label> <textarea @bind="body" class="form-control"></textarea> </div> </form> <button class="btn btn-primary mr-2" @onclick="CreateEvent">Create</button> <a href="/calendar" class="btn btn-secondrary">Cancel</a> </Authorized> <NotAuthorized> <RedirectToLogin /> </NotAuthorized> </AuthorizeView>
これにより、ユーザーが新しいイベントの値を入力できるよう、フォームがページに追加されます。
ファイルの末尾に次のコードを追加します。
@code{ [CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; } private string status; private bool isError; private string userTimeZone; private string subject; private string attendees; private DateTime start = new DateTime(DateTime.Today.Ticks); private DateTime end = new DateTime(DateTime.Today.Ticks); private string body; protected override async Task OnInitializedAsync() { // Get the user's time zone var user = (await authenticationStateTask).User; userTimeZone = user.GetUserGraphTimeZone() ?? "UTC"; } private async Task CreateEvent() { // Initalize an Event object // with user input var newEvent = new Event { Subject = subject, Start = new DateTimeTimeZone { DateTime = start.ToString("o"), TimeZone = userTimeZone }, End = new DateTimeTimeZone { DateTime = end.ToString("o"), TimeZone = userTimeZone }, Body = new ItemBody { Content = body, ContentType = BodyType.Text } }; // If the user provided attendees (semicolon-delimited // list of email addresses) add them if (!string.IsNullOrEmpty(attendees)) { var attendeeList = new List<Attendee>(); var attendeeArray = attendees.Split(";"); foreach (var email in attendeeArray) { Console.WriteLine($"Adding {email}"); attendeeList.Add(new Attendee { // Set to required attendee Type = AttendeeType.Required, EmailAddress = new EmailAddress { Address = email } }); } newEvent.Attendees = attendeeList; } var graphClient = clientFactory.GetAuthenticatedClient(); try { // POST /me/events await graphClient.Me .Events .Request() .AddAsync(newEvent); isError = false; status = "Event created"; } catch (ServiceException exception) { isError = true; status = exception.Message; } } }
このコードの動作を検討します。
- その
OnInitializedAsync
中で、認証されたユーザーのタイム ゾーンを取得します。 - フォーム
CreateEvent
の値を使用して新しい Event オブジェクトを初期化します。 - このイベントは、Graph SDK を使用して、ユーザーの予定表にイベントを追加します。
- その
変更内容をすべて保存し、アプリを再起動します。 [予定表] ページで 、[新しいイベント ] を選択します。 フォームに入力し、[作成] を 選択します。
おめでとうございます。
Blazor WebAssembly Microsoft のチュートリアルをGraphしました。 Microsoft Graphを呼び出す作業アプリが作成されたので、新しい機能を試して追加できます。 Microsoft Graphの概要を参照して、Microsoft Graph でアクセスできるすべてのデータを確認Graph。
フィードバック
このチュートリアルに関するフィードバックは、リポジトリのGitHubしてください。
このセクションに問題がある場合 このセクションを改善できるよう、フィードバックをお送りください。