概要
以下を読みながら、Blazor WebAssembly
とMicrosoft Graph API
でOneDriveの読み書きを試したときのメモ。
- microsoftgraph/msgraph-sdk-dotnet
https://github.com/microsoftgraph/msgraph-sdk-dotnet - Secure an ASP.NET Core Blazor WebAssembly standalone app with Microsoft Accounts
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/standalone-with-microsoft-accounts?view=aspnetcore-6.0 - Use Graph API with ASP.NET Core Blazor WebAssembly
https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/graph-api?view=aspnetcore-6.0 - List available drives
https://docs.microsoft.com/en-us/graph/api/drive-list?view=graph-rest-1.0&tabs=http
開発環境構築方法は割愛する。
Visual Studio 2022でBlazor WebAssemblyの開発ができる状態であれば、大丈夫なはず。
ざっくりな流れは、以下のとおり。
- Azure Active Directoryにアプリを登録する
- Blazor WebAssemblyを実装する
Azure Active Directoryにアプリを登録する
Azure Active Directoryにアプリを登録する必要がある。
- 以下にアクセスする。
https://aad.portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview - [追加] -> [アプリの登録]をクリックする。
- 各種入力をして、登録をクリックする。
- 名前
任意の名前。OneDriveReadWriteByBlazorWasmとした。 - サポートされているアカウントの種類
- リダイレクトURL
あとで設定する
- 名前
- 表示されているページのURLとクライアントIDを控える。
- パブリッククライアントフローを許可し、保存する。
- APIのアクセスを許可する。
Blazor WebAssemblyを実装する
ここから本題。
プロジェクト作成
dotnet new blazorwasm -au SingleOrg --client-id "{CLIENT ID}" --tenant-id "common" -o {APP NAME}
プレースホルダー | Azure Active Directory Name | 例 | 備考 |
---|---|---|---|
APP NAME | - | - | 任意のアプリ名 |
CLIENT ID | アプリケーション(クライアント)ID | 41451fa7-82d9-4673-8fa5-69eff5a761fd | 上で控えたやつ |
Nugetパッケージ[Microsoft.Graph]をプロジェクトに追加
cd {APP NAME}.csprojがあるフォルダ
dotnet add package Microsoft.Graph
openid と offline_access の DefaultAccessTokenScopes の MsalProviderOptions のペアを追加
Program.csにaddの2行を追加する
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("openid"); // add
options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access"); // add
});
ログインできることを確認
-
{APP NAME}.csproj
があるフォルダに移動してdotnet run
を実行する。
以下が出力される。Building... info: Microsoft.Hosting.Lifetime[14] Now listening on: https://localhost:7176 info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5013 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development info: Microsoft.Hosting.Lifetime[0] Content root path: xxxxxxxxx\OneDriveReadWriteByBlazorWasm\OneDriveReadWriteByBlazorWasm
-
[リダイレクトURL]を入力し、[構成]をクリックする。
[リダイレクトURL]はhttps://localhost:ポート番号/authentication/login-callback
とする。
ポート番号はdotnet run
の最初に出るURLを参照。
上の例で言うとhttps://localhost:7176
-
ログインできたことを確認したら、ブラウザを閉じる。
-
dotnet run
を[Ctrl + C]で止める。
Microsoft Graph APIを使用して、OneDriveを読み書き
-
Visual Studio 2022で
{APP NAME}.csproj
を開く。 -
プロジェクト直下に
GraphClientExtensions.cs
を追加し、以下のコードを書く。
namespace {APP NAME}
{
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.Authentication.WebAssembly.Msal.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Graph;
internal static class GraphClientExtensions
{
public static IServiceCollection AddGraphClient(
this IServiceCollection services, params string[] scopes)
{
services.Configure<RemoteAuthenticationOptions<MsalProviderOptions>>(
options =>
{
foreach (var scope in scopes)
{
options.ProviderOptions.AdditionalScopesToConsent.Add(scope);
}
});
services.AddScoped<IAuthenticationProvider,
NoOpGraphAuthenticationProvider>();
services.AddScoped<IHttpProvider, HttpClientHttpProvider>(sp =>
new HttpClientHttpProvider(new HttpClient()));
services.AddScoped(sp =>
{
return new GraphServiceClient(
sp.GetRequiredService<IAuthenticationProvider>(),
sp.GetRequiredService<IHttpProvider>());
});
return services;
}
private class NoOpGraphAuthenticationProvider : IAuthenticationProvider
{
public NoOpGraphAuthenticationProvider(IAccessTokenProvider tokenProvider)
{
TokenProvider = tokenProvider;
}
public IAccessTokenProvider TokenProvider { get; }
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
var result = await TokenProvider.RequestAccessToken(
new AccessTokenRequestOptions()
{
Scopes = new[] {
"https://graph.microsoft.com/User.Read",
"https://graph.microsoft.com/Files.ReadWrite"
}
});
if (result.TryGetToken(out var token))
{
request.Headers.Authorization ??= new AuthenticationHeaderValue(
"Bearer", token.Value);
}
}
}
private class HttpClientHttpProvider : IHttpProvider
{
private readonly HttpClient http;
public HttpClientHttpProvider(HttpClient http)
{
this.http = http;
}
public ISerializer Serializer { get; } = new Serializer();
public TimeSpan OverallTimeout { get; set; } = TimeSpan.FromSeconds(300);
public void Dispose()
{
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
return http.SendAsync(request);
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
HttpCompletionOption completionOption,
CancellationToken cancellationToken)
{
return http.SendAsync(request, completionOption, cancellationToken);
}
}
}
}
- Program.csにaddの1行を追加する
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("openid");
options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");
});
// add
builder.Services.AddGraphClient("https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/Files.ReadWrite");
-
Pages/GraphExample.razor
を追加し、以下のコードを書く
@page "/GraphExample"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.Graph
@using System.IO
@using System.Text
@attribute [Authorize]
@inject GraphServiceClient GraphClient
<h3>Graph Client Example</h3>
<h4>User infomation</h4>
@if (user != null)
{
<p>DisplayName: @user.DisplayName</p>
<p>UserPrincipalName: @user.UserPrincipalName</p>
}
<h4>Onedrive root items</h4>
@if (rootChildren != null)
{
<table class="table">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
@foreach (var rootChild in rootChildren)
{
<tr>
<td>@rootChild.Name</td>
</tr>
}
</tbody>
</table>
}
<h4>uploaded_by_graph_example</h4>
@if (lines != null)
{
@foreach(var line in lines)
{
<p>@line</p>
}
}
@code {
private User? user;
private IDriveItemChildrenCollectionPage? rootChildren;
private List<string>? lines;
protected override async Task OnInitializedAsync()
{
await this.InitializeUser();
await this.PutFileToOneDrive();
await this.InitializeItemList();
await this.ReadTextFromOneDrive();
}
private async Task InitializeUser()
{
var request = GraphClient.Me.Request();
user = await request.GetAsync();
}
private async Task PutFileToOneDrive()
{
await GraphClient.Me.Drive.Root
.ItemWithPath("uploaded_by_graph_example.txt").Content
.Request()
.PutAsync<DriveItem>(new MemoryStream(Encoding.GetEncoding("UTF-8").GetBytes("line1\nline2\n行3\n")));
}
private async Task InitializeItemList()
{
rootChildren = await GraphClient.Me.Drive.Root.Children.Request().GetAsync();
}
private async Task ReadTextFromOneDrive()
{
var stream = await GraphClient.Me.Drive.Root
.ItemWithPath("uploaded_by_graph_example.txt").Content
.Request()
.GetAsync();
var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var text = Encoding.GetEncoding("UTF-8").GetString(ms.ToArray());
lines = text.Split('\n').ToList();
}
}
-
{APP NAME}.csprojがあるフォルダに移動してdotnet runを実行し、ブラウザで
https://localhost:ポート番号/
を表示する。 -
[Log in]をクリックし、Microsoftアカウントにログインする。