Crear aplicaciones básicas de MVC de ASP.NET con Microsoft Graph
Este tutorial le enseña a crear una aplicación web de ASP.NET Core que use la API de Microsoft Graph para recuperar información de calendario para un usuario.This tutorial teaches you how to build an ASP.NET Core web app that uses the Microsoft Graph API to retrieve calendar information for a user.
Sugerencia
Si prefiere descargar el tutorial completo, puede descargar o clonar el repositorio de GitHub.If you prefer to just download the completed tutorial, you can download or clone the GitHub repository. Consulta el archivo README en la carpeta de demostración para obtener instrucciones sobre cómo configurar la aplicación con un identificador y un secreto de aplicación.See the README file in the demo folder for instructions on configuring the app with an app ID and secret.
Requisitos previosPrerequisites
Antes de comenzar este tutorial, debes tener instalado el SDK de .NET Core en el equipo de desarrollo.Before you start this tutorial, you should have the .NET Core SDK installed on your development machine. Si no tiene el SDK, visite el vínculo anterior para ver las opciones de descarga.If you do not have the SDK, visit the previous link for download options.
También debe tener una cuenta personal de Microsoft con un buzón en Outlook.com o una cuenta de Microsoft para el trabajo o la escuela.You should also have either a personal Microsoft account with a mailbox on Outlook.com, or a Microsoft work or school account. Si no tienes una cuenta de Microsoft, hay un par de opciones para obtener una cuenta gratuita:If you don't have a Microsoft account, there are a couple of options to get a free account:
- Puede registrarse para obtener una nueva cuenta personal de Microsoft.You can sign up for a new personal Microsoft account.
- Puede registrarse en el Programa de desarrolladores de Office 365 para obtener una suscripción gratuita a Office 365.You can sign up for the Office 365 Developer Program to get a free Office 365 subscription.
Nota
Este tutorial se escribió con .NET Core SDK versión 5.0.102.This tutorial was written with .NET Core SDK version 5.0.102. Los pasos de esta guía pueden funcionar con otras versiones, pero no se han probado.The steps in this guide may work with other versions, but that has not been tested.
ComentariosFeedback
Please provide any feedback on this tutorial in the GitHub repository.Please provide any feedback on this tutorial in the GitHub repository.
Crear una aplicación Web de MVC ASP.NET Core
Empiece por crear una ASP.NET web principal.Start by creating an ASP.NET Core web app.
Abra la interfaz de línea de comandos (CLI) en un directorio donde quiera crear el proyecto.Open your command-line interface (CLI) in a directory where you want to create the project. Ejecute el comando siguiente.Run the following command.
dotnet new mvc -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.Once the project is created, verify that it works by changing the current directory to the GraphTutorial directory and running the following command in your CLI.
dotnet run
Abra el explorador y vaya a
https://localhost:5001
.Open your browser and browse tohttps://localhost:5001
. Si todo funciona, debería ver una página ASP.NET principal predeterminada.If everything is working, you should see a default ASP.NET Core page.
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.If you receive a warning that the certificate for localhost is un-trusted you can use the .NET Core CLI to install and trust the development certificate. Consulta Aplicar HTTPS en ASP.NET Core para obtener instrucciones para sistemas operativos específicos.See Enforce HTTPS in ASP.NET Core for instructions for specific operating systems.
Agregar paquetes NuGetAdd NuGet packages
Antes de seguir adelante, instala algunos paquetes NuGet adicionales que usarás más adelante.Before moving on, install some additional NuGet packages that you will use later.
- Microsoft.Identity.Web para solicitar y administrar tokens de acceso.Microsoft.Identity.Web for requesting and managing access tokens.
- Microsoft.Identity.Web.MicrosoftGraph para agregar el SDK de Microsoft Graph mediante inserción de dependencias.Microsoft.Identity.Web.MicrosoftGraph for adding the Microsoft Graph SDK via dependency injection.
- Microsoft.Identity.Web.UI para la interfaz de usuario de inicio y de salida.Microsoft.Identity.Web.UI for sign-in and sign-out UI.
- TimeZoneConverter para controlar identificadores de zona horaria multiplataforma.TimeZoneConverter for handling time zoned identifiers cross-platform.
Ejecute los siguientes comandos en la CLI para instalar las dependencias.Run the following commands in your CLI to install the dependencies.
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
Diseñar la aplicaciónDesign the app
En esta sección crearás la estructura básica de la interfaz de usuario de la aplicación.In this section you will create the basic UI structure of the application.
Implementar métodos de extensión de alertaImplement alert extension methods
En esta sección creará métodos de extensión para el IActionResult
tipo devuelto por las vistas de controlador.In this section you will create extension methods for the IActionResult
type returned by controller views. Esta extensión permitirá pasar mensajes temporales de error o de éxito a la vista.This extension will enable passing temporary error or success messages to the view.
Sugerencia
Puede usar cualquier editor de texto para editar los archivos de origen de este tutorial.You can use any text editor to edit the source files for this tutorial. Sin embargo, Visual Studio código proporciona características adicionales, como depuración e IntelliSense.However, Visual Studio Code provides additional features, such as debugging and Intellisense.
Cree un nuevo directorio en el directorio GraphTutorial denominado Alerts.Create a new directory in the GraphTutorial directory named Alerts.
Crea un nuevo archivo denominado WithAlertResult.cs en el directorio ./Alerts y agrega el siguiente código.Create a new file named WithAlertResult.cs in the ./Alerts directory and add the following code.
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); } } }
Crea un nuevo archivo denominado AlertExtensions.cs en el directorio ./Alerts y agrega el siguiente código.Create a new file named AlertExtensions.cs in the ./Alerts directory and add the following code.
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); } } }
Implementar métodos de extensión de datos de usuarioImplement user data extension methods
En esta sección creará métodos de extensión para el ClaimsPrincipal
objeto generado por la plataforma Microsoft Identity.In this section you will create extension methods for the ClaimsPrincipal
object generated by the Microsoft Identity platform. Esto le permitirá ampliar la identidad de usuario existente con datos de Microsoft Graph.This will allow you to extend the existing user identity with data from Microsoft Graph.
Nota
Este código es solo un marcador de posición por ahora, lo completará en una sección posterior.This code is just a placeholder for now, you will complete it in a later section.
Cree un nuevo directorio en el directorio GraphTutorial denominado Graph.Create a new directory in the GraphTutorial directory named Graph.
Cree un archivo denominado GraphClaimsPrincipalExtensions.cs y agregue el siguiente código.Create a new file named GraphClaimsPrincipalExtensions.cs and add the following code.
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"; } } }
Crear vistasCreate views
En esta sección, implementará las vistas Desenlazadas para la aplicación.In this section you will implement the Razor views for the application.
Agregue un nuevo archivo denominado _LoginPartial.cshtml en el directorio ./Views/Shared y agregue el siguiente código.Add a new file named _LoginPartial.cshtml in the ./Views/Shared directory and add the following code.
@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>
Agregue un nuevo archivo denominado _AlertPartial.cshtml en el directorio ./Views/Shared y agregue el siguiente código.Add a new file named _AlertPartial.cshtml in the ./Views/Shared directory and add the following code.
@{ 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> }
Abra el archivo ./Views/Shared/_Layout.cshtml y reemplace todo su contenido por el código siguiente para actualizar el diseño global de la aplicación.Open the ./Views/Shared/_Layout.cshtml file, and replace its entire contents with the following code to update the global layout of the app.
@{ 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>
Abra ./wwwroot/css/site.css y agregue el siguiente código en la parte inferior del archivo.Open ./wwwroot/css/site.css and add the following code at the bottom of the file.
.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 }
Abra el archivo ./Views/Home/index.cshtml y reemplace su contenido por lo siguiente.Open the ./Views/Home/index.cshtml file and replace its contents with the following.
@{ 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>
Cree un nuevo directorio en el directorio ./wwwroot denominado img.Create a new directory in the ./wwwroot directory named img. Agregue un archivo de imagen que elija con el nombre no-profile-photo.png en este directorio.Add an image file of your choosing named no-profile-photo.png in this directory. Esta imagen se usará como foto del usuario cuando el usuario no tenga ninguna foto en Microsoft Graph.This image will be used as the user's photo when the user has no photo in Microsoft Graph.
Guarde todos los cambios y reinicie el servidor (
dotnet run
).Save all of your changes and restart the server (dotnet run
). Ahora, la aplicación debería tener un aspecto muy diferente.Now, the app should look very different.
Registrar la aplicación en el portal
En este ejercicio, creará un nuevo registro de aplicaciones Web de Azure AD con el centro de administración de Azure Active Directory.In this exercise, you will create a new Azure AD web application registration using the Azure Active Directory admin center.
Abra un explorador y vaya al centro de administración de Azure Active Directory.Open a browser and navigate to the Azure Active Directory admin center. Inicie sesión con una cuenta personal (también conocida como: cuenta Microsoft) o una cuenta profesional o educativa.Login using a personal account (aka: Microsoft Account) or Work or School Account.
Seleccione Azure Active Directory en el panel de navegación izquierdo y, a continuación, seleccione Registros de aplicaciones en Administrar.Select Azure Active Directory in the left-hand navigation, then select App registrations under Manage.
Seleccione Nuevo registro.Select New registration. En la página Registrar una aplicación , establezca los valores siguientes.On the Register an application page, set the values as follows.
- Establezca Nombre como
ASP.NET Core Graph Tutorial
.Set Name toASP.NET Core Graph Tutorial
. - Establezca Tipos de cuenta admitidos en Cuentas en cualquier directorio de organización y cuentas personales de Microsoft.Set Supported account types to Accounts in any organizational directory and personal Microsoft accounts.
- En URI de redirección , establezca la primera lista desplegable en
Web
y establezca el valorhttps://localhost:5001/
.Under Redirect URI , set the first drop-down toWeb
and set the value tohttps://localhost:5001/
.
- Establezca Nombre como
Seleccione Registrar.Select Register. En la página del tutorial de ASP.net Core Graph , copie el valor del identificador de la aplicación (cliente) y guárdelo, lo necesitará en el paso siguiente.On the ASP.NET Core Graph Tutorial page, copy the value of the Application (client) ID and save it, you will need it in the next step.
Seleccione Autenticación en Administrar.Select Authentication under Manage. En URI de redireccionamiento , agregue un URI con el valor
https://localhost:5001/signin-oidc
.Under Redirect URIs add a URI with the valuehttps://localhost:5001/signin-oidc
.Establezca la dirección URL de cierre de sesión en
https://localhost:5001/signout-oidc
.Set the Logout URL tohttps://localhost:5001/signout-oidc
.Busque la sección Concesión implícita y habilite los tokens de ID.Locate the Implicit grant section and enable ID tokens. Seleccione Guardar.Select Save.
Seleccione Certificados y secretos en Administrar.Select Certificates & secrets under Manage. Seleccione el botón Nuevo secreto de cliente.Select the New client secret button. Escriba un valor en Descripción y seleccione una de las opciones para Expires y seleccione Agregar.Enter a value in Description and select one of the options for Expires and select Add.
Copie el valor del secreto de cliente antes de salir de esta página.Copy the client secret value before you leave this page. Lo necesitará en el siguiente paso.You will need it in the next step.
Importante
El secreto de cliente no se vuelve a mostrar, así que asegúrese de copiarlo en este momento.This client secret is never shown again, so make sure you copy it now.
Agregar autenticación de Azure AD
En este ejercicio, ampliará la aplicación del ejercicio anterior para admitir la autenticación con Azure AD.In this exercise you will extend the application from the previous exercise to support authentication with Azure AD. Esto es necesario para obtener el token de acceso OAuth necesario para llamar a la API de Microsoft Graph.This is required to obtain the necessary OAuth access token to call the Microsoft Graph API. En este paso configurará la biblioteca Microsoft.Identity.Web.In this step you will configure the Microsoft.Identity.Web library.
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.To avoid storing the application ID and secret in source, you will use the .NET Secret Manager to store these values. El Administrador de secretos es solo con fines de desarrollo, las aplicaciones de producción deben usar un administrador de secretos de confianza para almacenar secretos.The Secret Manager is for development purposes only, production apps should use a trusted secret manager for storing secrets.
Abra ./appsettings.jsy reemplace su contenido por lo siguiente.Open ./appsettings.json and replace its contents with the following.
{ "AzureAd": { "Instance": "https://login.microsoftonline.com/", "TenantId": "common", "CallbackPath": "/signin-oidc" }, "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, sustituyendo con su identificador de aplicación desde Azure Portal y con el secreto de
YOUR_APP_ID
YOUR_APP_SECRET
aplicación.Open your CLI in the directory where GraphTutorial.csproj is located, and run the following commands, substitutingYOUR_APP_ID
with your application ID from the Azure portal, andYOUR_APP_SECRET
with your application secret.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ónImplement sign-in
Empiece por agregar los servicios de la plataforma de Microsoft Identity a la aplicación.Start by adding the Microsoft Identity platform services to the application.
Cree un archivo denominado GraphConstants.cs en el directorio ./Graph y agregue el siguiente código.Create a new file named GraphConstants.cs in the ./Graph directory and add the following code.
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" }; } }
Abra el archivo ./Startup.cs y agregue las siguientes
using
instrucciones en la parte superior del archivo.Open the ./Startup.cs file and add the followingusing
statements to the top of the file.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;
Reemplace la función
ConfigureServices
existente por lo siguiente.Replace the existingConfigureServices
function with the following.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(); }
En la
Configure
función, agregue la siguiente línea por encima de laapp.UseAuthorization();
línea.In theConfigure
function, add the following line above theapp.UseAuthorization();
line.app.UseAuthentication();
Abra ./Controllers/HomeController.cs y reemplace su contenido por lo siguiente.Open ./Controllers/HomeController.cs and replace its contents with the following.
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); } } }
Guarde los cambios e inicie el proyecto.Save your changes and start the project. Inicie sesión con su cuenta de Microsoft.Login with your Microsoft account.
Examine la solicitud de consentimiento.Examine the consent prompt. La lista de permisos corresponde a la lista de ámbitos de permisos configurados en ./Graph/GraphConstants.cs.The list of permissions correspond to list of permissions scopes configured in ./Graph/GraphConstants.cs.
- Mantenga el acceso a los datos a los que le ha concedido acceso: ( ) MSAL solicita este permiso para recuperar
offline_access
tokens de actualización.Maintain access to data you have given it access to: (offline_access
) This permission is requested by MSAL in order to retrieve refresh tokens. - Inicie sesión y lea su perfil: ( ) Este permiso permite a la aplicación obtener el perfil y la foto de perfil del usuario que ha iniciado
User.Read
sesión.Sign you in and read your profile: (User.Read
) This permission allows the app to get the logged-in user's profile and profile photo. - Lea la configuración del buzón: ( ) Este permiso permite a la aplicación leer la configuración del buzón del usuario, incluido el formato de zona
MailboxSettings.Read
horaria y hora.Read your mailbox settings: (MailboxSettings.Read
) This permission allows the app to read the user's mailbox settings, including time zone and time format. - Tener acceso completo a los calendarios: ( ) Este permiso permite a la aplicación leer eventos en el calendario del usuario, agregar nuevos eventos y modificar los
Calendars.ReadWrite
existentes.Have full access to your calendars: (Calendars.ReadWrite
) This permission allows the app to read events on the user's calendar, add new events, and modify existing ones.
Para obtener más información sobre el consentimiento, consulte Descripción de las experiencias de consentimiento de aplicaciones de Azure AD.For more information regarding consent, see Understanding Azure AD application consent experiences.
- Mantenga el acceso a los datos a los que le ha concedido acceso: ( ) MSAL solicita este permiso para recuperar
Consentimiento para los permisos solicitados.Consent to the requested permissions. El explorador redirige a la aplicación y muestra el token.The browser redirects to the app, showing the token.
Obtener detalles del usuarioGet user details
Una vez que el usuario haya iniciado sesión, puede obtener su información de Microsoft Graph.Once the user is logged in, you can get their information from Microsoft Graph.
Abra ./Graph/GraphClaimsPrincipalExtensions.cs y reemplace todo su contenido por lo siguiente.Open ./Graph/GraphClaimsPrincipalExtensions.cs and replace its entire contents with the following.
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)); } } }
Abra ./Startup.cs y reemplace la línea
.AddMicrosoftIdentityWebApp(Configuration)
existente por el siguiente código.Open ./Startup.cs and replace the existing.AddMicrosoftIdentityWebApp(Configuration)
line with the following code.// 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); }; })
Ten en cuenta lo que hace este código.Consider what this code does.
- Agrega un controlador de eventos para el
OnTokenValidated
evento.It adds an event handler for theOnTokenValidated
event.- Usa la interfaz
ITokenAcquisition
para obtener un token de acceso.It uses theITokenAcquisition
interface to get an access token. - Llama a Microsoft Graph para obtener el perfil y la foto del usuario.It calls Microsoft Graph to get the user's profile and photo.
- Agrega la información de Graph a la identidad del usuario.It adds the Graph information to the user's identity.
- Usa la interfaz
- Agrega un controlador de eventos para el
Agregue la siguiente llamada de función después de
EnableTokenAcquisitionToCallDownstreamApi
la llamada y antes de laAddInMemoryTokenCaches
llamada.Add the following function call after theEnableTokenAcquisitionToCallDownstreamApi
call and before theAddInMemoryTokenCaches
call.// Add a GraphServiceClient via dependency injection .AddMicrosoftGraph(options => { options.Scopes = string.Join(' ', GraphConstants.Scopes); })
Esto hará que graphServiceClient autenticado esté disponible para los controladores a través de la inserción de dependencias.This will make an authenticated GraphServiceClient available to controllers via dependency injection.
Abra ./Controllers/HomeController.cs y reemplace la
Index
función por lo siguiente.Open ./Controllers/HomeController.cs and replace theIndex
function with the following.public IActionResult Index() { return View(); }
Quite todas las referencias
ITokenAcquisition
a la clase HomeController.Remove all references toITokenAcquisition
in the HomeController class.Guarde los cambios, inicie la aplicación y realice el proceso de inicio de sesión.Save your changes, start the app, and go through the sign-in process. Debería volver a la página principal, pero la interfaz de usuario debe cambiar para indicar que ha iniciado sesión.You should end up back on the home page, but the UI should change to indicate that you are signed-in.
Haz clic en el avatar del usuario en la esquina superior derecha para acceder al vínculo Cerrar sesión.Click the user avatar in the top right corner to access the Sign Out link. Al hacer clic en Cerrar sesión, se restablece la sesión y se vuelve a la página principal.Clicking Sign Out resets the session and returns you to the home page.
Sugerencia
Si no ve el nombre de usuario en la página principal y falta el nombre y el correo electrónico del cuadro desplegable de avatares de uso después de realizar estos cambios, vuelva a cerrar sesión y cerrar sesión.If you do not see your user name on the home page and the use avatar dropdown is missing name and email after making these changes, sign out and sign back in.
Almacenar y actualizar tokensStoring and refreshing tokens
En este momento, la aplicación tiene un token de acceso, que se envía en el Authorization
encabezado de las llamadas API.At this point your application has an access token, which is sent in the Authorization
header of API calls. Este es el token que permite que la aplicación acceda a Microsoft Graph en nombre del usuario.This is the token that allows the app to access Microsoft Graph on the user's behalf.
Sin embargo, este token es de corta duración.However, this token is short-lived. El token expira una hora después de su emisión.The token expires an hour after it is issued. Aquí es donde el token de actualización resulta útil.This is where the refresh token becomes useful. El token de actualización permite a la aplicación solicitar un nuevo token de acceso sin necesidad de que el usuario vuelva a iniciar sesión.The refresh token allows the app to request a new access token without requiring the user to sign in again.
Dado que la aplicación usa la biblioteca Microsoft.Identity.Web, no es necesario implementar ningún almacenamiento de tokens ni lógica de actualización.Because the app is using the Microsoft.Identity.Web library, you do not have to implement any token storage or refresh logic.
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.The app uses the in-memory token cache, which is sufficient for apps that do not need to persist tokens when the app restarts. En su lugar, las aplicaciones de producción pueden usar las opciones de caché distribuida en la biblioteca Microsoft.Identity.Web.Production apps may instead use the distributed cache options in the Microsoft.Identity.Web library.
El GetAccessTokenForUserAsync
método controla la expiración del token y la actualización automáticamente.The GetAccessTokenForUserAsync
method handles token expiration and refresh for you. Primero comprueba el token almacenado en caché y, si no ha expirado, lo devuelve.It first checks the cached token, and if it is not expired, it returns it. Si ha expirado, usa el token de actualización en caché para obtener uno nuevo.If it is expired, it uses the cached refresh token to obtain a new one.
GraphServiceClient que obtienen los controladores a través de la inserción de dependencias se configurará previamente con un proveedor de autenticación GetAccessTokenForUserAsync
que lo use automáticamente.The GraphServiceClient that controllers get via dependency injection will be pre-configured with an authentication provider that uses GetAccessTokenForUserAsync
for you.
Obtener una vista de calendario
En esta sección incorporará Microsoft Graph a la aplicación.In this section you will incorporate Microsoft Graph into the application. Para esta aplicación, usará la biblioteca cliente de Microsoft Graph para .NET para realizar llamadas a Microsoft Graph.For this application, you will use the Microsoft Graph Client Library for .NET to make calls to Microsoft Graph.
Obtener eventos de calendario de OutlookGet calendar events from Outlook
Empiece por crear un controlador nuevo para las vistas de calendario.Start by creating a new controller for calendar views.
Agrega un nuevo archivo denominado CalendarController.cs en el directorio ./Controllers y agrega el siguiente código.Add a new file named CalendarController.cs in the ./Controllers directory and add the following code.
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; } } }
Agregue las siguientes funciones a la
CalendarController
clase para obtener la vista de calendario del usuario.Add the following functions to theCalendarController
class to get the user's calendar view.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); }
Ten en cuenta lo que hace el
GetUserWeekCalendar
código.Consider what the code inGetUserWeekCalendar
does.- Usa la zona horaria del usuario para obtener los valores de fecha y hora de inicio y finalización UTC de la semana.It uses the user's time zone to get UTC start and end date/time values for the week.
- Consulta la vista de calendario del usuario para obtener todos los eventos que se encuentra entre la fecha y hora de inicio y finalización.It queries the user's calendar view to get all events that fall between the start and end date/times. El uso de una vista de calendario en lugar de enumerar eventos expande los eventos periódicos y devuelve las repeticiones que se produzcan en la ventana de tiempo especificada.Using a calendar view instead of listing events expands recurring events, returning any occurrences that happen in the specified time window.
- Usa el encabezado
Prefer: outlook.timezone
para obtener resultados en la zona horaria del usuario.It uses thePrefer: outlook.timezone
header to get results back in the user's timezone. - Se usa
Select
para limitar los campos que regresan solo a los que usa la aplicación.It usesSelect
to limit the fields that come back to just those used by the app. - Se usa
OrderBy
para ordenar los resultados cronológicamente.It usesOrderBy
to sort the results chronologically. - Usa una página
PageIterator
para a través de la colección de eventos.It uses aPageIterator
to page through the events collection. Esto controla el caso en el que el usuario tiene más eventos en su calendario que el tamaño de página solicitado.This handles the case where the user has more events on their calendar than the requested page size.
Agregue la siguiente función a la
CalendarController
clase para implementar una vista temporal de los datos devueltos.Add the following function to theCalendarController
class to implement a temporary view of the returned data.// 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" }; } }
Inicie la aplicación, inicie sesión y haga clic en el vínculo Calendario de la barra de navegación.Start the app, sign in, and click the Calendar link in the nav bar. Si todo funciona, debería ver un volcado JSON de eventos en el calendario del usuario.If everything works, you should see a JSON dump of events on the user's calendar.
Mostrar los resultadosDisplay the results
Ahora puede agregar una vista para mostrar los resultados de una manera más fácil de usar.Now you can add a view to display the results in a more user-friendly manner.
Crear modelos de vistaCreate view models
Cree un archivo denominado CalendarViewEvent.cs en el directorio ./Models y agregue el siguiente código.Create a new file named CalendarViewEvent.cs in the ./Models directory and add the following code.
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); } } }
Cree un archivo denominado DailyViewModel.cs en el directorio ./Models y agregue el siguiente código.Create a new file named DailyViewModel.cs in the ./Models directory and add the following code.
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; } } }
Cree un archivo denominado CalendarViewModel.cs en el directorio ./Models y agregue el siguiente código.Create a new file named CalendarViewModel.cs in the ./Models directory and add the following code.
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)))); } } }
Crear vistasCreate views
Cree un directorio denominado Calendario en el directorio ./Views.Create a new directory named Calendar in the ./Views directory.
Cree un archivo denominado _DailyEventsPartial.cshtml en el directorio ./Views/Calendar y agregue el siguiente código.Create a new file named _DailyEventsPartial.cshtml in the ./Views/Calendar directory and add the following code.
@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> }
Cree un nuevo archivo denominado Index.cshtml en el directorio ./Views/Calendar y agregue el siguiente código.Create a new file named Index.cshtml in the ./Views/Calendar directory and add the following code.
@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>
Actualizar controlador de calendarioUpdate calendar controller
Abra ./Controllers/CalendarController.cs y reemplace la función
Index
existente por lo siguiente.Open ./Controllers/CalendarController.cs and replace the existingIndex
function with the following.// 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); } }
Inicia la aplicación, inicia sesión y haz clic en el vínculo Calendario.Start the app, sign in, and click the Calendar link. La aplicación ahora debe representar una tabla de eventos.The app should now render a table of events.
Crear un nuevo evento
En esta sección, agregará la capacidad de crear eventos en el calendario del usuario.In this section you will add the ability to create events on the user's calendar.
Crear modeloCreate model
Cree un nuevo archivo denominado NewEvent.CS en el directorio ./Models y agregue el siguiente código.Create a new file named NewEvent.cs in the ./Models directory and add the following code.
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; } } }
Crear vistaCreate view
Cree un nuevo archivo denominado New. cshtml en el directorio /views/Calendar y agregue el siguiente código.Create a new file named New.cshtml in he ./Views/Calendar directory and add the following code.
@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");} }
Agregar acciones de controladorAdd controller actions
Abra ./Controllers/CalendarController.CS y agregue la siguiente acción a la
CalendarController
clase para representar el nuevo formulario de evento.Open ./Controllers/CalendarController.cs and add the following action to theCalendarController
class to render the new event form.// Minimum permission scope needed for this view [AuthorizeForScopes(Scopes = new[] { "Calendars.ReadWrite" })] public IActionResult New() { return View(); }
Agregue la siguiente acción a la
CalendarController
clase para recibir el nuevo evento del formulario cuando el usuario haga clic en Guardar y use Microsoft Graph para agregar el evento al calendario del usuario.Add the following action to theCalendarController
class to receive the new event from the form when the user clicks Save and use Microsoft Graph to add the event to the user's calendar.[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); } }
Inicie la aplicación, inicie sesión y haga clic en el vínculo calendario .Start the app, sign in, and click the Calendar link. Haga clic en el botón nuevo evento , rellene el formulario y haga clic en Guardar.Click the New event button, fill in the form, and click Save.
¡Enhorabuena!
Ha completado el tutorial ASP.NET básico de Microsoft Graph.You've completed the ASP.NET Core Microsoft Graph tutorial. Ahora que tiene una aplicación de trabajo que llama a Microsoft Graph, puede experimentar y agregar nuevas características.Now that you have a working app that calls Microsoft Graph, you can experiment and add new features. Visite la información general de Microsoft Graph para ver todos los datos a los que puede tener acceso con Microsoft Graph.Visit the Overview of Microsoft Graph to see all of the data you can access with Microsoft Graph.
ComentariosFeedback
Envíe sus comentarios sobre este tutorial en el repositorio de github.Please provide any feedback on this tutorial in the GitHub repository.
¿Tiene algún problema con esta sección? Si es así, envíenos sus comentarios para que podamos mejorarla.