Llame a Microsoft Graph

Llame a Microsoft Graph desde las aplicaciones de ASP.NET Core y OWIN mediante Microsoft. Identity.Web y el SDK de Microsoft Graph para acceder a Microsoft 365 datos y servicios.

Descripción de la integración de Microsoft Graph

Microsoft Graph proporciona un punto de conexión de API unificado para acceder a datos entre Microsoft 365, Windows y Enterprise Mobility + Security. Microsoft. Identity.Web simplifica la autenticación y la adquisición de tokens para Microsoft Graph, mientras que el SDK de Microsoft Graph proporciona una API fluida y tipada para llamar a los puntos de conexión de Graph.

Elija Microsoft. Identity.Web.GraphServiceClient

Las siguientes ventajas hacen que Microsoft.Identity.Web.GraphServiceClient sea el enfoque recomendado para invocar a Microsoft Graph.

  • Adquisición automática de tokens: controla los tokens de usuario y aplicación sin problemas
  • Almacenamiento en caché de tokens: almacenamiento en caché integrado para el rendimiento
  • Fluent API: llamadas de Graph fáciles de escribir y compatibles con IntelliSense
  • Consentimiento incremental: solicitar ámbitos adicionales a petición
  • Varios esquemas de autenticación: compatibilidad con aplicaciones web y API web
  • Tanto la versión 1.0 como la beta: uso de puntos de conexión estables y en versión preliminar juntos

Instalación de paquetes necesarios

Instale el paquete de integración del SDK de Microsoft Graph:

dotnet add package Microsoft.Identity.Web.GraphServiceClient

Para las API beta de Microsoft Graph:

dotnet add package Microsoft.Identity.Web.GraphServiceClientBeta

Configuración de ASP.NET Core

1. Configurar servicios

Agregue compatibilidad con Microsoft Graph a la aplicación:

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

Configure las opciones de Graph en el archivo de configuración:

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

Configuración con código:

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

O bien, configure directamente en el código:

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

3. Configuración del soporte técnico nacional en la nube

Para usar Microsoft Graph en nubes nacionales, especifique BaseUrl en la configuración:

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

Consulte Microsoft Graph deployments for endpoint URLs (Implementaciones de Microsoft Graph para las direcciones URL del punto de conexión.

Uso de GraphServiceClient

Inyectar GraphServiceClient

Inserte GraphServiceClient desde el constructor:

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

Uso de permisos delegados (tokens de usuario)

Llame a Graph en nombre del usuario que ha iniciado sesión con permisos delegados.

Recuperación del perfil de usuario básico

Recupere la información del perfil del usuario actual de 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
        });
    }
}

Solicite ámbitos adicionales dinámicamente cuando la aplicación las necesite:

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

Aplicar opciones de consulta

Use las opciones de consulta del SDK de Graph para filtrar, seleccionar y ordenar los resultados:

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

Recorrer los resultados

Maneje los resultados paginados de Microsoft Graph mediante la iteración a través de cada página.

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

Uso de permisos de aplicación (tokens exclusivos para aplicaciones)

Llame a Graph con permisos de aplicación cuando no se requiera ningún contexto de usuario.

Llamar a Graph con WithAppOnly()

Use el WithAppOnly() método para realizar llamadas de Graph con permisos de aplicación.

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

Configurar los permisos de aplicaciones

Especifique una solicitud de token de aplicación en appsettings.json:

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

Los ámbitos se establecerán automáticamente en ["https://graph.microsoft.com/.default"].

Configuración de opciones detalladas solo para la aplicación

Establezca opciones explícitas de autenticación solo de aplicación en el código.

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

Control de varios esquemas de autenticación

Si la aplicación usa varios esquemas de autenticación (por ejemplo, aplicación web + API), especifique qué esquema usar:

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

Configuración de opciones de esquema detalladas

Establezca el esquema de autenticación y los ámbitos explícitamente en el código.

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

Uso de puntos de conexión v1.0 y Beta

Registre y llame a Microsoft Graph v1.0 y Beta en la misma aplicación.

1. Instalar ambos paquetes

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

2. Registrar ambos servicios

using Microsoft.Identity.Web;

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

3. Usar ambos clientes

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

Envío de solicitudes por lotes

Combine varias llamadas de Graph en una única solicitud HTTP para mejorar el rendimiento:

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

Aplicación de patrones comunes de Graph

Use estos patrones para realizar operaciones frecuentes de Microsoft Graph en la aplicación.

Obtener el administrador del usuario

Recupere el administrador del usuario que ha iniciado sesión en el directorio.

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

Obtener la foto del usuario

Descargue la foto de perfil del usuario que ha iniciado sesión como un flujo de datos.

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

Enviar correo electrónico

Envíe un mensaje de correo electrónico en nombre del usuario que ha iniciado sesión.

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

Creación de un evento de calendario

Cree un nuevo evento de calendario con asistentes para el usuario que ha iniciado sesión.

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

Buscar usuarios

Busque usuarios en el directorio por nombre de usuario o dirección de correo electrónico.

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

Implementación de la compatibilidad con OWIN

Para aplicaciones ASP.NET que usan OWIN, configure la factoría de adquisición de tokens y registre los servicios de 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. Llamar a la API desde los controladores

Recupere una instancia de GraphServiceClient en el controlador y llame a 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);
    }
}

Migrar desde Microsoft. Identity.Web.MicrosoftGraph 2.x

Si va a migrar desde el paquete anterior Microsoft.Identity.Web.MicrosoftGraph (SDK 4.x), revise los siguientes cambios clave:

1. Quitar el paquete antiguo y agregar nuevo

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

2. Actualizar llamadas al método

El .Request() método se ha quitado en SDK 5.x:

Antes (SDK 4.x):

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

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

Después (SDK 5.x):

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

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

3. Actualizar la ubicación de 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. Actualizar la ubicación de WithAppOnly()

Before:

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

After:

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

5. Actualizar la ubicación de 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));

Consulte Microsoft Graph .NET sdk v5 changelog para obtener detalles completos de la migración.

Manejo de errores

Gestionar ServiceException

Captura ODataError y MicrosoftIdentityWebChallengeUserException para controlar correctamente los fallos de 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");
    }
}

Seguimiento de los procedimientos recomendados

1. Solicitar ámbitos mínimos

Solicite solo los ámbitos que necesita.

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

Solicite ámbitos adicionales solo cuando sea necesario:

// 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

GraphServiceClient es seguro para reutilizar. Regístrese como singleton o inserte desde di.

4. Use select para reducir el tamaño de respuesta

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

Solucionar problemas comunes

Resolución de "Privilegios insuficientes para completar la operación"

Causa: La aplicación no tiene los permisos de Graph necesarios.

Solución:

  • Adición de permisos de API necesarios en el registro de aplicaciones
  • Consentimiento del administrador necesario para los permisos de la aplicación
  • Consentimiento del usuario necesario para permisos delegados

Resolver "AADSTS65001: el usuario o el administrador no han consentido"

Causa: El usuario no ha aceptado los ámbitos solicitados.

Solución: use el consentimiento incremental con .WithScopes() para desencadenar el flujo de consentimiento.

Resolución de errores de foto 404

Causa: El usuario no tiene una foto de perfil.

Solución: controle 404 correctamente y proporcione un avatar predeterminado.

Resolución de errores de solicitud por lotes

Causa: las solicitudes individuales en el lote pueden producir errores de forma independiente.

Solución: compruebe cada respuesta por lotes para ver si hay errores:

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

Next Steps: Obtenga información sobre calling SDK de Azure o custom API.