Crear Microsoft Teams aplicaciones con Microsoft Graph
Este tutorial te enseña a crear una aplicación Microsoft Teams con ASP.NET Core y la API de Microsoft Graph para recuperar información de calendario para un usuario.
Sugerencia
Si prefiere descargar el tutorial completado, puede descargar o clonar el repositorio GitHub archivo. Consulta el archivo README en la carpeta de demostración para obtener instrucciones sobre cómo configurar la aplicación con un identificador de aplicación y un secreto.
Requisitos previos
Antes de iniciar este tutorial, debe tener lo siguiente instalado en el equipo de desarrollo.
También debe tener una cuenta laboral o educativa de Microsoft en un espacio empresarial Microsoft 365 que haya habilitado la instalación Teams aplicación local. Si no tienes una cuenta laboral o educativa de Microsoft o tu organización no ha habilitado la instalación local de aplicaciones de Teams personalizadas, puedes registrarte en el Programa de desarrolladores de Microsoft 365 para obtener una suscripción Office 365 desarrollador gratuita.
Nota
Este tutorial se escribió con .NET SDK versión 5.0.302. Los pasos de esta guía pueden funcionar con otras versiones, pero eso no se ha probado.
Comentarios
Proporcione cualquier comentario sobre este tutorial en el repositorio GitHub archivo.
Crear una aplicación web ASP.NET Core MVC
Microsoft Teams de pestañas tienen varias opciones para autenticar al usuario y llamar a Microsoft Graph. En este ejercicio, implementará una pestaña que realiza el inicio de sesión único para obtener un token de autenticación en el cliente y, a continuación, usará el flujo en nombre del servidor para intercambiar ese token para obtener acceso a Microsoft Graph.
Para otras alternativas, vea lo siguiente.
- Cree una Microsoft Teams con microsoft Graph Toolkit. Este ejemplo es completamente del lado cliente y usa microsoft Graph Toolkit para controlar la autenticación y realizar llamadas a Microsoft Graph.
- Microsoft Teams ejemplo de autenticación . Este ejemplo contiene varios ejemplos que cubren diferentes escenarios de autenticación.
Cree el proyecto
Comience creando una aplicación ASP.NET Core web.
Abra la interfaz de línea de comandos (CLI) en un directorio donde desee crear el proyecto. Ejecuta el siguiente comando.
dotnet new webapp -o GraphTutorial
Una vez creado el proyecto, compruebe que funciona cambiando el directorio actual al directorio GraphTutorial y ejecutando el siguiente comando en la CLI.
dotnet run
Abra el explorador y vaya a
https://localhost:5001
. Si todo funciona, debería ver una página ASP.NET Core predeterminada.
Importante
Si recibe una advertencia de que el certificado de localhost no es de confianza, puede usar la CLI de .NET Core para instalar y confiar en el certificado de desarrollo. Consulta Aplicar HTTPS en ASP.NET Core para obtener instrucciones para sistemas operativos específicos.
Agregar paquetes NuGet
Antes de seguir, instala algunos paquetes NuGet que usarás más adelante.
- Microsoft.Identity.Web para autenticar y solicitar tokens de acceso.
- Microsoft.Identity.Web.MicrosoftGraph para agregar Microsoft Graph compatibilidad configurada con Microsoft.Identity.Web.
- Microsoft. Graph para actualizar la versión de este paquete instalada por Microsoft.Identity.Web.MicrosoftGraph.
- TimeZoneConverter para traducir Windows de zona horaria a identificadores de IANA.
Ejecute los siguientes comandos en la CLI para instalar las dependencias.
dotnet add package Microsoft.Identity.Web --version 1.15.2 dotnet add package Microsoft.Identity.Web.MicrosoftGraph --version 1.15.2 dotnet add package Microsoft.Graph --version 4.1.0 dotnet add package TimeZoneConverter
Diseñar la aplicación
En esta sección, crearás la estructura básica de la interfaz de usuario de la aplicación.
Sugerencia
Puede usar cualquier editor de texto para editar los archivos de origen de este tutorial. Sin embargo, Visual Studio Code proporciona características adicionales, como la depuración y Intellisense.
Abra ./Pages/Shared/_Layout.cshtml y reemplace todo su contenido por el siguiente código para actualizar el diseño global de la aplicación.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - GraphTutorial</title> <link rel="stylesheet" href="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/11.0.0/css/fabric.min.css" /> <link rel="stylesheet" href="~/css/site.css" /> </head> <body class="ms-Fabric"> <div class="container"> <main role="main"> @RenderBody() </main> </div> <script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="https://statics.teams.cdn.office.net/sdk/v1.7.0/js/MicrosoftTeams.min.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> @RenderSection("Scripts", required: false) </body> </html>
Esto reemplaza Bootstrap con Fluent interfaz deusuario, agrega el SDKde Microsoft Teams y simplifica el diseño.
Abra ./wwwroot/js/site.js y agregue el siguiente código.
(function () { // Support Teams themes microsoftTeams.initialize(); // On load, match the current theme microsoftTeams.getContext((context) => { if(context.theme !== 'default') { // For Dark and High contrast, set text to white document.body.style.color = '#fff'; document.body.style.setProperty('--border-style', 'solid'); } }); // Register event listener for theme change microsoftTeams.registerOnThemeChangeHandler((theme)=> { if(theme !== 'default') { document.body.style.color = '#fff'; document.body.style.setProperty('--border-style', 'solid'); } else { // For default theme, remove inline style document.body.style.color = ''; document.body.style.setProperty('--border-style', 'none'); } }); })();
Esto agrega un controlador de cambios de tema simple para cambiar el color de texto predeterminado para los temas oscuros y de contraste alto.
Abra ./wwwroot/css/site.css y reemplace su contenido por lo siguiente.
:root { --border-style: none; } .tab-title { margin-bottom: .5em; } .event-card { margin: .5em; padding: 1em; border-style: var(--border-style); border-width: 1px; border-color: #fff; } .event-card div { margin-bottom: .25em; } .event-card .ms-Icon { margin-right: 10px; float: left; position: relative; top: 3px; } .event-card .ms-Icon--MapPin { top: 2px; } .form-container { max-width: 720px; } .form-label { display: block; margin-bottom: .25em; } .form-input { width: 100%; margin-bottom: .25em; padding: .5em; box-sizing: border-box; } .form-button { padding: .5em; } .result-panel { display: none; padding: 1em; margin: 1em; } .error-msg { color: red; } .success-msg { color: green; }
Abra ./Pages/Index.cshtml y reemplace su contenido por el código siguiente.
@page @model IndexModel @{ ViewData["Title"] = "Home page"; } <div id="tab-container"> <h1 class="ms-fontSize-24 ms-fontWeight-semibold">Loading...</h1> </div> @section Scripts { <script> </script> }
Abra ./Startup.cs y quite la
app.UseHttpsRedirection();
línea delConfigure
método. Esto es necesario para que funcione el túnel de ngrok.
Ejecutar ngrok
Microsoft Teams no admite el hospedaje local para aplicaciones. El servidor que hospeda la aplicación debe estar disponible desde la nube mediante puntos de conexión HTTPS. Para depurar localmente, puede usar ngrok para crear una dirección URL pública para el proyecto hospedado localmente.
Abra la CLI y ejecute el siguiente comando para iniciar ngrok.
ngrok http 5000
Una vez que se inicie ngrok, copie la dirección URL de reenvío HTTPS. Debería tener el aspecto
https://50153897dd4d.ngrok.io
de . Necesitará este valor en pasos posteriores.
Importante
Si usa la versión gratuita de ngrok, la dirección URL de reenvío cambia cada vez que reinicia ngrok. Se recomienda dejar ngrok en ejecución hasta completar este tutorial para mantener la misma dirección URL. Si tienes que reiniciar ngrok, tendrás que actualizar la dirección URL en todas partes en las que se usa y volver a instalar la aplicación en Microsoft Teams.
Registrar la aplicación en el portal
En este ejercicio, creará un nuevo registro de aplicaciones web de Azure AD mediante el Centro Azure Active Directory administración.
Abra un explorador y vaya al centro de administración de Azure Active Directory. Inicie sesión con una cuenta personal (también conocida como: cuenta Microsoft) o una cuenta profesional o educativa.
Seleccione Azure Active Directory en el panel de navegación izquierdo y, a continuación, seleccione Registros de aplicaciones en Administrar.
Seleccione Nuevo registro. En la página Registrar una aplicación, establezca los valores de la siguiente manera, donde está la dirección URL de reenvío
YOUR_NGROK_URL
de ngrok que copió en la sección anterior.- Establezca Nombre como
Teams Graph Tutorial
. - Establezca Tipos de cuenta admitidos en Cuentas en cualquier directorio de organización y cuentas personales de Microsoft.
- En URI de redirección, establezca la primera lista desplegable en
Web
y establezca el valorYOUR_NGROK_URL/authcomplete
.
- Establezca Nombre como
Seleccione Registrar. En la Teams Graph tutorial, copie el valor del identificador de aplicación (cliente) y guárdelo, lo necesitará en el paso siguiente.
Seleccione Autenticación en Administrar. Busque la sección Concesión implícita y habilite los tokens de Access y los tokens de id.. Seleccione Guardar.
Seleccione Certificados y secretos en Administrar. Seleccione el botón Nuevo secreto de cliente. Escriba un valor en Descripción, y seleccione una de las opciones para Expira, y después, Agregar.
Copie el valor del secreto de cliente antes de salir de esta página. Lo necesitará en el siguiente paso.
Importante
El secreto de cliente no se vuelve a mostrar, así que asegúrese de copiarlo en este momento.
Seleccione Permisos de API en Administrar y, a continuación, seleccione Agregar un permiso.
Seleccione Microsoft Graph y, a continuación, Permisos delegados.
Seleccione los siguientes permisos y, a continuación, seleccione Agregar permisos.
- Calendars.ReadWrite: esto permitirá que la aplicación lea y escriba en el calendario del usuario.
- MailboxSettings.Read: esto permitirá a la aplicación obtener la zona horaria, el formato de fecha y el formato de hora del usuario desde la configuración de su buzón.
Configurar Teams inicio de sesión único
En esta sección, actualizarás el registro de la aplicación para admitir el inicio de sesión único en Teams.
Seleccione Exponer una API. Seleccione el vínculo Establecer junto al URI de id. de aplicación. Inserte el nombre de dominio de dirección URL de reenvío de ngrok (con una barra diagonal "/" anexada al final) entre las barras diagonales dobles y el GUID. El identificador completo debe ser similar a:
api://50153897dd4d.ngrok.io/ae7d8088-3422-4c8c-a351-6ded0f21d615
.En la sección Ámbitos definidos por esta API, seleccione Agregar un ámbito. Rellene los campos de la siguiente manera y seleccione Agregar ámbito.
- Nombre del ámbito:
access_as_user
- Quién¿puede dar su consentimiento?: Administradores y usuarios
- Nombre para mostrar del consentimiento de administrador:
Access the app as the user
- Descripción del consentimiento de administrador:
Allows Teams to call the app's web APIs as the current user.
- Nombre para mostrar del consentimiento del usuario:
Access the app as you
- Descripción del consentimiento del usuario:
Allows Teams to call the app's web APIs as you.
- Estado: habilitado
- Nombre del ámbito:
En la sección Aplicaciones cliente autorizadas, seleccione Agregar una aplicación cliente. Escriba un identificador de cliente en la siguiente lista, habilite el ámbito en Ámbitos autorizados y seleccione Agregar aplicación. Repita este proceso para cada uno de los IDs de cliente de la lista.
1fec8e78-bce4-4aaf-ab1b-5451cc387264
(Teams aplicación móvil/de escritorio)5e3ce6c0-2b1f-4285-8d4b-75ee78787346
(Teams web)
Crear manifiesto de aplicación
El manifiesto de la aplicación describe cómo se integra la aplicación con Microsoft Teams y se requiere para instalar aplicaciones. En esta sección, usarás App Studio en el Microsoft Teams para generar un manifiesto.
Si aún no tienes App Studio instalado en Teams, instáleslo ahora.
Inicie App Studio en Microsoft Teams y seleccione el editor de manifiestos.
Selecciona Crear una nueva aplicación.
En la página Detalles de la aplicación, rellene los campos necesarios.
Nota
Puedes usar los iconos predeterminados en la sección Personal de marca o cargar los tuyos propios.
En el menú de la izquierda, seleccione Pestañas en Funcionalidades.
Seleccione Agregar en Agregar una pestaña personal.
Rellene los campos de la siguiente manera, donde está la dirección URL de reenvío
YOUR_NGROK_URL
que copió en la sección anterior. Cuando termine, seleccione Guardar.- Nombre:
Create event
- Id. de entidad:
createEventTab
- Dirección URL de contenido:
YOUR_NGROK_URL/newevent
- Nombre:
Seleccione Agregar en Agregar una pestaña personal.
Rellene los campos de la siguiente manera, donde está la dirección URL de reenvío
YOUR_NGROK_URL
que copió en la sección anterior. Cuando termine, seleccione Guardar.- Nombre:
Graph calendar
- Id. de entidad:
calendarTab
- Dirección URL de contenido:
YOUR_NGROK_URL
- Nombre:
En el menú de la izquierda, seleccione Dominios y permisos en Finalizar.
Establece el identificador de aplicación de AAD en el identificador de aplicación desde el registro de la aplicación.
Establece el campo Inicio de sesión único en el URI del identificador de aplicación desde el registro de la aplicación.
En el menú de la izquierda, seleccione Probar y distribuir en Finalizar. Seleccione Descargar.
Cree un nuevo directorio en la raíz del proyecto denominado Manifiesto. Extraiga el contenido del archivo ZIP descargado en este directorio.
Agregar autenticación de Azure AD
En este ejercicio, extenderá la aplicación desde el ejercicio anterior para admitir la autenticación de inicio de sesión único con Azure AD. Esto es requerido para obtener el token de acceso OAuth necesario para llamar a la API de Microsoft Graph. En este paso, configurará la biblioteca Microsoft.Identity.Web.
Importante
Para evitar almacenar el identificador de aplicación y el secreto en el origen, usará el Administrador de secretos de .NET para almacenar estos valores. El Administrador de secretos solo tiene fines de desarrollo, las aplicaciones de producción deben usar un administrador de secretos de confianza para almacenar secretos.
Abra ./appsettings.jsy reemplace su contenido por lo siguiente.
{ "AzureAd": { "Instance": "https://login.microsoftonline.com/", "TenantId": "common" }, "Graph": { "Scopes": "https://graph.microsoft.com/.default" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" }
Abra la CLI en el directorio donde se encuentra GraphTutorial.csproj y ejecute los siguientes comandos, sustituyéndolo por el identificador de la aplicación desde Azure Portal y con el secreto de la
YOUR_APP_ID
YOUR_APP_SECRET
aplicación.dotnet user-secrets init dotnet user-secrets set "AzureAd:ClientId" "YOUR_APP_ID" dotnet user-secrets set "AzureAd:ClientSecret" "YOUR_APP_SECRET"
Implementar el inicio de sesión
En primer lugar, implemente el inicio de sesión único en el código JavaScript de la aplicación. Usará el SDK de JavaScript de Microsoft Teams para obtener un token de acceso que permita que el código JavaScript que se ejecuta en el cliente de Teams realice llamadas AJAX a la API web que implementará más adelante.
Abra ./Pages/Index.cshtml y agregue el siguiente código dentro de la
<script>
etiqueta.(function () { if (microsoftTeams) { microsoftTeams.initialize(); microsoftTeams.authentication.getAuthToken({ successCallback: (token) => { // TEMPORARY: Display the access token for debugging $('#tab-container').empty(); $('<code/>', { text: token, style: 'word-break: break-all;' }).appendTo('#tab-container'); }, failureCallback: (error) => { renderError(error); } }); } })(); function renderError(error) { $('#tab-container').empty(); $('<h1/>', { text: 'Error' }).appendTo('#tab-container'); $('<code/>', { text: JSON.stringify(error, Object.getOwnPropertyNames(error)), style: 'word-break: break-all;' }).appendTo('#tab-container'); }
Esto llama a
microsoftTeams.authentication.getAuthToken
la autenticación silenciosa como el usuario que ha iniciado sesión en Teams. Normalmente no hay ninguna solicitud de interfaz de usuario implicada, a menos que el usuario tenga que dar su consentimiento. A continuación, el código muestra el token en la pestaña.Guarde los cambios e inicie la aplicación ejecutando el siguiente comando en la CLI.
dotnet run
Importante
Si ha reiniciado ngrok y la dirección URL de ngrok ha cambiado, asegúrese de actualizar el valor de ngrok en el siguiente lugar antes de probar.
- Uri de redireccionamiento en el registro de la aplicación
- Uri del identificador de aplicación en el registro de la aplicación
contentUrl
en manifest.jsenvalidDomains
en manifest.jsenresource
en manifest.jsen
Cree un archivo ZIP conmanifest.js, color.png y outline.png.
En Microsoft Teams, selecciona Aplicaciones en la barra izquierda, selecciona Upload una aplicación personalizada y, a continuación, selecciona Upload para mí o mis equipos.
Vaya al archivo ZIP que creó anteriormente y seleccione Abrir.
Revise la información de la aplicación y seleccione Agregar.
La aplicación se abre en Teams y muestra un token de acceso.
Si copia el token, puede pegarlo enjwt.ms . Compruebe que la audiencia (la notificación) es su identificador de aplicación y que el único ámbito aud
(la scp
notificación) es el access_as_user
ámbito de la API que creó. Esto significa que este token no concede acceso directo a Microsoft Graph! En su lugar, la API web que implementará pronto tendrá que intercambiar este token mediante el flujo en nombre del usuario para obtener un token que funcionará con las llamadas Graph Microsoft.
Configurar la autenticación en la ASP.NET Core aplicación
Comience agregando los servicios de la plataforma Microsoft Identity a la aplicación.
Abra el archivo ./Startup.cs y agregue la siguiente
using
instrucción a la parte superior del archivo.using Microsoft.Identity.Web;
Agregue la siguiente línea justo antes de
app.UseAuthorization();
la línea de laConfigure
función.app.UseAuthentication();
Agregue la siguiente línea justo después de
endpoints.MapRazorPages();
la línea de laConfigure
función.endpoints.MapControllers();
Reemplace la función
ConfigureServices
existente por lo siguiente.public void ConfigureServices(IServiceCollection services) { // Use Web API authentication (default JWT bearer token scheme) services.AddMicrosoftIdentityWebApiAuthentication(Configuration) // Enable token acquisition via on-behalf-of flow .EnableTokenAcquisitionToCallDownstreamApi() // Specify that the down-stream API is Graph .AddMicrosoftGraph(Configuration.GetSection("Graph")) // Use in-memory token cache // See https://github.com/AzureAD/microsoft-identity-web/wiki/token-cache-serialization .AddInMemoryTokenCaches(); services.AddRazorPages(); services.AddControllers().AddNewtonsoftJson(); }
Este código configura la aplicación para permitir que las llamadas a las API web se autentiquen en función del token portador JWT en el
Authorization
encabezado. También agrega los servicios de adquisición de tokens que pueden intercambiar ese token a través del flujo en nombre del usuario.
Crear el controlador de API web
Cree un nuevo directorio en la raíz del proyecto denominado Controladores.
Cree un nuevo archivo en el directorio ./Controllers denominado CalendarController.cs y agregue el siguiente código.
using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Identity.Web; using Microsoft.Identity.Web.Resource; using Microsoft.Graph; using TimeZoneConverter; namespace GraphTutorial.Controllers { [ApiController] [Route("[controller]")] [Authorize] public class CalendarController : ControllerBase { private static readonly string[] apiScopes = new[] { "access_as_user" }; private readonly GraphServiceClient _graphClient; private readonly ITokenAcquisition _tokenAcquisition; private readonly ILogger<CalendarController> _logger; public CalendarController(ITokenAcquisition tokenAcquisition, GraphServiceClient graphClient, ILogger<CalendarController> logger) { _tokenAcquisition = tokenAcquisition; _graphClient = graphClient; _logger = logger; } [HttpGet] public async Task<ActionResult<string>> Get() { // This verifies that the access_as_user scope is // present in the bearer token, throws if not HttpContext.VerifyUserHasAnyAcceptedScope(apiScopes); // To verify that the identity libraries have authenticated // based on the token, log the user's name _logger.LogInformation($"Authenticated user: {User.GetDisplayName()}"); try { // TEMPORARY // Get a Graph token via OBO flow var token = await _tokenAcquisition .GetAccessTokenForUserAsync(new[]{ "User.Read", "MailboxSettings.Read", "Calendars.ReadWrite" }); // Log the token _logger.LogInformation($"Access token for Graph: {token}"); return Ok("{ \"status\": \"OK\" }"); } catch (MicrosoftIdentityWebChallengeUserException ex) { _logger.LogError(ex, "Consent required"); // This exception indicates consent is required. // Return a 403 with "consent_required" in the body // to signal to the tab it needs to prompt for consent return new ContentResult { StatusCode = (int)HttpStatusCode.Forbidden, ContentType = "text/plain", Content = "consent_required" }; } catch (Exception ex) { _logger.LogError(ex, "Error occurred"); throw; } } } }
Esto implementa una API web ( ) a
GET /calendar
la que se puede llamar desde la Teams pestaña. Por ahora, simplemente intenta intercambiar el token de portador por un token Graph. La primera vez que un usuario carga la pestaña, se producirá un error porque aún no han dado su consentimiento para permitir el acceso de la aplicación a Microsoft Graph en su nombre.Abra ./Pages/Index.cshtml y reemplace la
successCallback
función por la siguiente.successCallback: (token) => { // TEMPORARY: Call the Web API fetch('/calendar', { headers: { 'Authorization': `Bearer ${token}` } }).then(response => { response.text() .then(body => { $('#tab-container').empty(); $('<code/>', { text: body }).appendTo('#tab-container'); }); }).catch(error => { console.error(error); renderError(error); }); }
Esto llamará a la API web y mostrará la respuesta.
Guarde los cambios y reinicie la aplicación. Actualice la pestaña en Microsoft Teams. La página debe mostrar
consent_required
.Revise el resultado del registro en la CLI. Observe dos cosas.
- Una entrada como
Authenticated user: MeganB@contoso.com
. La API web ha autenticado al usuario en función del token enviado con la solicitud de API. - Una entrada como
AADSTS65001: The user or administrator has not consented to use the application with ID...
. Esto se espera, ya que aún no se ha solicitado al usuario el consentimiento para los ámbitos de permisos Graph Microsoft solicitados.
- Una entrada como
Implementar solicitud de consentimiento
Dado que la API web no puede preguntar al usuario, la Teams tendrá que implementar un mensaje. Esto solo tendrá que hacerse una vez para cada usuario. Una vez que un usuario da su consentimiento, no es necesario volver a confirmarlo a menos que revoque explícitamente el acceso a la aplicación.
Cree un nuevo archivo en el directorio ./Pages denominado Authenticate.cshtml.cs y agregue el siguiente código.
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace GraphTutorial.Pages { public class AuthenticateModel : PageModel { private readonly ILogger<IndexModel> _logger; public string ApplicationId { get; private set; } public string State { get; private set; } public string Nonce { get; private set; } public AuthenticateModel(IConfiguration configuration, ILogger<IndexModel> logger) { _logger = logger; // Read the application ID from the // configuration. This is used to build // the authorization URL for the consent prompt ApplicationId = configuration .GetSection("AzureAd") .GetValue<string>("ClientId"); // Generate a GUID for state and nonce State = System.Guid.NewGuid().ToString(); Nonce = System.Guid.NewGuid().ToString(); } } }
Cree un nuevo archivo en el directorio ./Pages denominado Authenticate.cshtml y agregue el siguiente código.
@page <!-- Copyright (c) Microsoft Corporation. Licensed under the MIT License. --> @model AuthenticateModel @section Scripts { <script> (function () { microsoftTeams.initialize(); // Save the state so it can be verified in // AuthComplete.cshtml localStorage.setItem('auth-state', '@Model.State'); // Get the context for tenant ID and login hint microsoftTeams.getContext((context) => { // Set all of the query parameters for an // authorization request const queryParams = { client_id: '@Model.ApplicationId', response_type: 'id_token token', response_mode: 'fragment', scope: 'https://graph.microsoft.com/.default openid', redirect_uri: `${window.location.origin}/authcomplete`, nonce: '@Model.Nonce', state: '@Model.State', login_hint: context.loginHint, }; // Generate the URL const authEndpoint = `https://login.microsoftonline.com/${context.tid}/oauth2/v2.0/authorize?${toQueryString(queryParams)}`; // Browse to the URL window.location.assign(authEndpoint); }); })(); // Helper function to build a query string from an object function toQueryString(queryParams) { let encodedQueryParams = []; for (let key in queryParams) { encodedQueryParams.push(key + '=' + encodeURIComponent(queryParams[key])); } return encodedQueryParams.join('&'); } </script> }
Cree un nuevo archivo en el directorio ./Pages denominado AuthComplete.cshtml y agregue el siguiente código.
@page <!-- Copyright (c) Microsoft Corporation. Licensed under the MIT License. --> @section Scripts { <script> (function () { microsoftTeams.initialize(); const hashParams = getHashParameters(); if (hashParams['error']) { microsoftTeams.authentication.notifyFailure(hashParams['error']); } else if (hashParams['access_token']) { // Check the state parameter const expectedState = localStorage.getItem('auth-state'); if (expectedState !== hashParams['state']) { microsoftTeams.authentication.notifyFailure('StateDoesNotMatch'); } else { // State parameter matches, report success localStorage.removeItem('auth-state'); microsoftTeams.authentication.notifySuccess('Success'); } } else { microsoftTeams.authentication.notifyFailure('NoTokenInResponse'); } })(); // Helper function to generate a hash from // a query string function getHashParameters() { let hashParams = {}; location.hash.substr(1).split('&').forEach(function(item) { let s = item.split('='), k = s[0], v = s[1] && decodeURIComponent(s[1]); hashParams[k] = v; }); return hashParams; } </script> }
Abra ./Pages/Index.cshtml y agregue las siguientes funciones dentro de la
<script>
etiqueta.function loadUserCalendar(token, callback) { // Call the API fetch('/calendar', { headers: { 'Authorization': `Bearer ${token}` } }).then(response => { if (response.ok) { // Get the JSON payload response.json() .then(events => { callback(events); }); } else if (response.status === 403) { response.text() .then(body => { // If the API sent 'consent_required' // we need to prompt the user if (body === 'consent_required') { promptForConsent((error) => { if (error) { renderError(error); } else { // Retry API call loadUserCalendar(token, callback); } }); } }); } }).catch(error => { renderError(error); }); } function promptForConsent(callback) { // Cause Teams to popup a window for consent microsoftTeams.authentication.authenticate({ url: `${window.location.origin}/authenticate`, width: 600, height: 535, successCallback: (result) => { callback(null); }, failureCallback: (error) => { callback(error); } }); }
Agregue la siguiente función dentro de la
<script>
etiqueta para mostrar un resultado correcto de la API web.function renderCalendar(events) { $('#tab-container').empty(); $('<pre/>').append($('<code/>', { text: JSON.stringify(events, null, 2), style: 'word-break: break-all;' })).appendTo('#tab-container'); }
Reemplace el existente
successCallback
por el código siguiente.successCallback: (token) => { loadUserCalendar(token, (events) => { renderCalendar(events); }); }
Guarde los cambios y reinicie la aplicación. Actualice la pestaña en Microsoft Teams. Debe obtener una ventana emergente que pida su consentimiento a los ámbitos de permisos de Graph Microsoft. Después de aceptar, la pestaña debe mostrar
{ "status": "OK" }
.Nota
Si se muestra la pestaña , deshabilite los bloqueadores de elementos
"FailedToOpenWindow"
emergentes en el explorador y vuelva a cargar la página.Revise el resultado del registro. Debería ver la
Access token for Graph
entrada. Si analiza ese token, observará que contiene los ámbitos de microsoft Graph configurados enappsettings.jsen.
Almacenar y actualizar tokens
En este momento, la aplicación tiene un token de acceso, que se envía en el Authorization
encabezado de las llamadas API. Este es el token que permite a la aplicación obtener acceso a Microsoft Graph en nombre del usuario.
Sin embargo, este token es de corta duración. El token expira una hora después de su emisión. Aquí es donde el token de actualización resulta útil. El token de actualización permite a la aplicación solicitar un nuevo token de acceso sin requerir que el usuario vuelva a iniciar sesión.
Dado que la aplicación usa la biblioteca Microsoft.Identity.Web, no es necesario implementar ninguna lógica de actualización o almacenamiento de tokens.
La aplicación usa la memoria caché de tokens en memoria, lo que es suficiente para las aplicaciones que no necesitan conservar tokens cuando se reinicia la aplicación. En su lugar, las aplicaciones de producción pueden usar las opciones de caché distribuida en la biblioteca Microsoft.Identity.Web.
El método controla la expiración y actualización del token GetAccessTokenForUserAsync
por usted. Primero comprueba el token almacenado en caché y, si no ha expirado, lo devuelve. Si ha expirado, usa el token de actualización en caché para obtener uno nuevo.
GraphServiceClient que los controladores obtienen a través de la inserción de dependencias está preconfigurado con un proveedor de autenticación que lo GetAccessTokenForUserAsync
usa.
Obtener una vista de calendario
En esta sección, incorporará Microsoft Graph a la aplicación. Para esta aplicación, usará la Biblioteca de cliente de Microsoft Graph para .NET para realizar llamadas a Microsoft Graph.
Obtener una vista de calendario
Una vista de calendario es un conjunto de eventos del calendario del usuario que se producen entre dos puntos de tiempo. Lo usarás para obtener los eventos del usuario de la semana actual.
Abra ./Controllers/CalendarController.cs y agregue la siguiente función a la clase CalendarController.
private DateTime GetUtcStartOfWeekInTimeZone(DateTime today, string timeZoneId) { // Time zone returned by Graph could be Windows or IANA style // TimeZoneConverter can take either TimeZoneInfo userTimeZone = TZConvert.GetTimeZoneInfo(timeZoneId); // Assumes Sunday as first day of week int diff = System.DayOfWeek.Sunday - today.DayOfWeek; // create date as unspecified kind var unspecifiedStart = DateTime.SpecifyKind(today.AddDays(diff), DateTimeKind.Unspecified); // convert to UTC return TimeZoneInfo.ConvertTimeToUtc(unspecifiedStart, userTimeZone); }
Agregue la siguiente función para controlar las excepciones devueltas de Microsoft Graph llamadas.
private async Task HandleGraphException(Exception exception) { if (exception is MicrosoftIdentityWebChallengeUserException) { _logger.LogError(exception, "Consent required"); // This exception indicates consent is required. // Return a 403 with "consent_required" in the body // to signal to the tab it needs to prompt for consent HttpContext.Response.ContentType = "text/plain"; HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; await HttpContext.Response.WriteAsync("consent_required"); } else if (exception is ServiceException) { var serviceException = exception as ServiceException; _logger.LogError(serviceException, "Graph service error occurred"); HttpContext.Response.ContentType = "text/plain"; HttpContext.Response.StatusCode = (int)serviceException.StatusCode; await HttpContext.Response.WriteAsync(serviceException.Error.ToString()); } else { _logger.LogError(exception, "Error occurred"); HttpContext.Response.ContentType = "text/plain"; HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; await HttpContext.Response.WriteAsync(exception.ToString()); } }
Reemplace la función
Get
existente por lo siguiente.[HttpGet] public async Task<IEnumerable<Event>> Get() { // This verifies that the access_as_user scope is // present in the bearer token, throws if not HttpContext.VerifyUserHasAnyAcceptedScope(apiScopes); // To verify that the identity libraries have authenticated // based on the token, log the user's name _logger.LogInformation($"Authenticated user: {User.GetDisplayName()}"); try { // Get the user's mailbox settings var me = await _graphClient.Me .Request() .Select(u => new { u.MailboxSettings }) .GetAsync(); // Get the start and end of week in user's time // zone var startOfWeek = GetUtcStartOfWeekInTimeZone( DateTime.Today, me.MailboxSettings.TimeZone); var endOfWeek = startOfWeek.AddDays(7); // Set the start and end of the view var viewOptions = new List<QueryOption> { new QueryOption("startDateTime", startOfWeek.ToString("o")), new QueryOption("endDateTime", endOfWeek.ToString("o")) }; // Get the user's calendar view var results = await _graphClient.Me .CalendarView .Request(viewOptions) // Send user time zone in request so date/time in // response will be in preferred time zone .Header("Prefer", $"outlook.timezone=\"{me.MailboxSettings.TimeZone}\"") // Get max 50 per request .Top(50) // Only return fields app will use .Select(e => new { e.Subject, e.Organizer, e.Start, e.End, e.Location }) // Order results chronologically .OrderBy("start/dateTime") .GetAsync(); return results.CurrentPage; } catch (Exception ex) { await HandleGraphException(ex); return null; } }
Revise los cambios. Esta nueva versión de la función:
- Devuelve
IEnumerable<Event>
en lugar destring
. - Obtiene la configuración del buzón del usuario mediante Microsoft Graph.
- Usa la zona horaria del usuario para calcular el inicio y el final de la semana actual.
- Obtiene una vista de calendario
- Usa la función para incluir un encabezado, lo que hace que los eventos devueltos tengan sus horas de inicio y finalización convertida en la
.Header()
zona horaria delPrefer: outlook.timezone
usuario. - Usa la
.Top()
función para solicitar como máximo 50 eventos. - Usa la
.Select()
función para solicitar solo los campos usados por la aplicación. - Usa la
OrderBy()
función para ordenar los resultados por hora de inicio.
- Usa la función para incluir un encabezado, lo que hace que los eventos devueltos tengan sus horas de inicio y finalización convertida en la
- Devuelve
Guarde los cambios y reinicie la aplicación. Actualice la pestaña en Microsoft Teams. La aplicación muestra una lista JSON de los eventos.
Mostrar los resultados
Ahora puedes mostrar la lista de eventos de una forma más fácil de usar.
Abra ./Pages/Index.cshtml y agregue las siguientes funciones dentro de la
<script>
etiqueta.function renderSubject(subject) { if (!subject || subject.length <= 0) { subject = '<No subject>'; } return $('<div/>', { class: 'ms-fontSize-18 ms-fontWeight-bold', text: subject }); } function renderOrganizer(organizer) { return $('<div/>', { class: 'ms-fontSize-14 ms-fontWeight-semilight', text: organizer.emailAddress.name }).append($('<i/>', { class: 'ms-Icon ms-Icon--PartyLeader', style: 'margin-right: 10px;' })); } function renderTimeSpan(start, end) { return $('<div/>', { class: 'ms-fontSize-14 ms-fontWeight-semilight', text: `${formatDateTime(start.dateTime)} - ${formatDateTime(end.dateTime)}` }).append($('<i/>', { class: 'ms-Icon ms-Icon--DateTime2', style: 'margin-right: 10px;' })); } function formatDateTime(dateTime) { const date = new Date(dateTime); // Format like 10/14/2020 4:00 PM let hours = date.getHours(); const minutes = date.getMinutes(); const ampm = hours >= 12 ? 'PM' : 'AM'; hours = hours % 12; hours = hours ? hours : 12; const minStr = minutes < 10 ? `0${minutes}` : minutes; return `${date.getMonth()+1}/${date.getDate()}/${date.getFullYear()} ${hours}:${minStr} ${ampm}`; } function renderLocation(location) { if (!location || location.displayName.length <= 0) { return null; } return $('<div/>', { class: 'ms-fontSize-14 ms-fontWeight-semilight', text: location.displayName }).append($('<i/>', { class: 'ms-Icon ms-Icon--MapPin', style: 'margin-right: 10px;' })); }
Reemplace la función
renderCalendar
existente por lo siguiente.function renderCalendar(events) { $('#tab-container').empty(); // Add title $('<div/>', { class: 'tab-title ms-fontSize-42', text: 'Week at a glance' }).appendTo('#tab-container'); // Render each event events.map(event => { const eventCard = $('<div/>', { class: 'event-card ms-depth-4', }); eventCard.append(renderSubject(event.subject)); eventCard.append(renderOrganizer(event.organizer)); eventCard.append(renderTimeSpan(event.start, event.end)); const location = renderLocation(event.location); if (location) { eventCard.append(location); } eventCard.appendTo('#tab-container'); }); }
Guarde los cambios y reinicie la aplicación. Actualice la pestaña en Microsoft Teams. La aplicación muestra eventos en el calendario del usuario.
Crear un nuevo evento
En esta sección, agregará la capacidad de crear eventos en el calendario del usuario.
Crear la nueva pestaña de eventos
Cree un nuevo archivo en el directorio ./Pages denominado NewEvent.cshtml y agregue el siguiente código.
@page <!-- Copyright (c) Microsoft Corporation. Licensed under the MIT License. --> @{ ViewData["Title"] = "New event"; } <div class="form-container"> <form id="newEventForm"> <div class="ms-Grid" dir="ltr"> <div class="ms-Grid-row"> <div class="ms-Grid-col ms-sm12"> <label class="ms-fontWeight-semibold form-label" for="subject">Subject</label> <input class="form-input" type="text" id="subject" name="subject" /> </div> </div> <div class="ms-Grid-row"> <div class="ms-Grid-col ms-sm12"> <label class="ms-fontWeight-semibold form-label" for="attendees">Attendees</label> <input class="form-input" type="text" id="attendees" name="attendees" placeholder="Enter email addresses of attendees. Separate multiple with ';'. Leave blank for no attendees." /> </div> </div> <div class="ms-Grid-row"> <div class="ms-Grid-col ms-sm6"> <label class="ms-fontWeight-semibold form-label" for="start">Start</label> <input class="form-input" type="datetime-local" id="start" name="start" /> </div> <div class="ms-Grid-col ms-sm6"> <label class="ms-fontWeight-semibold form-label" for="end">End</label> <input class="form-input" type="datetime-local" id="end" name="end" /> </div> </div> <div class="ms-Grid-row"> <div class="ms-Grid-col ms-sm12"> <label class="ms-fontWeight-semibold form-label" for="body">Body</label> <textarea class="form-input" id="body" name="body" rows="4"></textarea> </div> </div> <input class="form-button" type="submit" value="Create"/> </div> </form> <div class="ms-depth-16 result-panel"></div> </div> @section Scripts { <script> (function () { if (microsoftTeams) { microsoftTeams.initialize(); } $('#newEventForm').on('submit', async (e) => { e.preventDefault(); $('.result-panel').empty(); $('.result-panel').hide(); const formData = new FormData(newEventForm); // Basic validation // Require subject, start, and end const subject = formData.get('subject'); const start = formData.get('start'); const end = formData.get('end'); if (subject.length <= 0 || start.length <= 0 || end.length <= 0) { $('<div/>', { class: 'error-msg', text: 'Subject, Start, and End are required.' }).appendTo('.result-panel'); $('.result-panel').show(); return; } // Get the auth token from Teams microsoftTeams.authentication.getAuthToken({ successCallback: (token) => { createEvent(token, formData); }, failureCallback: (error) => { $('<div/>', { class: 'error-msg', text: `Error getting token: ${error}` }).appendTo('.result-panel'); $('.result-panel').show(); } }); }); })(); async function createEvent(token, formData) { // Convert the form to a JSON payload jsonFormData = formDataToJson(); // Post the payload to the web API const response = await fetch('/calendar', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, method: 'POST', body: jsonFormData }); if (response.ok) { $('<div/>', { class: 'success-msg', text: 'Event added to your calendar' }).appendTo('.result-panel'); $('.result-panel').show(); } else { const error = await response.text(); $('<div/>', { class: 'error-msg', text: `Error creating event: ${error}` }).appendTo('.result-panel'); $('.result-panel').show(); } } // Helper method to serialize the form fields // as JSON function formDataToJson() { const array = $('#newEventForm').serializeArray(); const jsonObj = {}; array.forEach((kvp) => { jsonObj[kvp.name] = kvp.value; }); return JSON.stringify(jsonObj); } </script> }
Esto implementa un formulario simple y agrega JavaScript para publicar los datos del formulario en la API web.
Implementar la API web
Cree un nuevo directorio denominado Models en la raíz del proyecto.
Cree un nuevo archivo en el directorio ./Models denominado NewEvent.cs y agregue el siguiente código.
namespace GraphTutorial.Models { public class NewEvent { public string Subject { get; set; } public string Attendees { get; set; } public string Start { get; set; } public string End { get; set; } public string Body { get; set; } } }
Abra ./Controllers/CalendarController.cs y agregue la siguiente
using
instrucción en la parte superior del archivo.using GraphTutorial.Models;
Agregue la siguiente función a la clase CalendarController.
[HttpPost] public async Task<string> Post(NewEvent newEvent) { HttpContext.VerifyUserHasAnyAcceptedScope(apiScopes); try { // Get the user's mailbox settings var me = await _graphClient.Me .Request() .Select(u => new { u.MailboxSettings }) .GetAsync(); // Create a Graph Event var graphEvent = new Event { Subject = newEvent.Subject, Start = new DateTimeTimeZone { DateTime = newEvent.Start, TimeZone = me.MailboxSettings.TimeZone }, End = new DateTimeTimeZone { DateTime = newEvent.End, TimeZone = me.MailboxSettings.TimeZone } }; // If there are attendees, add them if (!string.IsNullOrEmpty(newEvent.Attendees)) { var attendees = new List<Attendee>(); var emailArray = newEvent.Attendees.Split(';'); foreach (var email in emailArray) { attendees.Add(new Attendee { Type = AttendeeType.Required, EmailAddress = new EmailAddress { Address = email } }); } graphEvent.Attendees = attendees; } // If there is a body, add it if (!string.IsNullOrEmpty(newEvent.Body)) { graphEvent.Body = new ItemBody { ContentType = BodyType.Text, Content = newEvent.Body }; } // Create the event await _graphClient.Me .Events .Request() .AddAsync(graphEvent); return "success"; } catch (Exception ex) { await HandleGraphException(ex); return null; } }
Esto permite un POST HTTP a la API web con los campos del formulario.
Guarde todos los cambios y reinicie la aplicación. Actualice la aplicación en Microsoft Teams y seleccione la pestaña Crear evento. Rellene el formulario y seleccione Crear para agregar un evento al calendario del usuario.
¡Enhorabuena!
Has completado la aplicación Microsoft Teams con Microsoft Graph tutorial. Ahora que tienes una aplicación de trabajo que llama a Microsoft Graph, puedes experimentar y agregar nuevas características. Visite la información general de Microsoft Graph para ver todos los datos a los que puede acceder con Microsoft Graph.
Comentarios
Proporcione cualquier comentario sobre este tutorial en el repositorio GitHub archivo.
¿Tiene algún problema con esta sección? Si es así, envíenos sus comentarios para que podamos mejorarla.