Microsoft Graph を呼び出す

ASP.NET Core および OWIN アプリケーションから Microsoft.Identity.Web と Microsoft Graph SDK を使用して、Microsoft Graph により Microsoft 365 のデータやサービスにアクセスします。

Microsoft Graph統合について

Microsoft Graphは、Microsoft 365、Windows、Enterprise Mobility + Security間でデータにアクセスするための統合 API エンドポイントを提供します。 Microsoft。Identity.Web は、Microsoft Graphの認証とトークンの取得を簡素化しますが、Microsoft Graph SDK は Graph エンドポイントを呼び出すための fluent 型の API を提供します。

Microsoft.Identity.Web.GraphServiceClient を選択します。

次の利点が、Microsoft.Identity.Web.GraphServiceClient を使用して Microsoft Graph を呼び出す際の推奨方法であることを示しています。

  • トークンの自動取得: ユーザー トークンとアプリ トークンをシームレスに処理します
  • トークン キャッシュ: パフォーマンスのための組み込みのキャッシュ
  • Fluent API: タイプ セーフで IntelliSense に対応した Graph 呼び出し
  • 増分同意: 要求時に追加のスコープを要求する
  • 複数の認証スキーム: Web アプリと Web API のサポート
  • v1.0 とベータ版の両方: 安定したエンドポイントとプレビュー エンドポイントを一緒に使用する

必要なパッケージをインストールする

Microsoft Graph SDK 統合パッケージをインストールします。

dotnet add package Microsoft.Identity.Web.GraphServiceClient

Microsoft Graph ベータ API の場合:

dotnet add package Microsoft.Identity.Web.GraphServiceClientBeta

ASP.NET Coreを設定する

1. サービスを構成する

あなたのアプリケーションにMicrosoft Graphサポートを追加します。

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Add authentication (web app or web API)
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

// Add Microsoft Graph support
builder.Services.AddMicrosoftGraph();

builder.Services.AddControllersWithViews();

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

2. appsettings.json を構成する

構成ファイルで Graph オプションを構成します。

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "ClientSecret": "your-client-secret",
    "CallbackPath": "/signin-oidc"
  },
  "DownstreamApis": {
    "MicrosoftGraph": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "Scopes": ["User.Read", "User.ReadBasic.All"]
    }
  }
}

コードを使用した構成:

builder.Services.AddMicrosoftGraph(options =>
{
    builder.Configuration.GetSection("DownstreamApis:MicrosoftGraph").Bind(options);
});

または、コードで直接構成します。

builder.Services.AddMicrosoftGraph();
builder.Services.Configure<MicrosoftGraphOptions>(options =>
{
    options.BaseUrl = "https://graph.microsoft.com/v1.0";
    options.Scopes = new[] { "User.Read", "Mail.Read" };
});

3. 各国のクラウド サポートを構成する

国内クラウドでMicrosoft Graphを使用するには、構成で BaseUrl を指定します。

{
  "DownstreamApis": {
    "MicrosoftGraph": {
      "BaseUrl": "https://graph.microsoft.us/v1.0",
      "Scopes": ["User.Read"]
    }
  }
}

エンドポイント URL については、「Microsoft Graph デプロイ」を参照してください。

GraphServiceClient を使用する

GraphServiceClient を挿入する

コンストラクターから GraphServiceClient を挿入します。

using Microsoft.Graph;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize]
public class ProfileController : Controller
{
    private readonly GraphServiceClient _graphClient;
    
    public ProfileController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }
    
    public async Task<IActionResult> Index()
    {
        // Call Microsoft Graph
        var user = await _graphClient.Me.GetAsync();
        return View(user);
    }
}

委任されたアクセス許可を使用する (ユーザー トークン)

委任されたアクセス許可を持つサインインユーザーの代わりに Graph を呼び出します。

基本的なユーザー プロファイルを取得する

Microsoft Graphから現在のユーザーのプロファイル情報を取得します。

[Authorize]
public class ProfileController : Controller
{
    private readonly GraphServiceClient _graphClient;
    
    public ProfileController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }
    
    public async Task<IActionResult> Me()
    {
        // Get current user's profile
        var user = await _graphClient.Me.GetAsync();
        
        return View(new UserViewModel
        {
            DisplayName = user.DisplayName,
            Mail = user.Mail,
            JobTitle = user.JobTitle
        });
    }
}

アプリケーションで必要なときに、追加のスコープを動的に要求します。

