Wywoływanie programu Microsoft Graph

Wywołaj Microsoft Graph z aplikacji ASP.NET Core i OWIN przy użyciu Microsoft.Identity.Web i Microsoft Graph SDK w celu uzyskania dostępu do danych i usług Microsoft 365.

Omówienie integracji Microsoft Graph

Microsoft Graph zapewnia ujednolicony punkt końcowy interfejsu API umożliwiający uzyskiwanie dostępu do danych między Microsoft 365, Windows i Enterprise Mobility + Security. Microsoft.Identity.Web upraszcza uwierzytelnianie i pozyskiwanie tokenów dla Microsoft Graph, a zestaw SDK Microsoft Graph zapewnia płynny, typowany interfejs API do wywoływania punktów końcowych Microsoft Graph.

Wybierz pozycję Microsoft. Identity.Web.GraphServiceClient

Poniższe korzyści sprawiają, że Microsoft.Identity.Web.GraphServiceClient jest zalecanym podejściem do wywoływania Microsoft Graph.

  • Automatyczne pozyskiwanie tokenów: bezproblemowo obsługuje tokeny użytkowników i aplikacji
  • Wbudowane buforowanie tokenów dla poprawy wydajności
  • Interfejs API Fluent: bezpieczne dla typu, przyjazne dla funkcji IntelliSense wywołania grafu
  • Zgoda przyrostowa: żądanie dodatkowych zakresów na żądanie
  • Wiele schematów uwierzytelniania: obsługa aplikacji internetowych i internetowych interfejsów API
  • Zarówno wersja 1.0, jak i Beta: używaj stabilnych i przeglądowych punktów końcowych razem

Instalowanie wymaganych pakietów

Zainstaluj pakiet integracyjny zestawu SDK Microsoft Graph:

dotnet add package Microsoft.Identity.Web.GraphServiceClient

W przypadku interfejsów API Microsoft Graph Beta:

dotnet add package Microsoft.Identity.Web.GraphServiceClientBeta

Konfigurowanie ASP.NET Core

1. Konfigurowanie usług

Dodaj obsługę Microsoft Graph do aplikacji:

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. Konfigurowanie appsettings.json

Skonfiguruj opcje programu Graph w pliku konfiguracji:

{
  "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"]
    }
  }
}

Konfiguracja z kodem:

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

Możesz też skonfigurować bezpośrednio w kodzie:

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

3. Konfigurowanie obsługi chmury krajowej

Aby użyć Microsoft Graph w chmurach krajowych, określ element BaseUrl w konfiguracji:

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

Zobacz wdrożenia Microsoft Graph dla adresów URL punktów końcowych.

Korzystanie z GraphServiceClient

Wstrzykiwanie GraphServiceClient

Wstrzyknij GraphServiceClient z konstruktora:

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);
    }
}

Korzystanie z uprawnień delegowanych (tokenów użytkowników)

Wywołaj program Graph w imieniu zalogowanego użytkownika z delegowanymi uprawnieniami.

Pobieranie podstawowego profilu użytkownika

Pobierz informacje o profilu bieżącego użytkownika z 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
        });
    }
}

Żądaj dodatkowych zakresów dynamicznie, gdy aplikacja ich potrzebuje:

[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;
        }
    }
}

Stosowanie opcji zapytania

Użyj opcji zapytań zestawu Graph SDK, aby filtrować, wybierać i porządkować wyniki:

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);
}

Przeglądaj wyniki strona po stronie

Obsłuż stronicowane wyniki z Microsoft Graph, iterując po każdej stronie:

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);
}

Używanie uprawnień aplikacji (tokenów tylko dla aplikacji)

Wywołaj program Graph z uprawnieniami aplikacji, jeśli kontekst użytkownika nie jest wymagany.

Wywoływanie grafu za pomocą funkcji WithAppOnly()

WithAppOnly() Użyj metody , aby wykonywać wywołania programu Graph z uprawnieniami aplikacji.

[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);
    }
}

Konfigurowanie uprawnień aplikacji

Określ żądanie tokenu aplikacji w appsettings.json:

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

Zakresy zostaną automatycznie ustawione na ["https://graph.microsoft.com/.default"].

Konfigurowanie szczegółowych opcji tylko dla aplikacji

Ustaw jawne opcje uwierzytelniania tylko dla aplikacji w kodzie.

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);
}

Obsługa wielu schematów uwierzytelniania

Jeśli aplikacja używa wielu schematów uwierzytelniania (na przykład aplikacji internetowej i interfejsu API), określ schemat do użycia:

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);
    }
}

Konfigurowanie szczegółowych opcji schematu

Ustaw jawnie schemat uwierzytelniania i zakresy w kodzie.

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);
}

Użyj zarówno punktów końcowych v1.0, jak i beta.

