Chiamare Microsoft Graph

Chiamare Microsoft Graph dalle applicazioni ASP.NET Core e OWIN usando Microsoft. Identity.Web e Microsoft Graph SDK per accedere ai dati e ai servizi Microsoft 365.

Informazioni sull'integrazione di Microsoft Graph

Microsoft Graph fornisce un endpoint API unificato per l'accesso ai dati tra Microsoft 365, Windows e Enterprise Mobility + Security. Microsoft. Identity.Web semplifica l'autenticazione e l'acquisizione di token per Microsoft Graph, mentre Microsoft Graph SDK offre un'API fluente e tipizzata per chiamare gli endpoint Graph.

Scegliere Microsoft. Identity.Web.GraphServiceClient

I vantaggi seguenti rendono Microsoft. Identity.Web.GraphServiceClient l'approccio consigliato per chiamare Microsoft Graph.

  • Acquisizione automatica dei token: gestisce facilmente i token utente e app
  • Memorizzazione nella cache dei token: caching integrato per migliorare le prestazioni
  • API Fluent: chiamate Graph con sicurezza tipologica e compatibili con IntelliSense
  • Consenso incrementale: richiedere ambiti aggiuntivi su richiesta
  • Più schemi di autenticazione: supporto per app Web e API Web
  • Sia la versione 1.0 che la Beta: si usano insieme endpoint stabili e di anteprima

Installare i pacchetti necessari

Installare il pacchetto di integrazione Microsoft Graph SDK:

dotnet add package Microsoft.Identity.Web.GraphServiceClient

Per le API beta Microsoft Graph:

dotnet add package Microsoft.Identity.Web.GraphServiceClientBeta

Configurare ASP.NET Core

1. Configurare i servizi

Aggiungi il supporto di Microsoft Graph alla tua applicazione:

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

Configurare le opzioni graph nel file di configurazione:

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

Configurazione con codice:

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

Oppure configurare direttamente nel codice:

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

3. Configurare il supporto cloud nazionale

Per usare Microsoft Graph nei cloud nazionali, specificare BaseUrl nella configurazione:

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

Consulta Distribuzioni di Microsoft Graph per gli URL degli endpoint.

Usare GraphServiceClient

Inserire GraphServiceClient

GraphServiceClient Inserire dal costruttore:

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

Usare le autorizzazioni delegate (token utente)

Chiamare Graph per conto dell'utente connesso con autorizzazioni delegate.

Recuperare il profilo utente di base

Recuperare le informazioni del profilo dell'utente corrente da 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
        });
    }
}

Richiedere ambiti aggiuntivi in modo dinamico quando l'applicazione ne necessita:

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

Applicare le opzioni di query

Usare le opzioni di query di Graph SDK per filtrare, selezionare e ordinare i risultati:

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

Scorrere i risultati

Gestisci i risultati paginati da Microsoft Graph iterando attraverso ogni pagina.

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

Usare le autorizzazioni dell'applicazione (token solo per app)

Chiamare Graph con le autorizzazioni dell'applicazione quando non è necessario alcun contesto utente.

Chiamare Graph con WithAppOnly()

Usare il WithAppOnly() metodo per effettuare chiamate Graph con autorizzazioni dell'applicazione.

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

Configurare le autorizzazioni dell'app

Specificare una richiesta di token dell'app in appsettings.json:

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

Gli ambiti verranno impostati automaticamente su ["https://graph.microsoft.com/.default"].

Configurare opzioni dettagliate solo per app

Impostare opzioni esplicite di autenticazione solo app nel codice.

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

Gestire più schemi di autenticazione

Se l'app usa più schemi di autenticazione (ad esempio, app Web e API), specificare lo schema da usare:

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

Configurare le opzioni dettagliate dello schema

Impostare lo schema di autenticazione e gli ambiti in modo esplicito nel codice.

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

Usare sia gli endpoint v1.0 che Beta