[Authorize]
[AuthorizeForScopes("Mail.Read")]
public class MailController : Controller
{
    private readonly GraphServiceClient _graphClient;
    
    public MailController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }
    
    public async Task<IActionResult> Inbox()
    {
        try
        {
            // Request Mail.Read scope dynamically
            var messages = await _graphClient.Me.Messages
                .GetAsync(r => r.Options.WithScopes("Mail.Read"));
            
            return View(messages);
        }
        catch (MicrosoftIdentityWebChallengeUserException)
        {
            // ASP.NET Core will redirect user to consent
            // thansk to the AuthorizeForScopes attribute.
            throw;
        }
    }
}

クエリ オプションを適用する

Graph SDK クエリ オプションを使用して、結果をフィルター処理、選択、並べ替えます。

public async Task<IActionResult> UnreadMessages()
{
    var messages = await _graphClient.Me.Messages
        .GetAsync(requestConfiguration =>
        {
            requestConfiguration.QueryParameters.Filter = "isRead eq false";
            requestConfiguration.QueryParameters.Select = new[] { "subject", "from", "receivedDateTime" };
            requestConfiguration.QueryParameters.Orderby = new[] { "receivedDateTime desc" };
            requestConfiguration.QueryParameters.Top = 10;
            
            // Request specific scope
            requestConfiguration.Options.WithScopes("Mail.Read");
        });
    
    return View(messages);
}

結果を順に見ていく

各ページを反復処理して、Microsoft Graphからのページングされた結果を処理します。

public async Task<IActionResult> AllUsers()
{
    var allUsers = new List<User>();
    
    // Get first page
    var users = await _graphClient.Users
        .GetAsync(r => r.Options.WithScopes("User.ReadBasic.All"));
    
    // Add first page
    allUsers.AddRange(users.Value);
    
    // Iterate through remaining pages
    var pageIterator = PageIterator<User, UserCollectionResponse>
        .CreatePageIterator(
            _graphClient,
            users,
            user =>
            {
                allUsers.Add(user);
                return true; // Continue iteration
            });
    
    await pageIterator.IterateAsync();
    
    return View(allUsers);
}

アプリケーションのアクセス許可を使用する (アプリ専用トークン)

ユーザー コンテキストが必要ない場合は、アプリケーションのアクセス許可で Graph を呼び出します。

WithAppOnly() を使用して Graph を呼び出す

WithAppOnly() メソッドを使用して、アプリケーションのアクセス許可を持つ Graph 呼び出しを行います。

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class AdminController : ControllerBase
{
    private readonly GraphServiceClient _graphClient;
    
    public AdminController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }
    
    [HttpGet("users/count")]
    public async Task<ActionResult<int>> GetUserCount()
    {
        // Get count using app permissions
        var count = await _graphClient.Users.Count
            .GetAsync(r => r.Options.WithAppOnly());
        
        return Ok(count);
    }
    
    [HttpGet("applications")]
    public async Task<ActionResult> GetApplications()
    {
        // List applications using app permissions
        var apps = await _graphClient.Applications
            .GetAsync(r => r.Options.WithAppOnly());
        
        return Ok(apps.Value);
    }
}

アプリケーション権限を構成する

appsettings.jsonでアプリ トークン要求を指定します。

{
  "DownstreamApis": {
    "MicrosoftGraph": {
      "BaseUrl": "https://graph.microsoft.com/v1.0",
      "RequestAppToken": true
    }
  }
}

スコープは自動的に ["https://graph.microsoft.com/.default"]に設定されます。

詳細なアプリ専用オプションを構成する

明示的なアプリ専用認証オプションをコードで設定します。

public async Task<IActionResult> GetApplicationsDetailed()
{
    var apps = await _graphClient.Applications
        .GetAsync(r =>
        {
            r.Options.WithAuthenticationOptions(options =>
            {
                // Request app token explicitly
                options.RequestAppToken = true;
                
                // Scopes automatically become [.default]
                // No need to specify: options.Scopes = new[] { "https://graph.microsoft.com/.default" };
            });
        });
    
    return Ok(apps);
}

複数の認証スキームを処理する

アプリで複数の認証スキーム (Web アプリ + API など) を使用する場合は、使用するスキームを指定します。

using Microsoft.AspNetCore.Authentication.JwtBearer;

[Authorize]
public class ApiDataController : ControllerBase
{
    private readonly GraphServiceClient _graphClient;
    