Zarejestruj się i wywołaj zarówno Microsoft Graph w wersji 1.0, jak i beta w tej samej aplikacji.

1. Zainstaluj oba pakiety

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

2. Rejestrowanie obu usług

using Microsoft.Identity.Web;

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

3. Użyj obu klientów

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 });
    }
}

Wysyłanie żądań wsadowych

Połącz wiele wywołań programu Graph w jedno żądanie HTTP, aby zwiększyć wydajność:

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
    });
}

Stosowanie typowych wzorców grafu

Te wzorce umożliwiają częste wykonywanie operacji Microsoft Graph w aplikacji.

Pobierz menedżera użytkownika

Pobierz menedżera zalogowanego użytkownika z katalogu.

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");
}

Pobieranie zdjęcia użytkownika

Pobierz zdjęcie profilu zalogowanego użytkownika w postaci strumienia.

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");
    }
}

Wyślij e-mail

Wyślij wiadomość e-mail w imieniu zalogowanego użytkownika.

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");
}

Tworzenie zdarzenia kalendarza

Utwórz nowe wydarzenie kalendarza z uczestnikami dla zalogowanego użytkownika.

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);
}

Wyszukiwanie użytkowników

Wyszukaj użytkowników w katalogu według nazwy wyświetlanej lub adresu e-mail.

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);
}

Implementowanie obsługi OWIN

W przypadku aplikacji ASP.NET korzystających z OWIN skonfiguruj fabrykę pozyskiwania tokenów i zarejestruj usługi 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. Wywoływanie interfejsu API z kontrolerów

Pobierz wystąpienie GraphServiceClient w kontrolerze i wywołaj 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);
    }
}

Migrowanie z Microsoft. Identity.Web.MicrosoftGraph 2.x

Jeśli przeprowadzasz migrację ze starszego pakietu Microsoft.Identity.Web.MicrosoftGraph (SDK 4.x), zapoznaj się z następującymi kluczowymi zmianami:

1. Usuń stary pakiet i dodaj nowy

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

2. Aktualizowanie wywołań metod

W zestawie SDK 5.x metoda .Request() została usunięta.

Przed (SDK 4.x):

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

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

Po SDK 5.x:

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

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

3. Zaktualizuj lokalizację WithScopes()

Before:

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. Aktualizowanie lokalizacji withAppOnly()

Before:

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

After:

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

5. Zaktualizuj lokalizację WithAuthenticationScheme()

Before:

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

After:

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

Aby uzyskać szczegółowe informacje na temat migracji, zobacz Microsoft Graph .NET SDK v5 changelog .

Zarządzanie błędami

Obsługa elementu ServiceException

Przechwyć ODataError i MicrosoftIdentityWebChallengeUserException, aby bezpiecznie obsługiwać błędy interfejs 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");
    }
}

Postępuj zgodnie z najlepszymi rozwiązaniami

1. Zażądaj minimalnych zakresów

Proś tylko o zakresy, których potrzebujesz.

//  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" };

Zażądaj dodatkowych zakresów tylko w razie potrzeby:

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

3. Cache GraphServiceClient

Klasa GraphServiceClient jest bezpieczna do ponownego użycia. Zarejestruj jako singleton albo wstrzyknij z użyciem DI.

4. Użyj opcji wybierz, aby zmniejszyć rozmiar odpowiedzi

//  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" });

Rozwiązywanie typowych problemów

Rozwiązywanie problemu "Niewystarczające uprawnienia do ukończenia operacji"

Przyczyna: Aplikacja nie ma wymaganych uprawnień programu Graph.

Rozwiązanie:

  • Dodaj wymagane uprawnienia interfejsu API w ramach rejestracji aplikacji
  • Zgoda administratora wymagana dla uprawnień aplikacji
  • Zgoda użytkownika wymagana dla uprawnień delegowanych

Rozwiąż problem "AADSTS65001: użytkownik lub administrator nie wyraził zgody"

Przyczyna: Użytkownik nie wyraził zgody na żądane zakresy.

Rozwiązanie: Stosuj zgodę przyrostową z .WithScopes(), aby wyzwolić przepływ zgody.

Rozwiązywanie problemów ze zdjęciem 404

Przyczyna: Użytkownik nie ma zdjęcia profilowego.

Rozwiązanie: Obsłuż błąd 404 w sposób bezpieczny i zastosuj domyślny awatar.

Rozwiązać błędy żądań wsadowych

Przyczyna: Pojedyncze żądania w partii mogą niezależnie zakończyć się niepowodzeniem.

Rozwiązanie: Sprawdź każdą odpowiedź w partii pod kątem błędów:

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

Kolejne kroki: Dowiedz się o wywoływaniu Azure SDK lub niestandardowych API.