Registrare e chiamare sia Microsoft Graph versione 1.0 che beta nella stessa applicazione.

1. Installare entrambi i pacchetti

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

2. Registrare entrambi i servizi

using Microsoft.Identity.Web;

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

3. Usare entrambi i client

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

Inviare richieste in batch

Combinare più chiamate Graph in una singola richiesta HTTP per migliorare le prestazioni:

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

Applicare modelli comuni di grafo

Usare questi modelli per eseguire operazioni frequenti Microsoft Graph nell'applicazione.

Recupera il responsabile dell'utente

Recuperare il responsabile dell'utente autenticato dalla directory.

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

Ottenere la foto dell'utente

Esportare la foto del profilo dell'utente connesso in formato di flusso di dati.

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

Invia messaggio di posta elettronica

Inviare un messaggio di posta elettronica per conto dell'utente connesso.

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

Creare un evento del calendario

Creare un nuovo evento del calendario con i partecipanti per l'utente connesso.

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

Cerca utenti

Cercare gli utenti nella directory in base al nome visualizzato o all'indirizzo di posta elettronica.

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

Implementare il supporto OWIN

Per applicazioni ASP.NET che usano OWIN, configurare la fabbrica di acquisizione dei token e registrare i servizi di 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. Chiamare l'API dai controller

Recuperare un'istanza di GraphServiceClient nel controller e chiamare 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);
    }
}

Eseguire la migrazione da Microsoft. Identity.Web.MicrosoftGraph 2.x

Se stai eseguendo la migrazione dal vecchio pacchetto Microsoft.Identity.Web.MicrosoftGraph (SDK 4.x), esamina le seguenti modifiche chiave:

1. Rimuovere il pacchetto precedente e aggiungere nuovo

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

2. Aggiornare le chiamate al metodo

Il .Request() metodo è stato rimosso in 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();

Dopo (SDK 5.x):

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

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

3. Aggiornare la posizione 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. Aggiornare il percorso WithAppOnly()

Before:

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

After:

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

5. Aggiornare conAuthenticationScheme() location

Before:

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

After:

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

Per informazioni dettagliate sulla migrazione, vedere Microsoft Graph .NET SDK v5 changelog.

Gestire gli errori

Gestire ServiceException

Intercettare ODataError e MicrosoftIdentityWebChallengeUserException per gestire correttamente gli errori di API Graph.

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

Seguire le migliori pratiche

Richiedere livelli minimi di accesso

Richiedi solo gli ambiti di cui hai bisogno.

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

Richiedere ambiti aggiuntivi solo quando necessario:

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

3. Cache di GraphServiceClient

GraphServiceClient è sicuro da riutilizzare. Registra come singleton o inserisce da DI.

4. Usare select per ridurre le dimensioni della risposta

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

Risolvere i problemi comuni

Risolvere "Privilegi insufficienti per completare l'operazione"

Causa: l'app non dispone delle autorizzazioni graph necessarie.

Soluzione:

  • Aggiungere le autorizzazioni API necessarie nella registrazione dell'app
  • Consenso amministratore necessario per le autorizzazioni dell'app
  • Consenso utente necessario per le autorizzazioni delegate

Risolvere "AADSTS65001: l'utente o l'amministratore non ha acconsentito"

Causa: l'utente non ha acconsentito agli ambiti richiesti.

Soluzione: usare il consenso incrementale con .WithScopes() per attivare il flusso di consenso.

Risolvere gli errori 404 delle foto

Causa: l'utente non ha una foto del profilo.

Soluzione: gestire correttamente 404 e fornire avatar predefinito.

Risolvere gli errori delle richieste batch

Causa: le singole richieste in batch possono avere esito negativo in modo indipendente.

Soluzione: controllare ogni risposta in batch per verificare la presenza di errori:

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

Successivi passaggi: Scopri come chiamare gli SDK di Azure o usare le API personalizzate.