    public ApiDataController(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }
    
    [HttpGet("profile")]
    public async Task<ActionResult> GetProfile()
    {
        // Specify JWT Bearer scheme
        var user = await _graphClient.Me
            .GetAsync(r => r.Options
                .WithAuthenticationScheme(JwtBearerDefaults.AuthenticationScheme));
        
        return Ok(user);
    }
}

詳細なスキーム オプションを構成する

認証スキームとスコープをコードで明示的に設定します。

public async Task<ActionResult> GetMailWithScheme()
{
    var messages = await _graphClient.Me.Messages
        .GetAsync(r =>
        {
            r.Options.WithAuthenticationOptions(options =>
            {
                // Specify authentication scheme
                options.AcquireTokenOptions.AuthenticationOptionsName = 
                    JwtBearerDefaults.AuthenticationScheme;
                
                // Specify scopes
                options.Scopes = new[] { "Mail.Read" };
            });
        });
    
    return Ok(messages);
}

v1.0 エンドポイントとベータ エンドポイントの両方を使用する

同じアプリケーションMicrosoft Graph v1.0 と Beta の両方を登録して呼び出します。

1. 両方のパッケージをインストールする

dotnet add package Microsoft.Identity.Web.GraphServiceClient
dotnet add package Microsoft.Identity.Web.GraphServiceClientBeta

2. 両方のサービスを登録する

using Microsoft.Identity.Web;

builder.Services.AddMicrosoftGraph();
builder.Services.AddMicrosoftGraphBeta();

3. 両方のクライアントを使用する

using GraphServiceClient = Microsoft.Graph.GraphServiceClient;
using GraphBetaServiceClient = Microsoft.Graph.Beta.GraphServiceClient;

public class MyController : Controller
{
    private readonly GraphServiceClient _graphClient;
    private readonly GraphBetaServiceClient _graphBetaClient;
    
    public MyController(
        GraphServiceClient graphClient,
        GraphBetaServiceClient graphBetaClient)
    {
        _graphClient = graphClient;
        _graphBetaClient = graphBetaClient;
    }
    
    public async Task<IActionResult> GetData()
    {
        // Use stable v1.0 endpoint
        var user = await _graphClient.Me.GetAsync();
        
        // Use beta endpoint for preview features
        var profile = await _graphBetaClient.Me.Profile.GetAsync();
        
        return View(new { user, profile });
    }
}

バッチ要求を送信する

パフォーマンスを向上させるために、複数の Graph 呼び出しを 1 つの HTTP 要求に結合します。

using Microsoft.Graph.Models;

public async Task<IActionResult> GetDashboard()
{
    var batchRequestContent = new BatchRequestContentCollection(_graphClient);
    
    // Add multiple requests to batch
    var userRequest = _graphClient.Me.ToGetRequestInformation();
    var messagesRequest = _graphClient.Me.Messages.ToGetRequestInformation();
    var eventsRequest = _graphClient.Me.Events.ToGetRequestInformation();
    
    var userRequestId = await batchRequestContent.AddBatchRequestStepAsync(userRequest);
    var messagesRequestId = await batchRequestContent.AddBatchRequestStepAsync(messagesRequest);
    var eventsRequestId = await batchRequestContent.AddBatchRequestStepAsync(eventsRequest);
    
    // Send batch request
    var batchResponse = await _graphClient.Batch.PostAsync(batchRequestContent);
    
    // Extract responses
    var user = await batchResponse.GetResponseByIdAsync<User>(userRequestId);
    var messages = await batchResponse.GetResponseByIdAsync<MessageCollectionResponse>(messagesRequestId);
    var events = await batchResponse.GetResponseByIdAsync<EventCollectionResponse>(eventsRequestId);
    
    return View(new DashboardViewModel 
    { 
        User = user,
        Messages = messages.Value,
        Events = events.Value
    });
}

一般的なグラフ パターンを適用する

これらのパターンを使用して、アプリケーションで頻繁にMicrosoft Graph操作を実行します。

ユーザーのマネージャーの取得

サインインしているユーザーのマネージャーをディレクトリから取得します。

public async Task<IActionResult> GetManager()
{
    var manager = await _graphClient.Me.Manager.GetAsync();
    
    // Cast to User (manager is DirectoryObject)
    if (manager is User managerUser)
    {
        return View(managerUser);
    }
    
    return NotFound("Manager not found");
}

ユーザーの写真を取得する

