Créer Microsoft Teams applications avec Microsoft Graph
Ce didacticiel vous apprend à créer une application Microsoft Teams à l’aide de ASP.NET Core et de l’API Microsoft Graph pour récupérer les informations de calendrier d’un utilisateur.
Conseil
Si vous préférez simplement télécharger le didacticiel terminé, vous pouvez télécharger ou cloner GitHub référentiel. Consultez le fichier README dans le dossier de démonstration pour obtenir des instructions sur la configuration de l’application avec un ID d’application et une secret.
Conditions préalables
Avant de commencer ce didacticiel, les instructions suivantes doivent être installées sur votre ordinateur de développement.
Vous devez également avoir un compte scolaire ou scolaire Microsoft dans un client Microsoft 365 qui a activé le chargement de version Teams’application personnalisée. Si vous n’avez pas de compte professionnel ou scolaire Microsoft, ou si votre organisation n’a pas activé le chargement indépendant d’une application Teams personnalisée, vous pouvez vous inscrire au programme pour les développeurs Microsoft 365 pour obtenir un abonnement Office 365 développeur gratuit.
Notes
Ce didacticiel a été écrit avec le SDK .NET version 5.0.302. Les étapes de ce guide peuvent fonctionner avec d’autres versions, mais elles n’ont pas été testées.
Commentaires
Veuillez fournir des commentaires sur ce didacticiel dans GitHub référentiel.
Créer une application ASP.NET Core web MVC
Microsoft Teams applications d’onglets ont plusieurs options pour authentifier l’utilisateur et appeler Microsoft Graph. Dans cet exercice, vous allez implémenter un onglet qui se connecte pour obtenir un jeton d’th sur le client, puis utilise le flux de la part de sur le serveur pour échanger ce jeton afin d’accéder à Microsoft Graph.
Pour d’autres solutions, consultez les options suivantes.
- Créez un onglet Microsoft Teams’aide de Microsoft Graph Shared Computer Toolkit. Cet exemple est entièrement côté client et utilise microsoft Graph Shared Computer Toolkit pour gérer l’authentification et effectuer des appels à Microsoft Graph.
- Microsoft Teams d’authentification . Cet exemple contient plusieurs exemples couvrant différents scénarios d’authentification.
Créez le projet
Commencez par créer une application ASP.NET Core web.
Ouvrez votre interface de ligne de commande (CLI) dans un répertoire où vous souhaitez créer le projet. Exécutez la commande suivante :
dotnet new webapp -o GraphTutorial
Une fois le projet créé, vérifiez qu’il fonctionne en modifiant le répertoire actuel en répertoire GraphTutorial et en exécutant la commande suivante dans votre CLI.
dotnet run
Ouvrez votre navigateur et accédez à
https://localhost:5001
. Si tout fonctionne, vous devez voir une page de ASP.NET Core par défaut.
Important
Si vous recevez un avertissement signalant que le certificat pour localhost n’est pas approuvé, vous pouvez utiliser l’CLI .NET Core pour installer et faire confiance au certificat de développement. Voir Appliquer HTTPS dans ASP.NET Core pour obtenir des instructions pour des systèmes d’exploitation spécifiques.
Ajouter des packages NuGet
Avant de passer à autre chose, installez des packages NuGet supplémentaires que vous utiliserez ultérieurement.
- Microsoft.Identity.Web pour l’authentification et la demande de jetons d’accès.
- Microsoft.Identity.Web.MicrosoftGraph pour ajouter microsoft Graph prise en charge configurée avec Microsoft.Identity.Web.
- Microsoft. Graph pour mettre à jour la version de ce package installé par Microsoft.Identity.Web.MicrosoftGraph.
- TimeZoneConverter pour la traduction Windows identificateurs de fuseau horaire en identificateurs IANA.
Exécutez les commandes suivantes dans votre CLI pour installer les dépendances.
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
Concevoir l’application
Dans cette section, vous allez créer la structure d’interface utilisateur de base de l’application.
Conseil
Vous pouvez utiliser n’importe quel éditeur de texte pour modifier les fichiers sources de ce didacticiel. Toutefois, Visual Studio Code fournit des fonctionnalités supplémentaires, telles que le débogage et Intellisense.
Ouvrez ./Pages/Shared/_Layout.cshtml et remplacez tout son contenu par le code suivant pour mettre à jour la disposition globale de l’application.
<!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>
Cela remplace Bootstrap par Fluent’interfaceutilisateur, ajoute Microsoft Teams SDKet simplifie la disposition.
Ouvrez ./wwwroot/js/site.js et ajoutez le code suivant.
(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'); } }); })();
Cela ajoute un simple handler de modification de thème pour modifier la couleur de texte par défaut des thèmes foncés et à contraste élevé.
Ouvrez ./wwwroot/css/site.css et remplacez son contenu par ce qui suit.
: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; }
Ouvrez ./Pages/Index.cshtml et remplacez son contenu par le code suivant.
@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> }
Ouvrez ./Startup.cs et supprimez
app.UseHttpsRedirection();
la ligne dans laConfigure
méthode. Cela est nécessaire pour que le tunneling ngrok fonctionne.
Exécuter ngrok
Microsoft Teams ne prend pas en charge l’hébergement local pour les applications. Le serveur hébergeant votre application doit être disponible à partir du cloud à l’aide de points de terminaison HTTPS. Pour le débogage local, vous pouvez utiliser ngrok pour créer une URL publique pour votre projet hébergé localement.
Ouvrez votre CLI et exécutez la commande suivante pour démarrer ngrok.
ngrok http 5000
Une fois ngrok démarre, copiez l’URL de forwarding HTTPS. Il doit ressembler
https://50153897dd4d.ngrok.io
à . Vous aurez besoin de cette valeur dans les étapes ultérieures.
Important
Si vous utilisez la version gratuite de ngrok, l’URL de forwarding change chaque fois que vous redémarrez ngrok. Il est recommandé de laisser ngrok en cours d’exécution jusqu’à ce que vous complétiez ce didacticiel pour conserver la même URL. Si vous devez redémarrer ngrok, vous devez mettre à jour votre URL partout où elle est utilisée et réinstaller l’application dans Microsoft Teams.
Inscrire l’application sur le portail
Dans cet exercice, vous allez créer une inscription d’application web Azure AD à l’aide Azure Active Directory centre d’administration.
Ouvrez un navigateur et accédez au Centre d’administration Azure Active Directory. Connectez-vous à l’aide d’un compte personnel (compte Microsoft) ou d’un compte professionnel ou scolaire.
Sélectionnez Azure Active Directory dans le volet de navigation gauche, puis sélectionnez Inscriptions d’applications sous Gérer.
Sélectionnez Nouvelle inscription. Dans la page Enregistrer une application, définissez les valeurs comme suit, où se trouve l’URL de forwarding ngrok que vous avez copiée
YOUR_NGROK_URL
dans la section précédente.- Définissez le Nom sur
Teams Graph Tutorial
. - Définissez les Types de comptes pris en charge sur Comptes dans un annuaire organisationnel et comptes personnels Microsoft.
- Sous URI de redirection, définissez la première flèche déroulante sur
Web
, et la valeur surYOUR_NGROK_URL/authcomplete
.
- Définissez le Nom sur
Sélectionner Inscription. Dans la page Teams Graph didacticiel, copiez la valeur de l’ID de l’application (client) et enregistrez-la, vous en aurez besoin à l’étape suivante.
Sous Gérer, sélectionnez Authentification. Recherchez la section d’octroi implicite et activez les jetons d’accès et les jetons d’ID. Sélectionnez Enregistrer.
Sélectionnez Certificats et secrets sous Gérer. Sélectionnez le bouton Nouveau secret client. Entrez une valeur dans Description, sélectionnez une des options pour Expire le, puis sélectionnez Ajouter.
Copiez la valeur du secret client avant de quitter cette page. Vous en aurez besoin à l’étape suivante.
Important
Ce secret client n’apparaîtra plus jamais, aussi veillez à le copier maintenant.
Sélectionnez les autorisations d’API sous Gérer, puis sélectionnez Ajouter une autorisation.
Sélectionnez Microsoft Graph, puis Autorisations déléguées.
Sélectionnez les autorisations suivantes, puis sélectionnez Ajouter des autorisations.
- Calendars.ReadWrite : cela permettra à l’application de lire et d’écrire dans le calendrier de l’utilisateur.
- MailboxSettings.Read : cela permettra à l’application d’obtenir le fuseau horaire, le format de date et le format d’heure de l’utilisateur à partir de ses paramètres de boîte aux lettres.
Configurer l Teams l' sign-on unique
Dans cette section, vous allez mettre à jour l’inscription de l’application pour prendre en charge l' sign-on unique dans Teams.
Sélectionnez Exposer une API. Sélectionnez le lien Définir à côté de l’URI d’ID d’application. Insérez votre nom de domaine d’URL de forwarding ngrok (avec une barre oblique « / » à la fin) entre les barres obliques doubles et le GUID. L’ID entier doit ressembler à :
api://50153897dd4d.ngrok.io/ae7d8088-3422-4c8c-a351-6ded0f21d615
.Dans les étendues définies par cette section API, sélectionnez Ajouter une étendue. Remplissez les champs comme suit et sélectionnez Ajouter une étendue.
- Nom de l’étendue :
access_as_user
- Qui pouvez-vous consentir ? : Administrateurs et utilisateurs
- Nom complet du consentement de l’administrateur :
Access the app as the user
- Description du consentement de l’administrateur :
Allows Teams to call the app's web APIs as the current user.
- Nom complet du consentement de l’utilisateur :
Access the app as you
- Description du consentement de l’utilisateur :
Allows Teams to call the app's web APIs as you.
- État : activé
- Nom de l’étendue :
Dans la section Applications clientes autorisées, sélectionnez Ajouter une application cliente. Entrez un ID client dans la liste suivante, activez l’étendue sous Étendues autorisées, puis sélectionnez Ajouter une application. Répétez ce processus pour chacun des ID clients de la liste.
1fec8e78-bce4-4aaf-ab1b-5451cc387264
(Teams application mobile/de bureau)5e3ce6c0-2b1f-4285-8d4b-75ee78787346
(Teams application web)
Créer un manifeste d’application
Le manifeste de l’application décrit comment l’application s’intègre Microsoft Teams et est nécessaire pour installer des applications. Dans cette section, vous allez utiliser App Studio dans le client Microsoft Teams pour générer un manifeste.
Si App Studio n’est pas déjà installé dans Teams, installez-le maintenant.
Lancez App Studio dans Microsoft Teams puis sélectionnez l’éditeur de manifeste.
Sélectionnez Créer une application.
Dans la page Détails de l’application, remplissez les champs requis.
Notes
Vous pouvez utiliser les icônes par défaut dans la section Branding ou télécharger les vôtres.
Dans le menu de gauche, sélectionnez Onglets sous Fonctionnalités.
Sélectionnez Ajouter sous Ajouter un onglet personnel.
Remplissez les champs comme suit, où se trouve l’URL de forwarding que vous
YOUR_NGROK_URL
avez copiée dans la section précédente. Sélectionnez Enregistrer lorsque vous avez terminé.- Nom :
Create event
- ID d’entité :
createEventTab
- URL de contenu :
YOUR_NGROK_URL/newevent
- Nom :
Sélectionnez Ajouter sous Ajouter un onglet personnel.
Remplissez les champs comme suit, où se trouve l’URL de forwarding que vous
YOUR_NGROK_URL
avez copiée dans la section précédente. Sélectionnez Enregistrer lorsque vous avez terminé.- Nom :
Graph calendar
- ID d’entité :
calendarTab
- URL de contenu :
YOUR_NGROK_URL
- Nom :
Dans le menu de gauche, sélectionnez Domaines et autorisations sous Terminer.
Définissez l’ID d’application AAD sur l’ID de l’application à partir de l’inscription de votre application.
Définissez le champ d' sign-on unique sur l’URI d’ID d’application à partir de l’inscription de votre application.
Dans le menu de gauche, sélectionnez Test et distribuez-le sous Terminer. Sélectionnez Télécharger.
Créez un répertoire à la racine du projet nommé Manifest. Extrayons le contenu du fichier ZIP téléchargé dans ce répertoire.
Ajouter une authentification Azure AD
Dans cet exercice, vous allez étendre l’application de l’exercice précédent pour prendre en charge l’authentification unique avec Azure AD. Cette opération est obligatoire pour obtenir le jeton d’accès OAuth nécessaire afin d’appeler l’API Microsoft Graph. Dans cette étape, vous allez configurer la bibliothèque Microsoft.Identity.Web.
Important
Pour éviter de stocker l’ID d’application et la secret dans la source, vous utiliserez .NET Secret Manager pour stocker ces valeurs. Le Gestionnaire de secret est uniquement à des fins de développement, les applications de production doivent utiliser un gestionnaire de secret approuvé pour stocker les secrets.
Ouvrez ./appsettings.jset remplacez son contenu par ce qui suit.
{ "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": "*" }
Ouvrez votre CLI dans le répertoire où se trouve GraphTutorial.csproj, puis exécutez les commandes suivantes, en remplaçant votre ID d’application par le portail Azure et votre
YOUR_APP_ID
secret d’application.YOUR_APP_SECRET
dotnet user-secrets init dotnet user-secrets set "AzureAd:ClientId" "YOUR_APP_ID" dotnet user-secrets set "AzureAd:ClientSecret" "YOUR_APP_SECRET"
Implémentation de la connexion
Tout d’abord, implémentez l' sign-on unique dans le code JavaScript de l’application. Vous utiliserez le SDK JavaScript Microsoft Teams pour obtenir un jeton d’accès qui permet au code JavaScript en cours d’exécution dans le client Teams d’effectuer des appels AJAX à l’API Web que vous implémenterez ultérieurement.
Ouvrez ./Pages/Index.cshtml et ajoutez le code suivant à l’intérieur de la
<script>
balise.(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'); }
Cela appelle l’utilisateur à s’authentifier silencieusement en tant qu’utilisateur qui
microsoftTeams.authentication.getAuthToken
est Teams. En règle générale, aucune invite d’interface utilisateur n’est impliquée, sauf si l’utilisateur doit donner son consentement. Le code affiche ensuite le jeton dans l’onglet.Enregistrez vos modifications et démarrez votre application en exécutant la commande suivante dans votre CLI.
dotnet run
Important
Si vous avez redémarré ngrok et que votre URL ngrok a changé, veillez à mettre à jour la valeur ngrok à l’endroit suivant avant de tester.
- URI de redirection dans l’inscription de votre application
- URI de l’ID d’application dans l’inscription de votre application
contentUrl
in manifest.jsonvalidDomains
in manifest.jsonresource
in manifest.json
Créez un fichier ZIP avecmanifest.jssur, color.png et outline.png.
In Microsoft Teams, select Apps in the left-hand bar, select Télécharger a custom app, then select Télécharger for me or my teams.
Accédez au fichier ZIP que vous avez créé précédemment et sélectionnez Ouvrir.
Examinez les informations de l’application et sélectionnez Ajouter.
L’application s’ouvre Teams et affiche un jeton d’accès.
Si vous copiez le jeton, vous pouvez le coller dans jwt.ms. Vérifiez que l’audience (la revendication) est votre ID d’application et que la seule étendue (la revendication) est l’étendue API que aud
scp
vous avez access_as_user
créée. Cela signifie que ce jeton n’accorde pas un accès direct à Microsoft Graph! Au lieu de cela, l’API Web que vous implémentez prochainement devra échanger ce jeton à l’aide du flux « de la part de » pour obtenir un jeton qui fonctionne avec les appels Microsoft Graph.
Configurer l’authentification dans l ASP.NET Core appl;
Commencez par ajouter les services de plateforme Microsoft Identity à l’application.
Ouvrez le fichier ./Startup.cs et ajoutez l’instruction
using
suivante en haut du fichier.using Microsoft.Identity.Web;
Ajoutez la ligne suivante juste avant la
app.UseAuthorization();
ligne dans laConfigure
fonction.app.UseAuthentication();
Ajoutez la ligne suivante juste après la
endpoints.MapRazorPages();
ligne dans laConfigure
fonction.endpoints.MapControllers();
Remplacez la fonction
ConfigureServices
existante par ce qui suit.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(); }
Ce code configure l’application pour autoriser l’authentification des appels aux API Web en fonction du jeton du porteur JWT dans
Authorization
l’en-tête. Il ajoute également les services d’acquisition de jetons qui peuvent échanger ce jeton via le flux « de la part de ».
Créer le contrôleur d’API Web
Créez un répertoire à la racine du projet nommé Controllers.
Créez un fichier dans le répertoire ./Controllers nommé CalendarController.cs et ajoutez le code suivant.
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; } } } }
Cela implémente une API Web ( ) qui peut être appelée à partir de
GET /calendar
l’onglet Teams web. Pour l’instant, il tente simplement d’échanger le jeton du porteur contre Graph jeton. La première fois qu’un utilisateur charge l’onglet, cela échoue car il n’a pas encore accepté d’autoriser l’accès de l’application à Microsoft Graph en son nom.Ouvrez ./Pages/Index.cshtml et remplacez
successCallback
la fonction par ce qui suit.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); }); }
Cela appelle l’API Web et affiche la réponse.
Enregistrez vos modifications, puis redémarrez l’application. Actualisez l’onglet dans Microsoft Teams. La page doit
consent_required
s’afficher.Examinez la sortie du journal dans votre CLI. Notez deux choses.
- Une entrée telle
Authenticated user: MeganB@contoso.com
que . L’API Web a authentifié l’utilisateur en fonction du jeton envoyé avec la demande d’API. - Une entrée telle
AADSTS65001: The user or administrator has not consented to use the application with ID...
que . Ceci est attendu, car l’utilisateur n’a pas encore été invité à donner son consentement pour les étendues d’autorisation microsoft Graph demandées.
- Une entrée telle
Implémenter une invite de consentement
Étant donné que l’API Web ne peut pas inviter l’utilisateur, l’onglet Teams doit implémenter une invite. Cette étape n’aura besoin d’être effectuée qu’une seule fois pour chaque utilisateur. Une fois qu’un utilisateur a donné son consentement, il n’a pas besoin de se reconsenter, sauf s’il révoque explicitement l’accès à votre application.
Créez un fichier dans le répertoire ./Pages nommé Authenticate.cshtml.cs et ajoutez le code suivant.
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(); } } }
Créez un fichier dans le répertoire ./Pages nommé Authenticate.cshtml et ajoutez le code suivant.
@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> }
Créez un fichier dans le répertoire ./Pages nommé AuthComplete.cshtml et ajoutez le code suivant.
@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> }
Ouvrez ./Pages/Index.cshtml et ajoutez les fonctions suivantes à l’intérieur de la
<script>
balise.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); } }); }
Ajoutez la fonction suivante à l’intérieur
<script>
de la balise pour afficher un résultat réussi à partir de l’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'); }
Remplacez
successCallback
l’existant par le code suivant.successCallback: (token) => { loadUserCalendar(token, (events) => { renderCalendar(events); }); }
Enregistrez vos modifications, puis redémarrez l’application. Actualisez l’onglet dans Microsoft Teams. Vous devez obtenir une fenêtre pop-up demandant l’autorisation d’accès aux Graph Microsoft. Après l’acceptation, l’onglet doit
{ "status": "OK" }
s’afficher.Notes
Si l’onglet s’affiche, désactivez les bloqueurs de fenêtres pop-up dans votre
"FailedToOpenWindow"
navigateur et rechargez la page.Examinez la sortie du journal. Vous devriez voir
Access token for Graph
l’entrée. Si vous l’allez, vous remarquerez qu’il contient les étendues microsoft Graph configurées dans appsettings.jssur.
Stockage et actualisation des jetons
À ce stade, votre application dispose d’un jeton d’accès, qui est envoyé dans l’en-tête des Authorization
appels d’API. Il s’agit du jeton qui permet à l’application d’accéder à Microsoft Graph pour le compte de l’utilisateur.
Cependant, ce jeton est de courte durée. Le jeton expire une heure après son émission. C’est là que le jeton d’actualisation devient utile. Le jeton d’actualisation permet à l’application de demander un nouveau jeton d’accès sans obliger l’utilisateur à se reconnecter.
Étant donné que l’application utilise la bibliothèque Microsoft.Identity.Web, vous n’avez pas besoin d’implémenter de logique de stockage ou d’actualisation de jeton.
L’application utilise le cache de jetons en mémoire, ce qui est suffisant pour les applications qui n’ont pas besoin de rendre persistants les jetons au redémarrage de l’application. Les applications de production peuvent utiliser à la place les options de cache distribué dans la bibliothèque Microsoft.Identity.Web.
La GetAccessTokenForUserAsync
méthode gère l’expiration et l’actualisation des jetons pour vous. Il vérifie d’abord le jeton mis en cache et, s’il n’a pas expiré, il le renvoie. S’il a expiré, il utilise le jeton d’actualisation mis en cache pour en obtenir un nouveau.
GraphServiceClient que les contrôleurs obtiennent via l’injection de dépendances est préconfiguré avec un fournisseur d’authentification qui vous GetAccessTokenForUserAsync
est utilisé.
Obtenir un affichage Calendrier
Dans cette section, vous allez incorporer Microsoft Graph dans l’application. Pour cette application, vous allez utiliser la bibliothèque cliente Microsoft Graph pour .NET pour effectuer des appels à Microsoft Graph.
Obtenir un affichage Calendrier
Un affichage Calendrier est un ensemble d’événements du calendrier de l’utilisateur qui se produisent entre deux points de temps. Vous l’utiliserez pour obtenir les événements de l’utilisateur pour la semaine en cours.
Ouvrez ./Controllers/CalendarController.cs et ajoutez la fonction suivante à la classe 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); }
Ajoutez la fonction suivante pour gérer les exceptions renvoyées par microsoft Graph appels.
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()); } }
Remplacez la fonction
Get
existante par ce qui suit.[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; } }
Examinez les modifications. Cette nouvelle version de la fonction :
- Renvoie
IEnumerable<Event>
au lieu destring
. - Obtient les paramètres de boîte aux lettres de l’utilisateur à l’aide de Microsoft Graph.
- Utilise le fuseau horaire de l’utilisateur pour calculer le début et la fin de la semaine en cours.
- Obtient un affichage Calendrier
- Utilise la fonction pour inclure un en-tête, ce qui entraîne la conversion des heures de début et de fin des événements renvoyés en fuseau horaire de
.Header()
Prefer: outlook.timezone
l’utilisateur. - Utilise la
.Top()
fonction pour demander au maximum 50 événements. - Utilise la
.Select()
fonction pour demander uniquement les champs utilisés par l’application. - Utilise la
OrderBy()
fonction pour trier les résultats par heure de début.
- Utilise la fonction pour inclure un en-tête, ce qui entraîne la conversion des heures de début et de fin des événements renvoyés en fuseau horaire de
- Renvoie
Enregistrez vos modifications, puis redémarrez l’application. Actualisez l’onglet dans Microsoft Teams. L’application affiche une liste JSON des événements.
Afficher les résultats
Vous pouvez désormais afficher la liste des événements d’une manière plus conviviale.
Ouvrez ./Pages/Index.cshtml et ajoutez les fonctions suivantes à l’intérieur de la
<script>
balise.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;' })); }
Remplacez la fonction
renderCalendar
existante par ce qui suit.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'); }); }
Enregistrez vos modifications, puis redémarrez l’application. Actualisez l’onglet dans Microsoft Teams. L’application affiche des événements sur le calendrier de l’utilisateur.
Créer un événement
Dans cette section, vous allez ajouter la possibilité de créer des événements sur le calendrier de l’utilisateur.
Créer l’onglet Nouvel événement
Créez un fichier dans le répertoire ./Pages nommé NewEvent.cshtml et ajoutez le code suivant.
@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> }
Cela implémente un formulaire simple et ajoute JavaScript pour publier les données du formulaire dans l’API Web.
Implémenter l’API Web
Créez un répertoire nommé Modèles à la racine du projet.
Créez un fichier dans le répertoire ./Models nommé NewEvent.cs et ajoutez le code suivant.
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; } } }
Ouvrez ./Controllers/CalendarController.cs et ajoutez l’instruction suivante en
using
haut du fichier.using GraphTutorial.Models;
Ajoutez la fonction suivante à la classe 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; } }
Cela permet d’une demande HTTP POST sur l’API Web avec les champs du formulaire.
Enregistrez toutes vos modifications et redémarrez l’application. Actualisez l’application Microsoft Teams, puis sélectionnez l’onglet Créer un événement. Remplissez le formulaire et sélectionnez Créer pour ajouter un événement au calendrier de l’utilisateur.
Félicitations !
Vous avez terminé le didacticiel Microsoft Teams’application microsoft Graph. Maintenant que vous disposez d’une application de travail qui appelle Microsoft Graph, vous pouvez expérimenter et ajouter de nouvelles fonctionnalités. Consultez la vue d’ensemble de Microsoft Graph pour voir toutes les données accessibles avec Microsoft Graph.
Commentaires
Veuillez fournir des commentaires sur ce didacticiel dans GitHub référentiel.
Vous avez un problème avec cette section ? Si c'est le cas, faites-nous part de vos commentaires pour que nous puissions l'améliorer.