Créer des applications MVC ASP.NET principales avec Microsoft Graph
Ce didacticiel vous apprend à créer une application web ASP.NET Core qui utilise 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 le GitHub complet. 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, le SDK .NET Core doit être installé sur votre ordinateur de développement. Si vous n’avez pas le SDK, consultez le lien précédent pour obtenir les options de téléchargement.
Vous devez également avoir un compte Microsoft personnel avec une boîte aux lettres sur Outlook.com, ou un compte scolaire ou scolaire Microsoft. Si vous n’avez pas de compte Microsoft, deux options s’offrent à vous pour obtenir un compte gratuit :
- Vous pouvez vous inscrire à un nouveau compte Microsoft personnel.
- Vous pouvez vous inscrire au programme Microsoft 365 développeur pour obtenir un abonnement Office 365 gratuit.
Notes
Ce didacticiel a été écrit avec le SDK .NET Core version 5.0.102. Les étapes de ce guide peuvent fonctionner avec d’autres versions, mais elles n’ont pas été testées.
Commentaires
N’hésitez pas à nous faire part de vos commentaires sur ce didacticiel dans GitHub référentiel.
Créer une application Web MVC ASP.NET principale
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 mvc -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 à la suite, installez des packages NuGet supplémentaires que vous utiliserez ultérieurement.
- Microsoft.Identity.Web pour la demande et la gestion des jetons d’accès.
- Microsoft.Identity.Web.MicrosoftGraph pour ajouter le SDK Microsoft Graph via l’injection de dépendances.
- Microsoft.Identity.Web.UI pour l’interface utilisateur de la signature et de la signature.
- TimeZoneConverter pour la gestion des identificateurs fuseau horaires sur plusieurs plateformes.
Exécutez les commandes suivantes dans votre CLI pour installer les dépendances.
dotnet add package Microsoft.Identity.Web --version 1.5.1 dotnet add package Microsoft.Identity.Web.MicrosoftGraph --version 1.5.1 dotnet add package Microsoft.Identity.Web.UI --version 1.5.1 dotnet add package TimeZoneConverter
Concevoir l’application
Dans cette section, vous allez créer la structure d’interface utilisateur de base de l’application.
Implémenter des méthodes d’extension d’alerte
Dans cette section, vous allez créer des méthodes d’extension pour le IActionResult
type renvoyé par les affichages du contrôleur. Cette extension permet de transmettre des messages d’erreur ou de réussite temporaires à l’affichage.
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.
Créez un répertoire dans le répertoire GraphTutorial nommé Alertes.
Créez un fichier nommé WithAlertResult.cs dans le répertoire ./Alerts et ajoutez le code suivant.
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.DependencyInjection; using System.Threading.Tasks; namespace GraphTutorial { // WithAlertResult adds temporary error/info/success // messages to the result of a controller action. // This data is read and displayed by the _AlertPartial view public class WithAlertResult : IActionResult { public IActionResult Result { get; } public string Type { get; } public string Message { get; } public string DebugInfo { get; } public WithAlertResult(IActionResult result, string type, string message, string debugInfo) { Result = result; Type = type; Message = message; DebugInfo = debugInfo; } public async Task ExecuteResultAsync(ActionContext context) { var factory = context.HttpContext.RequestServices .GetService<ITempDataDictionaryFactory>(); var tempData = factory.GetTempData(context.HttpContext); tempData["_alertType"] = Type; tempData["_alertMessage"] = Message; tempData["_alertDebugInfo"] = DebugInfo; await Result.ExecuteResultAsync(context); } } }
Créez un fichier nommé AlertExtensions.cs dans le répertoire ./Alerts et ajoutez le code suivant.
using Microsoft.AspNetCore.Mvc; namespace GraphTutorial { public static class AlertExtensions { public static IActionResult WithError(this IActionResult result, string message, string debugInfo = null) { return Alert(result, "danger", message, debugInfo); } public static IActionResult WithSuccess(this IActionResult result, string message, string debugInfo = null) { return Alert(result, "success", message, debugInfo); } public static IActionResult WithInfo(this IActionResult result, string message, string debugInfo = null) { return Alert(result, "info", message, debugInfo); } private static IActionResult Alert(IActionResult result, string type, string message, string debugInfo) { return new WithAlertResult(result, type, message, debugInfo); } } }
Implémenter des méthodes d’extension de données utilisateur
Dans cette section, vous allez créer des méthodes d’extension pour l’objet ClaimsPrincipal
généré par la plateforme Microsoft Identity. Cela vous permettra d’étendre l’identité utilisateur existante avec les données de Microsoft Graph.
Notes
Ce code n’est qu’un espace réservé pour l’instant, vous le compléterez dans une section ultérieure.
Créez un répertoire dans le répertoire GraphTutorial nommé Graph.
Créez un fichier nommé GraphClaimsPrincipalExtensions.cs et ajoutez le code suivant.
using System.Security.Claims; namespace GraphTutorial { public static class GraphClaimTypes { public const string DisplayName ="graph_name"; public const string Email = "graph_email"; public const string Photo = "graph_photo"; public const string TimeZone = "graph_timezone"; public const string DateTimeFormat = "graph_datetimeformat"; } // Helper methods to access Graph user data stored in // the claims principal public static class GraphClaimsPrincipalExtensions { public static string GetUserGraphDisplayName(this ClaimsPrincipal claimsPrincipal) { return "Adele Vance"; } public static string GetUserGraphEmail(this ClaimsPrincipal claimsPrincipal) { return "adelev@contoso.com"; } public static string GetUserGraphPhoto(this ClaimsPrincipal claimsPrincipal) { return "/img/no-profile-photo.png"; } } }
Créer des affichages
Dans cette section, vous allez implémenter les affichages Dutable pour l’application.
Ajoutez un nouveau fichier nommé _LoginPartial.cshtml dans le répertoire ./Views/Shared et ajoutez le code suivant.
@using GraphTutorial <ul class="nav navbar-nav"> @if (User.Identity.IsAuthenticated) { <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button"> <img src="@User.GetUserGraphPhoto()" class="nav-profile-photo rounded-circle align-self-center mr-2"> </a> <div class="dropdown-menu dropdown-menu-right"> <h5 class="dropdown-item-text mb-0">@User.GetUserGraphDisplayName()</h5> <p class="dropdown-item-text text-muted mb-0">@User.GetUserGraphEmail()</p> <div class="dropdown-divider"></div> <a class="dropdown-item" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignOut">Sign out</a> </div> </li> } else { <li class="nav-item"> <a class="nav-link" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in</a> </li> } </ul>
Ajoutez un nouveau fichier nommé _AlertPartial.cshtml dans le répertoire ./Views/Shared et ajoutez le code suivant.
@{ var type = $"{TempData["_alertType"]}"; var message = $"{TempData["_alertMessage"]}"; var debugInfo = $"{TempData["_alertDebugInfo"]}"; } @if (!string.IsNullOrEmpty(type)) { <div class="alert alert-@type" role="alert"> @if (string.IsNullOrEmpty(debugInfo)) { <p class="mb-0">@message</p> } else { <p class="mb-3">@message</p> <pre class="alert-pre border bg-light p-2"><code>@debugInfo</code></pre> } </div> }
Ouvrez le fichier ./Views/Shared/_Layout.cshtml et remplacez l’intégralité de son contenu par le code suivant pour mettre à jour la disposition globale de l’application.
@{ string controller = $"{ViewContext.RouteData.Values["controller"]}"; } <!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="~/lib/bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/css/site.css" /> </head> <body> <header> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-dark bg-dark border-bottom box-shadow mb-3"> <div class="container"> <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">GraphTutorial</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="navbar-collapse collapse mr-auto"> <ul class="navbar-nav flex-grow-1"> <li class="@(controller == "Home" ? "nav-item active" : "nav-item")"> <a class="nav-link" asp-area="" asp-controller="Home" asp-action="Index">Home</a> </li> @if (User.Identity.IsAuthenticated) { <li class="@(controller == "Calendar" ? "nav-item active" : "nav-item")"> <a class="nav-link" asp-area="" asp-controller="Calendar" asp-action="Index">Calendar</a> </li> } </ul> <partial name="_LoginPartial"/> </div> </div> </nav> </header> <div class="container"> <main role="main" class="pb-3"> <partial name="_AlertPartial"/> @RenderBody() </main> </div> <footer class="border-top footer text-muted"> <div class="container"> © 2020 - GraphTutorial - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </div> </footer> <script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> @RenderSection("Scripts", required: false) </body> </html>
Ouvrez ./wwwroot/css/site.css et ajoutez le code suivant en bas du fichier.
.nav-profile-photo { width: 32px; } .alert-pre { word-wrap: break-word; word-break: break-all; white-space: pre-wrap; } .calendar-view-date-cell { width: 150px; } .calendar-view-date { width: 40px; font-size: 36px; line-height: 36px; margin-right: 10px; } .calendar-view-month { font-size: 0.75em; } .calendar-view-timespan { width: 200px; } .calendar-view-subject { font-size: 1.25em; } .calendar-view-organizer { font-size: .75em; } .calendar-view-date-diff { font-size: .75em }
Ouvrez le fichier ./Views/Home/index.cshtml et remplacez son contenu par ce qui suit.
@{ ViewData["Title"] = "Home Page"; } @using GraphTutorial <div class="jumbotron"> <h1>ASP.NET Core Graph Tutorial</h1> <p class="lead">This sample app shows how to use the Microsoft Graph API to access a user's data from ASP.NET Core</p> @if (User.Identity.IsAuthenticated) { <h4>Welcome @User.GetUserGraphDisplayName()!</h4> <p>Use the navigation bar at the top of the page to get started.</p> } else { <a class="btn btn-primary btn-large" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Click here to sign in</a> } </div>
Créez un répertoire dans le répertoire ./wwwroot nommé img. Ajoutez un fichier image de votre choix nommé no-profile-photo.png dans ce répertoire. Cette image est utilisée comme photo de l’utilisateur lorsque l’utilisateur n’a pas de photo dans Microsoft Graph.
Conseil
Vous pouvez télécharger l’image utilisée dans ces captures d’écran à partir GitHub.
Enregistrez toutes vos modifications et redémarrez le serveur (
dotnet run
). L’application doit maintenant avoir une apparence très différente.
Inscrire l’application sur le portail
Dans cet exercice, vous allez créer une nouvelle inscription Azure AD’application web à l’aide du centre d Azure Active Directory’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. Sur la page Inscrire une application, définissez les valeurs comme suit.
- Définissez le Nom sur
ASP.NET Core 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 surhttps://localhost:5001/
.
- Définissez le Nom sur
Sélectionner Inscription. Dans la page ASP.NET Core Graph didacticiel, copiez la valeur de l’ID d’application (client) et enregistrez-la. Vous en aurez besoin à l’étape suivante.
Sous Gérer, sélectionnez Authentification. Sous URI de redirection , ajoutez un URI avec la valeur
https://localhost:5001/signin-oidc
.Définissez l’URL de connexion sur
https://localhost:5001/signout-oidc
.Recherchez la section Octroi implicite, puis activez 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 due la clé secrète 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.
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 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 le gestionnaire de secret .NET 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.json et remplacez son contenu par ce qui suit.
{ "AzureAd": { "Instance": "https://login.microsoftonline.com/", "TenantId": "common", "CallbackPath": "/signin-oidc" }, "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,
YOUR_APP_ID
en remplaçant votre ID d’application par le portail AzureYOUR_APP_SECRET
et votre secret d’application.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
Commencez par ajouter les services de plateforme Microsoft Identity à l’application.
Créez un fichier nommé GraphConstants.cs dans le répertoire ./Graph et ajoutez le code suivant.
namespace GraphTutorial { public static class GraphConstants { // Defines the permission scopes used by the app public readonly static string[] Scopes = { "User.Read", "MailboxSettings.Read", "Calendars.ReadWrite" }; } }
Ouvrez le fichier ./Startup.cs et ajoutez
using
les instructions suivantes en haut du fichier.using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.Graph; using System.Net; using System.Net.Http.Headers;
Remplacez la fonction
ConfigureServices
existante par ce qui suit.public void ConfigureServices(IServiceCollection services) { services // Use OpenId authentication .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) // Specify this is a web app and needs auth code flow .AddMicrosoftIdentityWebApp(Configuration) // Add ability to call web API (Graph) // and get access tokens .EnableTokenAcquisitionToCallDownstreamApi(options => { Configuration.Bind("AzureAd", options); }, GraphConstants.Scopes) // Use in-memory token cache // See https://github.com/AzureAD/microsoft-identity-web/wiki/token-cache-serialization .AddInMemoryTokenCaches(); // Require authentication services.AddControllersWithViews(options => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }) // Add the Microsoft Identity UI pages for signin/out .AddMicrosoftIdentityUI(); }
Dans la
Configure
fonction, ajoutez la ligne suivante au-dessus de laapp.UseAuthorization();
ligne.app.UseAuthentication();
Ouvrez ./Controllers/HomeController.cs et remplacez son contenu par ce qui suit.
using GraphTutorial.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Identity.Web; using System.Diagnostics; using System.Threading.Tasks; namespace GraphTutorial.Controllers { public class HomeController : Controller { ITokenAcquisition _tokenAcquisition; private readonly ILogger<HomeController> _logger; // Get the ITokenAcquisition interface via // dependency injection public HomeController( ITokenAcquisition tokenAcquisition, ILogger<HomeController> logger) { _tokenAcquisition = tokenAcquisition; _logger = logger; } public async Task<IActionResult> Index() { // TEMPORARY // Get the token and display it try { string token = await _tokenAcquisition .GetAccessTokenForUserAsync(GraphConstants.Scopes); return View().WithInfo("Token acquired", token); } catch (MicrosoftIdentityWebChallengeUserException) { return Challenge(); } } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [AllowAnonymous] public IActionResult ErrorWithMessage(string message, string debug) { return View("Index").WithError(message, debug); } } }
Enregistrez vos modifications et démarrez le projet. Connectez-vous avec votre compte Microsoft.
Examinez l’invite de consentement. La liste des autorisations correspond à la liste des étendues d’autorisations configurées dans ./Graph/GraphConstants.cs.
- Conservez l’accès aux données à qui vous avez accordé l’accès : (
offline_access
) Cette autorisation est demandée par MSAL afin de récupérer les jetons d’actualisation. - Connectez-vous et lisez votre profil : (
User.Read
) Cette autorisation permet à l’application d’obtenir le profil et la photo de profil de l’utilisateur connecté. - Lisez les paramètres de votre boîte aux lettres : (
MailboxSettings.Read
) Cette autorisation permet à l’application de lire les paramètres de boîte aux lettres de l’utilisateur, y compris le fuseau horaire et le format horaire. - Avoir un accès total à vos calendriers : (
Calendars.ReadWrite
) Cette autorisation permet à l’application de lire des événements sur le calendrier de l’utilisateur, d’ajouter de nouveaux événements et de modifier des événements existants.
Pour plus d’informations sur le consentement, voir Understanding Azure AD application consent experiences.
- Conservez l’accès aux données à qui vous avez accordé l’accès : (
Consentement aux autorisations demandées. Le navigateur vous redirige vers l’application, affichant le jeton.
Obtenir les détails de l’utilisateur
Une fois que l’utilisateur a ouvert une session, vous pouvez obtenir ses informations à partir de Microsoft Graph.
Ouvrez ./Graph/GraphClaimsPrincipalExtensions.cs et remplacez tout son contenu par ce qui suit.
using Microsoft.Graph; using System; using System.IO; using System.Security.Claims; namespace GraphTutorial { public static class GraphClaimTypes { public const string DisplayName ="graph_name"; public const string Email = "graph_email"; public const string Photo = "graph_photo"; public const string TimeZone = "graph_timezone"; public const string TimeFormat = "graph_timeformat"; } // Helper methods to access Graph user data stored in // the claims principal public static class GraphClaimsPrincipalExtensions { public static string GetUserGraphDisplayName(this ClaimsPrincipal claimsPrincipal) { return claimsPrincipal.FindFirstValue(GraphClaimTypes.DisplayName); } public static string GetUserGraphEmail(this ClaimsPrincipal claimsPrincipal) { return claimsPrincipal.FindFirstValue(GraphClaimTypes.Email); } public static string GetUserGraphPhoto(this ClaimsPrincipal claimsPrincipal) { return claimsPrincipal.FindFirstValue(GraphClaimTypes.Photo); } public static string GetUserGraphTimeZone(this ClaimsPrincipal claimsPrincipal) { return claimsPrincipal.FindFirstValue(GraphClaimTypes.TimeZone); } public static string GetUserGraphTimeFormat(this ClaimsPrincipal claimsPrincipal) { return claimsPrincipal.FindFirstValue(GraphClaimTypes.TimeFormat); } public static void AddUserGraphInfo(this ClaimsPrincipal claimsPrincipal, User user) { var identity = claimsPrincipal.Identity as ClaimsIdentity; identity.AddClaim( new Claim(GraphClaimTypes.DisplayName, user.DisplayName)); identity.AddClaim( new Claim(GraphClaimTypes.Email, user.Mail ?? user.UserPrincipalName)); identity.AddClaim( new Claim(GraphClaimTypes.TimeZone, user.MailboxSettings.TimeZone)); identity.AddClaim( new Claim(GraphClaimTypes.TimeFormat, user.MailboxSettings.TimeFormat)); } public static void AddUserGraphPhoto(this ClaimsPrincipal claimsPrincipal, Stream photoStream) { var identity = claimsPrincipal.Identity as ClaimsIdentity; if (photoStream == null) { // Add the default profile photo identity.AddClaim( new Claim(GraphClaimTypes.Photo, "/img/no-profile-photo.png")); return; } // Copy the photo stream to a memory stream // to get the bytes out of it var memoryStream = new MemoryStream(); photoStream.CopyTo(memoryStream); var photoBytes = memoryStream.ToArray(); // Generate a date URI for the photo var photoUrl = $"data:image/png;base64,{Convert.ToBase64String(photoBytes)}"; identity.AddClaim( new Claim(GraphClaimTypes.Photo, photoUrl)); } } }
Ouvrez ./Startup.cs et remplacez la ligne
.AddMicrosoftIdentityWebApp(Configuration)
existante par le code suivant.// Specify this is a web app and needs auth code flow .AddMicrosoftIdentityWebApp(options => { Configuration.Bind("AzureAd", options); options.Prompt = "select_account"; options.Events.OnTokenValidated = async context => { var tokenAcquisition = context.HttpContext.RequestServices .GetRequiredService<ITokenAcquisition>(); var graphClient = new GraphServiceClient( new DelegateAuthenticationProvider(async (request) => { var token = await tokenAcquisition .GetAccessTokenForUserAsync(GraphConstants.Scopes, user:context.Principal); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); }) ); // Get user information from Graph var user = await graphClient.Me.Request() .Select(u => new { u.DisplayName, u.Mail, u.UserPrincipalName, u.MailboxSettings }) .GetAsync(); context.Principal.AddUserGraphInfo(user); // Get the user's photo // If the user doesn't have a photo, this throws try { var photo = await graphClient.Me .Photos["48x48"] .Content .Request() .GetAsync(); context.Principal.AddUserGraphPhoto(photo); } catch (ServiceException ex) { if (ex.IsMatch("ErrorItemNotFound") || ex.IsMatch("ConsumerPhotoIsNotSupported")) { context.Principal.AddUserGraphPhoto(null); } else { throw; } } }; options.Events.OnAuthenticationFailed = context => { var error = WebUtility.UrlEncode(context.Exception.Message); context.Response .Redirect($"/Home/ErrorWithMessage?message=Authentication+error&debug={error}"); context.HandleResponse(); return Task.FromResult(0); }; options.Events.OnRemoteFailure = context => { if (context.Failure is OpenIdConnectProtocolException) { var error = WebUtility.UrlEncode(context.Failure.Message); context.Response .Redirect($"/Home/ErrorWithMessage?message=Sign+in+error&debug={error}"); context.HandleResponse(); } return Task.FromResult(0); }; })
Prenez en compte ce que fait ce code.
- Il ajoute un handler d’événements pour l’événement
OnTokenValidated
.- Il utilise l’interface
ITokenAcquisition
pour obtenir un jeton d’accès. - Il appelle Microsoft Graph pour obtenir le profil et la photo de l’utilisateur.
- Il ajoute les Graph à l’identité de l’utilisateur.
- Il utilise l’interface
- Il ajoute un handler d’événements pour l’événement
Ajoutez l’appel de fonction suivant après l’appel
EnableTokenAcquisitionToCallDownstreamApi
et avant l’appelAddInMemoryTokenCaches
.// Add a GraphServiceClient via dependency injection .AddMicrosoftGraph(options => { options.Scopes = string.Join(' ', GraphConstants.Scopes); })
Cela rend un GraphServiceClient authentifié disponible pour les contrôleurs via l’injection de dépendance.
Ouvrez ./Controllers/HomeController.cs et remplacez la
Index
fonction par ce qui suit.public IActionResult Index() { return View(); }
Supprimez toutes les références à
ITokenAcquisition
la classe HomeController .Enregistrez vos modifications, démarrez l’application et traversez le processus de signature. Vous devez revenir sur la page d’accueil, mais l’interface utilisateur doit changer pour indiquer que vous êtes en cours de signature.
Cliquez sur l’avatar de l’utilisateur dans le coin supérieur droit pour accéder au lien de connexion. Le fait de cliquer sur Se déconnecter réinitialise la session et vous ramène à la page d’accueil.
Conseil
Si vous ne voyez pas votre nom d’utilisateur sur la page d’accueil et que le nom et l’e-mail de l’avatar d’utilisation sont manquants après avoir apporté ces modifications, dé connectez-vous et connectez-vous.
Stockage et actualisation des jetons
À ce stade, votre application dispose d’un jeton d’accès, qui est Authorization
envoyé dans l’en-tête des 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 les options de cache distribué dans la bibliothèque Microsoft.Identity.Web.
La méthode GetAccessTokenForUserAsync
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.
Le GraphServiceClient que les contrôleurs obtiennent via l’injection de dépendances sera 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.
Récupérer les événements de calendrier à partir d’Outlook
Commencez par créer un contrôleur pour les affichages calendrier.
Ajoutez un nouveau fichier nommé CalendarController.cs dans le répertoire ./Controllers et ajoutez le code suivant.
using GraphTutorial.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Identity.Web; using Microsoft.Graph; using System; using System.Collections.Generic; using System.Threading.Tasks; using TimeZoneConverter; namespace GraphTutorial.Controllers { public class CalendarController : Controller { private readonly GraphServiceClient _graphClient; private readonly ILogger<HomeController> _logger; public CalendarController( GraphServiceClient graphClient, ILogger<HomeController> logger) { _graphClient = graphClient; _logger = logger; } } }
Ajoutez les fonctions suivantes à la classe
CalendarController
pour obtenir l’affichage Calendrier de l’utilisateur.private async Task<IList<Event>> GetUserWeekCalendar(DateTime startOfWeekUtc) { // Configure a calendar view for the current week var endOfWeekUtc = startOfWeekUtc.AddDays(7); var viewOptions = new List<QueryOption> { new QueryOption("startDateTime", startOfWeekUtc.ToString("o")), new QueryOption("endDateTime", endOfWeekUtc.ToString("o")) }; var events = 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=\"{User.GetUserGraphTimeZone()}\"") // Get max 50 per request .Top(50) // Only return fields app will use .Select(e => new { e.Subject, e.Organizer, e.Start, e.End }) // Order results chronologically .OrderBy("start/dateTime") .GetAsync(); IList<Event> allEvents; // Handle case where there are more than 50 if (events.NextPageRequest != null) { allEvents = new List<Event>(); // Create a page iterator to iterate over subsequent pages // of results. Build a list from the results var pageIterator = PageIterator<Event>.CreatePageIterator( _graphClient, events, (e) => { allEvents.Add(e); return true; } ); await pageIterator.IterateAsync(); } else { // If only one page, just use the result allEvents = events.CurrentPage; } return allEvents; } private static DateTime GetUtcStartOfWeekInTimeZone(DateTime today, TimeZoneInfo timeZone) { // 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, timeZone); }
Réfléchissez à ce que fait le
GetUserWeekCalendar
code.- Il utilise le fuseau horaire de l’utilisateur pour obtenir les valeurs de date/heure de début et de fin UTC de la semaine.
- Elle interroge l’affichage Calendrier de l’utilisateur pour obtenir tous les événements se s’erdant entre la date et l’heure de début et de fin. L’utilisation d’un affichage Calendrier au lieu de répertorier des événements développe les événements périodiques, renvoyant toutes les occurrences qui se produisent dans la fenêtre de temps spécifiée.
- Il utilise l’en-tête
Prefer: outlook.timezone
pour obtenir les résultats dans le fuseau horaire de l’utilisateur. - Il permet
Select
de limiter les champs qui reviennent uniquement à ceux utilisés par l’application. - Il utilise pour
OrderBy
trier les résultats dans l’ordre chronologique. - Il utilise une page
PageIterator
à travers la collection d’événements. Cela gère le cas où l’utilisateur a plus d’événements sur son calendrier que la taille de page demandée.
Ajoutez la fonction suivante à la classe
CalendarController
pour implémenter une vue temporaire des données renvoyées.// Minimum permission scope needed for this view [AuthorizeForScopes(Scopes = new[] { "Calendars.Read" })] public async Task<IActionResult> Index() { try { var userTimeZone = TZConvert.GetTimeZoneInfo( User.GetUserGraphTimeZone()); var startOfWeek = CalendarController.GetUtcStartOfWeekInTimeZone( DateTime.Today, userTimeZone); var events = await GetUserWeekCalendar(startOfWeek); // Return a JSON dump of events return new ContentResult { Content = _graphClient.HttpProvider.Serializer.SerializeObject(events), ContentType = "application/json" }; } catch (ServiceException ex) { if (ex.InnerException is MicrosoftIdentityWebChallengeUserException) { throw; } return new ContentResult { Content = $"Error getting calendar view: {ex.Message}", ContentType = "text/plain" }; } }
Démarrez l’application, connectez-vous, puis cliquez sur le lien Calendrier dans la barre de navigation. Si tout fonctionne, vous devriez voir une image mémoire JSON des événements dans le calendrier de l’utilisateur.
Afficher les résultats
Vous pouvez désormais ajouter une vue pour afficher les résultats de façon plus parlante.
Créer des modèles d’affichage
Créez un fichier nommé CalendarViewEvent.cs dans le répertoire ./Models et ajoutez le code suivant.
using Microsoft.Graph; using System; namespace GraphTutorial.Models { public class CalendarViewEvent { public string Subject { get; private set; } public string Organizer { get; private set; } public DateTime Start { get; private set; } public DateTime End { get; private set; } public CalendarViewEvent(Event graphEvent) { Subject = graphEvent.Subject; Organizer = graphEvent.Organizer.EmailAddress.Name; Start = DateTime.Parse(graphEvent.Start.DateTime); End = DateTime.Parse(graphEvent.End.DateTime); } } }
Créez un fichier nommé DailyViewModel.cs dans le répertoire ./Models et ajoutez le code suivant.
using System; using System.Collections.Generic; namespace GraphTutorial.Models { public class DailyViewModel { // Day the view is for public DateTime Day { get; private set; } // Events on this day public IEnumerable<CalendarViewEvent> Events { get; private set; } public DailyViewModel(DateTime day, IEnumerable<CalendarViewEvent> events) { Day = day; Events = events; } } }
Créez un fichier nommé CalendarViewModel.cs dans le répertoire ./Models et ajoutez le code suivant.
using Microsoft.Graph; using System; using System.Collections.Generic; using System.Linq; namespace GraphTutorial.Models { public class CalendarViewModel { private DateTime _startOfWeek; private DateTime _endOfWeek; private List<CalendarViewEvent> _events; public CalendarViewModel() { _startOfWeek = DateTime.MinValue; _events = new List<CalendarViewEvent>(); } public CalendarViewModel(DateTime startOfWeek, IEnumerable<Event> events) { _startOfWeek = startOfWeek; _endOfWeek = startOfWeek.AddDays(7); _events = new List<CalendarViewEvent>(); if (events != null) { foreach (var item in events) { _events.Add(new CalendarViewEvent(item)); } } } // Get the start - end dates of the week public string TimeSpan() { return $"{_startOfWeek.ToString("MMMM d, yyyy")} - {_startOfWeek.AddDays(6).ToString("MMMM d, yyyy")}"; } // Property accessors to pass to the daily view partial // These properties get all events on the specific day public DailyViewModel Sunday { get { return new DailyViewModel( _startOfWeek, GetEventsForDay(System.DayOfWeek.Sunday)); } } public DailyViewModel Monday { get { return new DailyViewModel( _startOfWeek.AddDays(1), GetEventsForDay(System.DayOfWeek.Monday)); } } public DailyViewModel Tuesday { get { return new DailyViewModel( _startOfWeek.AddDays(2), GetEventsForDay(System.DayOfWeek.Tuesday)); } } public DailyViewModel Wednesday { get { return new DailyViewModel( _startOfWeek.AddDays(3), GetEventsForDay(System.DayOfWeek.Wednesday)); } } public DailyViewModel Thursday { get { return new DailyViewModel( _startOfWeek.AddDays(4), GetEventsForDay(System.DayOfWeek.Thursday)); } } public DailyViewModel Friday { get { return new DailyViewModel( _startOfWeek.AddDays(5), GetEventsForDay(System.DayOfWeek.Friday)); } } public DailyViewModel Saturday { get { return new DailyViewModel( _startOfWeek.AddDays(6), GetEventsForDay(System.DayOfWeek.Saturday)); } } private IEnumerable<CalendarViewEvent> GetEventsForDay(System.DayOfWeek day) { return _events.Where(e => (e.End > _startOfWeek && ((e.Start.DayOfWeek.Equals(day) && e.Start >= _startOfWeek) || (e.End.DayOfWeek.Equals(day) && e.End < _endOfWeek)))); } } }
Créer des affichages
Créez un répertoire nommé Calendrier dans le répertoire ./Views .
Créez un fichier nommé _DailyEventsPartial.cshtml dans le répertoire ./Views/Calendar et ajoutez le code suivant.
@model DailyViewModel @{ bool dateCellAdded = false; var timeFormat = User.GetUserGraphTimeFormat(); var rowClass = Model.Day.Date.Equals(DateTime.Today.Date) ? "table-warning" : ""; } @if (Model.Events.Count() <= 0) { // Render an empty row for the day <tr> <td class="calendar-view-date-cell"> <div class="calendar-view-date float-left text-right">@Model.Day.Day</div> <div class="calendar-view-day">@Model.Day.ToString("dddd")</div> <div class="calendar-view-month text-muted">@Model.Day.ToString("MMMM, yyyy")</div> </td> <td></td> <td></td> </tr> } @foreach(var item in Model.Events) { <tr class="@rowClass"> @if (!dateCellAdded) { // Only add the day cell once dateCellAdded = true; <td class="calendar-view-date-cell" rowspan="@Model.Events.Count()"> <div class="calendar-view-date float-left text-right">@Model.Day.Day</div> <div class="calendar-view-day">@Model.Day.ToString("dddd")</div> <div class="calendar-view-month text-muted">@Model.Day.ToString("MMMM, yyyy")</div> </td> } <td class="calendar-view-timespan"> <div>@item.Start.ToString(timeFormat) - @item.End.ToString(timeFormat)</div> @if (item.Start.Date != Model.Day.Date) { <div class="calendar-view-date-diff">Start date: @item.Start.Date.ToShortDateString()</div> } @if (item.End.Date != Model.Day.Date) { <div class="calendar-view-date-diff">End date: @item.End.Date.ToShortDateString()</div> } </td> <td> <div class="calendar-view-subject">@item.Subject</div> <div class="calendar-view-organizer">@item.Organizer</div> </td> </tr> }
Créez un fichier nommé Index.cshtml dans le répertoire ./Views/Calendar et ajoutez le code suivant.
@model CalendarViewModel @{ ViewData["Title"] = "Calendar"; } <div class="mb-3"> <h1 class="mb-3">@Model.TimeSpan()</h1> <a class="btn btn-light btn-sm" asp-controller="Calendar" asp-action="New">New event</a> </div> <div class="calendar-week"> <div class="table-responsive"> <table class="table table-sm"> <thead> <tr> <th>Date</th> <th>Time</th> <th>Event</th> </tr> </thead> <tbody> <partial name="_DailyEventsPartial" for="Sunday" /> <partial name="_DailyEventsPartial" for="Monday" /> <partial name="_DailyEventsPartial" for="Tuesday" /> <partial name="_DailyEventsPartial" for="Wednesday" /> <partial name="_DailyEventsPartial" for="Thursday" /> <partial name="_DailyEventsPartial" for="Friday" /> <partial name="_DailyEventsPartial" for="Saturday" /> </tbody> </table> </div> </div>
Mettre à jour le contrôleur de calendrier
Ouvrez ./Controllers/CalendarController.cs et remplacez la fonction
Index
existante par ce qui suit.// Minimum permission scope needed for this view [AuthorizeForScopes(Scopes = new[] { "Calendars.Read" })] public async Task<IActionResult> Index() { try { var userTimeZone = TZConvert.GetTimeZoneInfo( User.GetUserGraphTimeZone()); var startOfWeekUtc = CalendarController.GetUtcStartOfWeekInTimeZone( DateTime.Today, userTimeZone); var events = await GetUserWeekCalendar(startOfWeekUtc); // Convert UTC start of week to user's time zone for // proper display var startOfWeekInTz = TimeZoneInfo.ConvertTimeFromUtc(startOfWeekUtc, userTimeZone); var model = new CalendarViewModel(startOfWeekInTz, events); return View(model); } catch (ServiceException ex) { if (ex.InnerException is MicrosoftIdentityWebChallengeUserException) { throw; } return View(new CalendarViewModel()) .WithError("Error getting calendar view", ex.Message); } }
Démarrez l’application, connectez-vous, puis cliquez sur le lien Calendrier. L’application doit désormais afficher un tableau des événements.
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 un modèle
Créez un fichier nommé NewEvent.cs dans le répertoire ./Models et ajoutez le code suivant.
using System; using System.ComponentModel.DataAnnotations; namespace GraphTutorial.Models { public class NewEvent { [Required] public string Subject { get; set; } public DateTime Start { get; set; } public DateTime End { get; set; } [DataType(DataType.MultilineText)] public string Body { get; set; } [RegularExpression(@"((\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*)*([;])*)*", ErrorMessage="Please enter one or more email addresses separated by a semi-colon (;)")] public string Attendees { get; set; } } }
Créer un affichage
Créez un fichier nommé New.cshtml dans le répertoire ./Views/Calendar et ajoutez le code suivant.
@model NewEvent @{ ViewData["Title"] = "New event"; } <form asp-action="New"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="Subject" class="control-label"></label> <input asp-for="Subject" class="form-control" /> <span asp-validation-for="Subject" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Attendees" class="control-label"></label> <input asp-for="Attendees" class="form-control" /> <span asp-validation-for="Attendees" class="text-danger"></span> </div> <div class="form-row"> <div class="col"> <div class="form-group"> <label asp-for="Start" class="control-label"></label> <input asp-for="Start" class="form-control" /> <span asp-validation-for="Start" class="text-danger"></span> </div> </div> <div class="col"> <div class="form-group"> <label asp-for="End" class="control-label"></label> <input asp-for="End" class="form-control" /> <span asp-validation-for="End" class="text-danger"></span> </div> </div> </div> <div class="form-group"> <label asp-for="Body" class="control-label"></label> <textarea asp-for="Body" class="form-control"></textarea> <span asp-validation-for="Body" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Save" class="btn btn-primary" /> </div> </form> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
Ajouter des actions de contrôleur
Ouvrez ./Controllers/CalendarController.cs et ajoutez l’action
CalendarController
suivante à la classe pour restituer le nouveau formulaire d’événement.// Minimum permission scope needed for this view [AuthorizeForScopes(Scopes = new[] { "Calendars.ReadWrite" })] public IActionResult New() { return View(); }
Ajoutez l’action
CalendarController
suivante à la classe pour recevoir le nouvel événement à partir du formulaire lorsque l’utilisateur clique sur Enregistrer et utiliser Microsoft Graph pour ajouter l’événement au calendrier de l’utilisateur.[HttpPost] [ValidateAntiForgeryToken] [AuthorizeForScopes(Scopes = new[] { "Calendars.ReadWrite" })] public async Task<IActionResult> New([Bind("Subject,Attendees,Start,End,Body")] NewEvent newEvent) { var timeZone = User.GetUserGraphTimeZone(); // Create a Graph event with the required fields var graphEvent = new Event { Subject = newEvent.Subject, Start = new DateTimeTimeZone { DateTime = newEvent.Start.ToString("o"), // Use the user's time zone TimeZone = timeZone }, End = new DateTimeTimeZone { DateTime = newEvent.End.ToString("o"), // Use the user's time zone TimeZone = timeZone } }; // Add body if present if (!string.IsNullOrEmpty(newEvent.Body)) { graphEvent.Body = new ItemBody { ContentType = BodyType.Text, Content = newEvent.Body }; } // Add attendees if present if (!string.IsNullOrEmpty(newEvent.Attendees)) { var attendees = newEvent.Attendees.Split(';', StringSplitOptions.RemoveEmptyEntries); if (attendees.Length > 0) { var attendeeList = new List<Attendee>(); foreach (var attendee in attendees) { attendeeList.Add(new Attendee{ EmailAddress = new EmailAddress { Address = attendee }, Type = AttendeeType.Required }); } graphEvent.Attendees = attendeeList; } } try { // Add the event await _graphClient.Me.Events .Request() .AddAsync(graphEvent); // Redirect to the calendar view with a success message return RedirectToAction("Index").WithSuccess("Event created"); } catch (ServiceException ex) { // Redirect to the calendar view with an error message return RedirectToAction("Index") .WithError("Error creating event", ex.Error.Message); } }
Démarrez l’application, connectez-vous, puis cliquez sur le lien Calendrier. Cliquez sur le bouton Nouvel événement , remplissez le formulaire, puis cliquez sur Enregistrer.
Félicitations !
Vous avez terminé le didacticiel ASP.NET Core 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
N’hésitez pas à nous faire part de vos 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.