サインインしているユーザーのプロファイル写真をストリームとしてダウンロードします。

public async Task<IActionResult> GetPhoto()
{
    try
    {
        var photoStream = await _graphClient.Me.Photo.Content.GetAsync();
        
        return File(photoStream, "image/jpeg");
    }
    catch (ServiceException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
    {
        return NotFound("Photo not available");
    }
}

電子メールを送信する

サインインしているユーザーの代わりに電子メール メッセージを送信します。

public async Task<IActionResult> SendEmail([FromBody] EmailRequest request)
{
    var message = new Message
    {
        Subject = request.Subject,
        Body = new ItemBody
        {
            ContentType = BodyType.Html,
            Content = request.Body
        },
        ToRecipients = new List<Recipient>
        {
            new Recipient
            {
                EmailAddress = new EmailAddress
                {
                    Address = request.ToEmail
                }
            }
        }
    };
    
    await _graphClient.Me.SendMail
        .PostAsync(new SendMailPostRequestBody
        {
            Message = message,
            SaveToSentItems = true
        },
        requestConfiguration =>
        {
            requestConfiguration.Options.WithScopes("Mail.Send");
        });
    
    return Ok("Email sent");
}

予定表イベントを作成する

サインインしているユーザーの出席者を含む新しい予定表イベントを作成します。

public async Task<IActionResult> CreateEvent([FromBody] EventRequest request)
{
    var newEvent = new Event
    {
        Subject = request.Subject,
        Start = new DateTimeTimeZone
        {
            DateTime = request.StartTime.ToString("yyyy-MM-ddTHH:mm:ss"),
            TimeZone = "UTC"
        },
        End = new DateTimeTimeZone
        {
            DateTime = request.EndTime.ToString("yyyy-MM-ddTHH:mm:ss"),
            TimeZone = "UTC"
        },
        Attendees = request.Attendees.Select(email => new Attendee
        {
            EmailAddress = new EmailAddress { Address = email },
            Type = AttendeeType.Required
        }).ToList()
    };
    
    var createdEvent = await _graphClient.Me.Events
        .PostAsync(newEvent, r => r.Options.WithScopes("Calendars.ReadWrite"));
    
    return Ok(createdEvent);
}

ユーザーの検索

ディレクトリ内のユーザーを表示名または電子メール アドレスで検索します。

public async Task<IActionResult> SearchUsers(string searchTerm)
{
    var users = await _graphClient.Users
        .GetAsync(requestConfiguration =>
        {
            requestConfiguration.QueryParameters.Filter = 
                $"startswith(displayName,'{searchTerm}') or startswith(mail,'{searchTerm}')";
            requestConfiguration.QueryParameters.Select = 
                new[] { "displayName", "mail", "jobTitle" };
            requestConfiguration.QueryParameters.Top = 10;
            
            requestConfiguration.Options.WithScopes("User.ReadBasic.All");
        });
    
    return Ok(users.Value);
}

OWIN のサポートを実装する

OWIN を使用する ASP.NET アプリケーションの場合は、トークン取得ファクトリを構成し、Microsoft Graphサービスを登録します。

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.OWIN;
using Owin;

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
      OwinTokenAcquirerFactory factory = TokenAcquirerFactory.GetDefaultInstance<OwinTokenAcquirerFactory>();
      app.AddMicrosoftIdentityWebApi(factory);
      factory.Services
        .AddMicrosoftGraph();
      factory.Build();
    }
}

2. コントローラーから API を呼び出す

コントローラーで GraphServiceClient インスタンスを取得し、Microsoft Graphを呼び出します。

using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
using System.Web.Http;

[Authorize]
public class DataController : ApiController
{
    public DataController()
    {
    }

    public async Task<IHttpActionResult> GetMyProfile()
    {
        GraphServiceClient graphServiceClient = this.GetGraphServiceClient();
        var me = await graphServiceClient.Me.GetAsync();
        return Ok(me);
    }
}

Microsoft.Identity.Web.MicrosoftGraph 2.x から移行します。

古い Microsoft.Identity.Web.MicrosoftGraph パッケージ (SDK 4.x) から移行する場合、以下の重要な変更を確認してください。

1. 古いパッケージを削除して新しいパッケージを追加する

dotnet remove package Microsoft.Identity.Web.MicrosoftGraph
dotnet add package Microsoft.Identity.Web.GraphServiceClient

2. メソッド呼び出しを更新する

.Request() メソッドは SDK 5.x で削除されました。

Before (SDK 4.x):

var user = await _graphClient.Me.Request().GetAsync();

var messages = await _graphClient.Me.Messages
    .Request()
    .WithScopes("Mail.Read")
    .GetAsync();

(SDK 5.x 以降):

var user = await _graphClient.Me.GetAsync();

var messages = await _graphClient.Me.Messages
    .GetAsync(r => r.Options.WithScopes("Mail.Read"));

3. WithScopes() の場所を更新する

以前:

var users = await _graphClient.Users
    .Request()
    .WithScopes("User.Read.All")
    .GetAsync();

After:

var users = await _graphClient.Users
    .GetAsync(r => r.Options.WithScopes("User.Read.All"));

4. WithAppOnly() の場所を更新する

以前:

var apps = await _graphClient.Applications
    .Request()
    .WithAppOnly()
    .GetAsync();

After:

var apps = await _graphClient.Applications
    .GetAsync(r => r.Options.WithAppOnly());

5. WithAuthenticationScheme() の場所を更新する

以前:

var user = await _graphClient.Me
    .Request()
    .WithAuthenticationScheme(JwtBearerDefaults.AuthenticationScheme)
    .GetAsync();

After:

var user = await _graphClient.Me
    .GetAsync(r => r.Options
        .WithAuthenticationScheme(JwtBearerDefaults.AuthenticationScheme));

完全な移行の詳細については、Microsoft Graph .NET SDK v5 changelog を参照してください。

エラーを処理する

ServiceException の処理

ODataErrorMicrosoftIdentityWebChallengeUserException をキャッチして、Graph APIエラーを適切に処理します。

using Microsoft.Graph.Models.ODataErrors;

public async Task<IActionResult> GetData()
{
    try
    {
        var user = await _graphClient.Me.GetAsync();
        return Ok(user);
    }
    catch (ODataError ex) when (ex.ResponseStatusCode == 404)
    {
        return NotFound("Resource not found");
    }
    catch (ODataError ex) when (ex.ResponseStatusCode == 403)
    {
        return Forbid("Insufficient permissions");
    }
    catch (MicrosoftIdentityWebChallengeUserException)
    {
        // User needs to consent
        throw;
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Graph API call failed");
        return StatusCode(500, "An error occurred");
    }
}

ベスト プラクティスに従う

1. 最小スコープを要求する

必要なスコープのみ要求してください

//  Bad: Requesting too many scopes
options.Scopes = new[] { "User.Read", "Mail.ReadWrite", "Calendars.ReadWrite", "Files.ReadWrite.All" };

//  Good: Request only what you need
options.Scopes = new[] { "User.Read" };

必要な場合にのみ、追加のスコープを要求します。

// Sign-in: Only User.Read
// Later, when accessing mail:
var messages = await _graphClient.Me.Messages
    .GetAsync(r => r.Options.WithScopes("Mail.Read"));

3. GraphServiceClient をキャッシュする

GraphServiceClient は再利用しても安全です。 シングルトンとして登録するか、DI から挿入します。

4. select を使用して応答サイズを小さくする

//  Bad: Getting all properties
var users = await _graphClient.Users.GetAsync();

//  Good: Select only needed properties
var users = await _graphClient.Users
    .GetAsync(r => r.QueryParameters.Select = 
        new[] { "displayName", "mail", "id" });

一般的な問題のトラブルシューティング

"操作を完了するための特権が不足しています" を解決する

原因: アプリに必要な Graph アクセス許可がありません。

解決策:

  • アプリの登録に必要な API アクセス許可を追加する
  • アプリのアクセス許可に必要な管理者の同意
  • 委任されたアクセス許可に必要なユーザーの同意

"AADSTS65001: ユーザーまたは管理者が同意していません" を解決する

原因: ユーザーが要求されたスコープに同意していません。

解決策: 同意フローをトリガーするには、 .WithScopes() で増分同意を使用します。

写真404エラーを解決する

原因: ユーザーにプロフィール写真がありません。

解決策: 404 を適切に処理し、既定のアバターを提供します。

バッチ要求エラーを解決する

原因: バッチ内の個々の要求が個別に失敗する可能性があります。

解決策: エラーがないか、各応答をバッチで確認します。

var userResponse = await batchResponse.GetResponseByIdAsync<User>(userRequestId);
if (userResponse == null)
{
    // Handle individual request failure
}

次の手順: calling Azure SDK または custom API について説明します。