Criar aplicativos Webassembly mais elaborados com o Microsoft Graph
Este tutorial ensina como criar um aplicativo Webassembly do lado do cliente que usa a API do Microsoft Graph para recuperar informações de calendário de um usuário.This tutorial teaches you how to build a client-side Blazor WebAssembly app that uses the Microsoft Graph API to retrieve calendar information for a user.
Dica
Se preferir baixar o tutorial concluído, você poderá baixar ou clonar o repositório do GitHub.If you prefer to just download the completed tutorial, you can download or clone the GitHub repository. Consulte o arquivo LEIAme na pasta demonstração para obter instruções sobre como configurar o aplicativo com uma ID de aplicativo e segredo.See the README file in the demo folder for instructions on configuring the app with an app ID and secret.
Pré-requisitosPrerequisites
Antes de iniciar este tutorial, você deve ter o SDK do .NET Core instalado em sua máquina de desenvolvimento.Before you start this tutorial, you should have the .NET Core SDK installed on your development machine. Se você não tiver o SDK, visite o link anterior para opções de download.If you do not have the SDK, visit the previous link for download options.
Você também deve ter uma conta pessoal da Microsoft com uma caixa de correio no Outlook.com ou uma conta corporativa ou de estudante da Microsoft.You should also have either a personal Microsoft account with a mailbox on Outlook.com, or a Microsoft work or school account. Se você não tem uma conta da Microsoft, há algumas opções para obter uma conta gratuita:If you don't have a Microsoft account, there are a couple of options to get a free account:
- Você pode se inscrever para uma nova conta pessoal da Microsoft.You can sign up for a new personal Microsoft account.
- Você pode se inscrever no programa para desenvolvedores do office 365 para obter uma assinatura gratuita do Office 365.You can sign up for the Office 365 Developer Program to get a free Office 365 subscription.
Observação
Este tutorial foi escrito com o .NET Core SDK versão 3.1.402.This tutorial was written with .NET Core SDK version 3.1.402. As etapas deste guia podem funcionar com outras versões, mas que não foram testadas.The steps in this guide may work with other versions, but that has not been tested.
ComentáriosFeedback
Forneça comentários sobre este tutorial no repositório do GitHub.Please provide any feedback on this tutorial in the GitHub repository.
Criar um aplicativo Webassembly mais
Comece criando um aplicativo Webassembly de mais.Start by creating a Blazor WebAssembly app.
Abra a interface de linha de comando (CLI) em um diretório onde você deseja criar o projeto.Open your command-line interface (CLI) in a directory where you want to create the project. Execute o seguinte comando.Run the following command.
dotnet new blazorwasm --auth SingleOrg -o GraphTutorial
O
--auth SingleOrg
parâmetro faz com que o projeto gerado inclua a configuração de autenticação com a plataforma de identidade da Microsoft.The--auth SingleOrg
parameter causes the generated project to include configuration for authentication with the Microsoft identity platform.Depois que o projeto for criado, verifique se ele funciona alterando o diretório atual para o diretório GraphTutorial e executando o seguinte comando na sua 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 watch run
Abra o navegador e navegue até
https://localhost:5001
.Open your browser and browse tohttps://localhost:5001
. Se tudo estiver funcionando, você deverá ver um "Olá, mundo!"If everything is working, you should see a "Hello, world!" Mensagem.message.
Importante
Se você receber um aviso de que o certificado para localhost é não confiável, você pode usar a CLI do .NET Core para instalar e confiar no certificado de desenvolvimento.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. Consulte impor HTTPS no ASP.NET Core para obter instruções para sistemas operacionais específicos.See Enforce HTTPS in ASP.NET Core for instructions for specific operating systems.
Adicionar pacotes NuGetAdd NuGet packages
Antes de prosseguir, instale alguns pacotes NuGet adicionais que serão usados posteriormente.Before moving on, install some additional NuGet packages that you will use later.
- Microsoft. Graph para fazer chamadas para o Microsoft Graph.Microsoft.Graph for making calls to Microsoft Graph.
- Timezoneconverter para conversão de identificadores de fuso horário do Windows em identificadores da IANA.TimeZoneConverter for translating Windows time zone identifiers to IANA identifiers.
Execute os seguintes comandos em sua CLI para instalar as dependências.Run the following commands in your CLI to install the dependencies.
dotnet add package Microsoft.Graph --version 3.18.0 dotnet add package TimeZoneConverter
Projetar o aplicativoDesign the app
Nesta seção, você criará a estrutura básica de interface do usuário do aplicativo.In this section you will create the basic UI structure of the application.
Remover exemplos de páginas geradas pelo modelo.Remove sample pages generated by the template. Exclua os arquivos a seguir.Delete the following files.
- ./Pages/Counter.razor./Pages/Counter.razor
- ./Pages/FetchData.razor./Pages/FetchData.razor
- ./Shared/SurveyPrompt.razor./Shared/SurveyPrompt.razor
- ./wwwroot/Sample-data/weather.jsem./wwwroot/sample-data/weather.json
Abra ./wwwroot/index.html e adicione o seguinte código imediatamente antes da marca de fechamento
</body>
.Open ./wwwroot/index.html and add the following code just before the closing</body>
tag.<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
Isso adiciona os arquivos de inicialização JavaScript.This adds the Bootstrap javascript files.
Abra ./wwwroot/CSS/app.css e adicione o código a seguir.Open ./wwwroot/css/app.css and add the following code.
.nav-profile-photo { width: 36px; }
Abra ./Shared/NavMenu.Razor e substitua seu conteúdo pelo seguinte.Open ./Shared/NavMenu.razor and replace its contents with the following.
<div class="top-row pl-4 navbar navbar-dark"> <a class="navbar-brand" href="">GraphTutorial</a> <button class="navbar-toggler" @onclick="ToggleNavMenu"> <span class="navbar-toggler-icon"></span> </button> </div> <div class="@NavMenuCssClass" @onclick="ToggleNavMenu"> <ul class="nav flex-column"> <li class="nav-item px-3"> <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> <span class="oi oi-home" aria-hidden="true"></span> Home </NavLink> </li> <li class="nav-item px-3"> <NavLink class="nav-link" href="calendar"> <span class="oi oi-calendar" aria-hidden="true"></span> Calendar </NavLink> </li> </ul> </div> @code { private bool collapseNavMenu = true; private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; private void ToggleNavMenu() { collapseNavMenu = !collapseNavMenu; } }
Abra ./pages/index.Razor e substitua seu conteúdo pelo seguinte.Open ./Pages/Index.razor and replace its contents with the following.
@page "/" <div class="jumbotron"> <h1>Blazor Client-side Graph Tutorial</h1> <p class="lead">This sample app shows how to use the Microsoft Graph API to access a user's data from a Blazor client-side app</p> <AuthorizeView> <Authorized> <h4>Welcome @context.User.Identity.Name!</h4> <p>Use the navigation bar on the left to get started.</p> </Authorized> <NotAuthorized> <a class="btn btn-primary btn-large" href="authentication/login">Click here to sign in</a> </NotAuthorized> </AuthorizeView> </div>
Abra ./Shared/LoginDisplay.Razor e substitua seu conteúdo pelo seguinte.Open ./Shared/LoginDisplay.razor and replace its contents with the following.
@using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @inject NavigationManager Navigation @inject SignOutSessionStateManager SignOutManager <AuthorizeView> <Authorized> <a class="text-decoration-none" data-toggle="dropdown" href="#" role="button"> <img src="/img/no-profile-photo.png" 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">@context.User.Identity.Name</h5> <p class="dropdown-item-text text-muted mb-0">placeholder@contoso.com</p> <div class="dropdown-divider"></div> <button class="dropdown-item" @onclick="BeginLogout">Log out</button> </div> </Authorized> <NotAuthorized> <a href="authentication/login">Log in</a> </NotAuthorized> </AuthorizeView> @code{ private async Task BeginLogout(MouseEventArgs args) { await SignOutManager.SetSignOutState(); Navigation.NavigateTo("authentication/logout"); } }
Crie um novo diretório no diretório ./wwwroot chamado img.Create a new directory in the ./wwwroot directory named img. Adicione um arquivo de imagem de sua escolha nomeada no-profile-photo.png nesse diretório.Add an image file of your choosing named no-profile-photo.png in this directory. Esta imagem será usada como foto do usuário quando o usuário não tiver nenhuma foto no Microsoft Graph.This image will be used as the user's photo when the user has no photo in Microsoft Graph.
Salve todas as suas alterações e atualize a página.Save all of your changes and refresh the page.
Registrar o aplicativo no portal
Neste exercício, você criará um novo registro de aplicativo Web do Azure AD usando o centro de administração do Azure Active Directory.In this exercise, you will create a new Azure AD web application registration using the Azure Active Directory admin center.
Abra um navegador e navegue até o centro de administração do Azure Active Directory.Open a browser and navigate to the Azure Active Directory admin center. Faça logon usando uma conta pessoal (também conhecida como Conta da Microsoft) ou Conta Corporativa ou de Estudante.Login using a personal account (aka: Microsoft Account) or Work or School Account.
Selecione Azure Active Directory na navegação esquerda e selecione Registros de aplicativos em Gerenciar.Select Azure Active Directory in the left-hand navigation, then select App registrations under Manage.
Selecione Novo registro.Select New registration. Na página Registrar um aplicativo, defina os valores da seguinte forma.On the Register an application page, set the values as follows.
- Defina Nome para
Blazor Graph Tutorial
.Set Name toBlazor Graph Tutorial
. - Defina Tipos de conta com suporte para Contas em qualquer diretório organizacional e contas pessoais da Microsoft.Set Supported account types to Accounts in any organizational directory and personal Microsoft accounts.
- Em URI de Redirecionamento, defina o primeiro menu suspenso para
Web
e defina o valor comohttps://localhost:5001/authentication/login-callback
.Under Redirect URI, set the first drop-down toWeb
and set the value tohttps://localhost:5001/authentication/login-callback
.
- Defina Nome para
Selecione Registrar.Select Register. Na página de tutorial do gráfico mais novo, copie o valor da ID do aplicativo (cliente) e salve-o, você precisará dele na próxima etapa.On the Blazor Graph Tutorial page, copy the value of the Application (client) ID and save it, you will need it in the next step.
Selecione Autenticação em Gerenciar.Select Authentication under Manage. Localize a seção Grant implícita e habilite tokens de acesso e tokens de ID.Locate the Implicit grant section and enable Access tokens and ID tokens. Selecione Salvar.Select Save.
Adicionar autenticação do Azure AD
Neste exercício, você estenderá o aplicativo do exercício anterior para oferecer suporte à autenticação com o Azure AD.In this exercise you will extend the application from the previous exercise to support authentication with Azure AD. Isso é necessário para obter o token de acesso OAuth necessário para chamar a API do Microsoft Graph.This is required to obtain the necessary OAuth access token to call the Microsoft Graph API.
Abrir ./wwwroot/appsettings.jsem.Open ./wwwroot/appsettings.json. Adicione uma
GraphScopes
propriedade e atualize osAuthority
ClientId
valores e para que correspondam ao seguinte.Add aGraphScopes
property and update theAuthority
andClientId
values to match the following.{ "AzureAd": { "Authority": "https://login.microsoftonline.com/common", "ClientId": "YOUR_APP_ID_HERE", "ValidateAuthority": true }, "GraphScopes": "User.Read;MailboxSettings.Read;Calendars.ReadWrite" }
Substitua
YOUR_APP_ID_HERE
pela ID do aplicativo do registro do aplicativo.ReplaceYOUR_APP_ID_HERE
with the application ID from your app registration.Importante
Se você estiver usando o controle de origem como o Git, agora seria uma boa hora para excluir o appsettings.jsno arquivo do controle de origem para evitar vazar inadvertidamente sua ID de aplicativo.If you're using source control such as git, now would be a good time to exclude the appsettings.json file from source control to avoid inadvertently leaking your app ID.
Revise os escopos incluídos no
GraphScopes
valor.Review the scopes included in theGraphScopes
value.- User. Read permite ao aplicativo obter o perfil e a foto do usuário.User.Read allows the application to get the user's profile and photo.
- MailboxSettings. Read permite que o aplicativo obtenha configurações de caixa de correio, o que inclui o fuso horário preferencial do usuário.MailboxSettings.Read allows the application to get mailbox settings, which includes the user's preferred time zone.
- Calendars. ReadWrite permite que o aplicativo Leia e grave no calendário do usuário.Calendars.ReadWrite allows the application to read and write to the user's calendar.
Implementar logonImplement sign-in
Neste ponto, o modelo de projeto do .NET Core adicionou o código para habilitar o logon.At this point the .NET Core project template has added the code to enable sign in. No entanto, nesta seção, você adicionará um código adicional para melhorar a experiência adicionando informações do Microsoft Graph à identidade do usuário.However, in this section you'll add additional code to improve the experience by adding information from Microsoft Graph to the user's identity.
Abra ./Pages/Authentication.Razor e substitua seu conteúdo pelo seguinte.Open ./Pages/Authentication.razor and replace its contents with the following.
@page "/authentication/{action}" @using Microsoft.AspNetCore.Components.WebAssembly.Authentication <RemoteAuthenticatorView Action="@Action" LogInFailed="LogInFailedFragment" /> @code{ [Parameter] public string Action { get; set; } private static RenderFragment LogInFailedFragment(string message) { return builder => { builder.OpenElement(0, "div"); builder.AddAttribute(1, "class", "alert alert-danger"); builder.AddAttribute(2, "role", "alert"); builder.OpenElement(3, "p"); builder.AddContent(4, "There was an error trying to log you in."); builder.CloseElement(); if (!string.IsNullOrEmpty(message)) { builder.OpenElement(5, "p"); builder.AddContent(6, $"Error: {message}"); builder.CloseElement(); } builder.CloseElement(); }; } }
Isso substitui a mensagem de erro padrão quando o logon falha ao exibir qualquer mensagem de erro retornada pelo processo de autenticação.This replaces the default error message when login fails to display any error message returned by the authentication process.
Crie um novo diretório na raiz do projeto chamado Graph.Create a new directory in the root of the project named Graph.
Crie um novo arquivo no diretório ./Graph chamado GraphUserAccountFactory.cs e adicione o código a seguir.Create a new file in the ./Graph directory named GraphUserAccountFactory.cs and add the following code.
using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; using Microsoft.Extensions.Logging; using Microsoft.Graph; namespace GraphTutorial.Graph { // Extends the AccountClaimsPrincipalFactory that builds // a user identity from the identity token. // This class adds additional claims to the user's ClaimPrincipal // that hold values from Microsoft Graph public class GraphUserAccountFactory : AccountClaimsPrincipalFactory<RemoteUserAccount> { private readonly IAccessTokenProviderAccessor accessor; private readonly ILogger<GraphUserAccountFactory> logger; public GraphUserAccountFactory(IAccessTokenProviderAccessor accessor, ILogger<GraphUserAccountFactory> logger) : base(accessor) { this.accessor = accessor; this.logger = logger; } public async override ValueTask<ClaimsPrincipal> CreateUserAsync( RemoteUserAccount account, RemoteAuthenticationUserOptions options) { // Create the base user var initialUser = await base.CreateUserAsync(account, options); // If authenticated, we can call Microsoft Graph if (initialUser.Identity.IsAuthenticated) { try { // Add additional info from Graph to the identity await AddGraphInfoToClaims(accessor, initialUser); } catch (AccessTokenNotAvailableException exception) { logger.LogError($"Graph API access token failure: {exception.Message}"); } catch (ServiceException exception) { logger.LogError($"Graph API error: {exception.Message}"); logger.LogError($"Response body: {exception.RawResponseBody}"); } } return initialUser; } private async Task AddGraphInfoToClaims( IAccessTokenProviderAccessor accessor, ClaimsPrincipal claimsPrincipal) { // TEMPORARY: Get the token and log it var result = await accessor.TokenProvider.RequestAccessToken(); if (result.TryGetToken(out var token)) { logger.LogInformation($"Access token: {token.Value}"); } } } }
Essa classe estende a classe AccountClaimsPrincipalFactory e substitui o
CreateUserAsync
método.This class extends the AccountClaimsPrincipalFactory class and overrides theCreateUserAsync
method. Por enquanto, esse método registra somente o token de acesso para fins de depuração.For now, this method only logs the access token for debugging purposes. Você implementará as chamadas do Microsoft Graph posteriormente neste exercício.You'll implement the Microsoft Graph calls later in this exercise.Abra ./Program.cs e adicione as seguintes
using
instruções na parte superior do arquivo.Open ./Program.cs and add the followingusing
statements at the top of the file.using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using GraphTutorial.Graph;
Dentro
Main
, substitua abuilder.Services.AddMsalAuthentication
chamada existente pelo seguinte.InsideMain
, replace the existingbuilder.Services.AddMsalAuthentication
call with the following.builder.Services.AddMsalAuthentication<RemoteAuthenticationState, RemoteUserAccount>(options => { var scopes = builder.Configuration.GetValue<string>("GraphScopes"); if (string.IsNullOrEmpty(scopes)) { Console.WriteLine("WARNING: No permission scopes were found in the GraphScopes app setting. Using default User.Read."); scopes = "User.Read"; } foreach(var scope in scopes.Split(';')) { Console.WriteLine($"Adding {scope} to requested permissions"); options.ProviderOptions.DefaultAccessTokenScopes.Add(scope); } builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); }) .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount, GraphUserAccountFactory>();
Considere o que esse código faz.Consider what this code does.
- Ele carrega o valor de
GraphScopes
de appsettings.jsem e adiciona cada escopo aos escopos padrão usados pelo provedor MSAL.It loads the value ofGraphScopes
from appsettings.json and adds each scope to the default scopes used by the MSAL provider. - Substitui o alocador de conta existente pela classe GraphUserAccountFactory .It replaces the existing account factory with the GraphUserAccountFactory class.
- Ele carrega o valor de
Salve suas alterações e reinicie o aplicativo.Save your changes and restart the app. Use o link fazer logon para fazer logon.Use the Log in link to log in. Revise e aceite as permissões solicitadas.Review and accept the requested permissions.
O aplicativo é atualizado com uma mensagem de boas-vindas.The app refreshes with a welcome message. Acesse as ferramentas de desenvolvedor do navegador e examine a guia console . O aplicativo registra o token de acesso.Access your browser's developer tools and review the Console tab. The app logs the access token.
Obter detalhes do usuárioGet user details
Depois que o usuário estiver conectado, você pode obter as informações do Microsoft Graph.Once the user is logged in, you can get their information from Microsoft Graph. Nesta seção, você usará informações do Microsoft Graph para adicionar declarações adicionais ao ClaimsPrincipal do usuário.In this section you'll use information from Microsoft Graph to add additional claims to the user's ClaimsPrincipal.
Crie um novo arquivo no diretório ./Graph chamado GraphClaimsPrincipalExtensions.cs e adicione o código a seguir.Create a new file in the ./Graph directory named GraphClaimsPrincipalExtensions.cs and add the following code.
using Microsoft.Graph; using System; using System.IO; using System.Security.Claims; namespace GraphTutorial { public static class GraphClaimTypes { public const string DateFormat = "graph_dateformat"; 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 GetUserGraphDateFormat(this ClaimsPrincipal claimsPrincipal) { var claim = claimsPrincipal.FindFirst(GraphClaimTypes.DateFormat); return claim == null ? null : claim.Value; } public static string GetUserGraphEmail(this ClaimsPrincipal claimsPrincipal) { var claim = claimsPrincipal.FindFirst(GraphClaimTypes.Email); return claim == null ? null : claim.Value; } public static string GetUserGraphPhoto(this ClaimsPrincipal claimsPrincipal) { var claim = claimsPrincipal.FindFirst(GraphClaimTypes.Photo); return claim == null ? null : claim.Value; } public static string GetUserGraphTimeZone(this ClaimsPrincipal claimsPrincipal) { var claim = claimsPrincipal.FindFirst(GraphClaimTypes.TimeZone); return claim == null ? null : claim.Value; } public static string GetUserGraphTimeFormat(this ClaimsPrincipal claimsPrincipal) { var claim = claimsPrincipal.FindFirst(GraphClaimTypes.TimeFormat); return claim == null ? null : claim.Value; } // Adds claims from the provided User object public static void AddUserGraphInfo(this ClaimsPrincipal claimsPrincipal, User user) { var identity = claimsPrincipal.Identity as ClaimsIdentity; identity.AddClaim( new Claim(GraphClaimTypes.DateFormat, user.MailboxSettings.DateFormat)); 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)); } // Converts a photo Stream to a Data URI and stores it in a claim public static void AddUserGraphPhoto(this ClaimsPrincipal claimsPrincipal, Stream photoStream) { var identity = claimsPrincipal.Identity as ClaimsIdentity; if (photoStream != null) { // 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 photoUri = $"data:image/png;base64,{Convert.ToBase64String(photoBytes)}"; identity.AddClaim( new Claim(GraphClaimTypes.Photo, photoUri)); } } } }
Este código implementa os métodos de extensão da classe ClaimsPrincipal que permitem obter e definir declarações com valores de objetos do Microsoft Graph.This code implements extension methods for the ClaimsPrincipal class that allow you to get and set claims with values from Microsoft Graph objects.
Crie um novo arquivo no diretório ./Graph chamado BlazorAuthProvider.cs e adicione o código a seguir.Create a new file in the ./Graph directory named BlazorAuthProvider.cs and add the following code.
using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; using Microsoft.Graph; namespace GraphTutorial.Graph { public class BlazorAuthProvider : IAuthenticationProvider { private readonly IAccessTokenProviderAccessor accessor; public BlazorAuthProvider(IAccessTokenProviderAccessor accessor) { this.accessor = accessor; } // Function called every time the GraphServiceClient makes a call public async Task AuthenticateRequestAsync(HttpRequestMessage request) { // Request the token from the accessor var result = await accessor.TokenProvider.RequestAccessToken(); if (result.TryGetToken(out var token)) { // Add the token to the Authorization header request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); } } } }
Este código implementa um provedor de autenticação para o SDK do Microsoft Graph que usa o IAccessTokenProviderAccessor fornecido pelo pacote Microsoft. AspNetCore. Components. Webassembly. Authentication para obter tokens de acesso.This code implements an authentication provider for the Microsoft Graph SDK that uses the IAccessTokenProviderAccessor provided by the Microsoft.AspNetCore.Components.WebAssembly.Authentication package to get access tokens.
Crie um novo arquivo no diretório ./Graph chamado GraphClientFactory.cs e adicione o código a seguir.Create a new file in the ./Graph directory named GraphClientFactory.cs and add the following code.
using System.Net.Http; using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; using Microsoft.Extensions.Logging; using Microsoft.Graph; namespace GraphTutorial.Graph { public class GraphClientFactory { private readonly IAccessTokenProviderAccessor accessor; private readonly HttpClient httpClient; private readonly ILogger<GraphClientFactory> logger; private GraphServiceClient graphClient; public GraphClientFactory(IAccessTokenProviderAccessor accessor, HttpClient httpClient, ILogger<GraphClientFactory> logger) { this.accessor = accessor; this.httpClient = httpClient; this.logger = logger; } public GraphServiceClient GetAuthenticatedClient() { // Use the existing one if it's there if (graphClient == null) { // Create a GraphServiceClient using a scoped // HttpClient graphClient = new GraphServiceClient(httpClient); // Configure the auth provider graphClient.AuthenticationProvider = new BlazorAuthProvider(accessor); } return graphClient; } } }
Essa classe cria um GraphServiceClient configurado com o BlazorAuthProvider.This class creates a GraphServiceClient configured with the BlazorAuthProvider.
Abra ./Program.cs e altere o BaseAddress do novo HttpClient para
"https://graph.microsoft.com"
.Open ./Program.cs and change the BaseAddress of the new HttpClient to"https://graph.microsoft.com"
.// HttpClient for passing into GraphServiceClient constructor builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://graph.microsoft.com") });
Adicione o código a seguir antes da
await builder.Build().RunAsync();
linha.Add the following code before theawait builder.Build().RunAsync();
line.builder.Services.AddScoped<GraphClientFactory>();
Isso adiciona o GraphClientFactory como um serviço com escopo que podemos disponibilizar por meio da injeção de dependência.This adds the GraphClientFactory as a scoped service that we can make available via dependency injection.
Abra ./Graph/GraphUserAccountFactory.cs e adicione a propriedade a seguir à classe.Open ./Graph/GraphUserAccountFactory.cs and add the following property to the class.
private readonly GraphClientFactory clientFactory;
Atualize o construtor para obter um parâmetro GraphClientFactory e atribuí-lo à
clientFactory
propriedade.Update the constructor to take a GraphClientFactory parameter and assign it to theclientFactory
property.public GraphUserAccountFactory(IAccessTokenProviderAccessor accessor, GraphClientFactory clientFactory, ILogger<GraphUserAccountFactory> logger) : base(accessor) { this.accessor = accessor; this.clientFactory = clientFactory; this.logger = logger; }
Substitua a função
AddGraphInfoToClaims
existente pelo seguinte.Replace the existingAddGraphInfoToClaims
function with the following.private async Task AddGraphInfoToClaims( IAccessTokenProviderAccessor accessor, ClaimsPrincipal claimsPrincipal) { var graphClient = clientFactory.GetAuthenticatedClient(); // Get user profile including mailbox settings // GET /me?$select=displayName,mail,mailboxSettings,userPrincipalName var user = await graphClient.Me .Request() // Request only the properties used to // set claims .Select(u => new { u.DisplayName, u.Mail, u.MailboxSettings, u.UserPrincipalName }) .GetAsync(); logger.LogInformation($"Got user: {user.DisplayName}"); claimsPrincipal.AddUserGraphInfo(user); // Get user's photo // GET /me/photos/48x48/$value var photo = await graphClient.Me .Photos["48x48"] // Smallest standard size .Content .Request() .GetAsync(); claimsPrincipal.AddUserGraphPhoto(photo); }
Considere o que esse código faz.Consider what this code does.
- Ele Obtém o perfil do usuário.It gets the user's profile.
- Ele usa
Select
para limitar quais propriedades são retornadas.It usesSelect
to limit which properties are returned.
- Ele usa
- Ele Obtém a foto do usuário.It gets the user's photo.
- Ele solicita especificamente a versão de pixel 48x48 da foto do usuário.It requests specifically the 48x48 pixel version of the user's photo.
- Ele adiciona as informações ao ClaimsPrincipal.It adds the information to the ClaimsPrincipal.
- Ele Obtém o perfil do usuário.It gets the user's profile.
Abra ./Shared/LoginDisplay.Razor e faça as seguintes alterações.Open ./Shared/LoginDisplay.razor and make the following changes.
- Substituir
/img/no-profile-photo.png
por@(context.User.GetUserGraphPhoto() ?? "/img/no-profile-photo.png")
.Replace/img/no-profile-photo.png
with@(context.User.GetUserGraphPhoto() ?? "/img/no-profile-photo.png")
. - Substituir
placeholder@contoso.com
por@context.User.GetUserGraphEmail()
.Replaceplaceholder@contoso.com
with@context.User.GetUserGraphEmail()
.
... <img src="@(context.User.GetUserGraphPhoto() ?? "/img/no-profile-photo.png")" class="nav-profile-photo rounded-circle align-self-center mr-2"> ... <p class="dropdown-item-text text-muted mb-0">@context.User.GetUserGraphEmail()</p> ...
- Substituir
Salve todas as suas alterações e reinicie o aplicativo.Save all of your changes and restart the app. Faça logon no aplicativo.Log into the app. O aplicativo atualiza para mostrar a foto do usuário no menu superior.The app updates to show the user's photo in the top menu. Selecionar a foto do usuário abre um menu suspenso com o nome do usuário, o endereço de email e um botão de logoff .Selecting the user's photo opens a drop-down menu with the user's name, email address, and a Log out button.
Obter um modo de exibição de calendário
Nesta seção, você incorporará o Microsoft Graph ao aplicativo para obter um modo de exibição do calendário do usuário para a semana atual.In this section you will further incorporate Microsoft Graph into the application to get a view of the user's calendar for the current week.
Obter um modo de exibição de calendárioGet a calendar view
Crie um novo arquivo no diretório ./Pages chamado Calendar. Razor e adicione o código a seguir.Create a new file in the ./Pages directory named Calendar.razor and add the following code.
@page "/calendar" @using Microsoft.Graph @using TimeZoneConverter @inject GraphTutorial.Graph.GraphClientFactory clientFactory <AuthorizeView> <Authorized> <!-- Temporary JSON dump of events --> <code>@graphClient.HttpProvider.Serializer.SerializeObject(events)</code> </Authorized> <NotAuthorized> <RedirectToLogin /> </NotAuthorized> </AuthorizeView> @code{ [CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; } private GraphServiceClient graphClient; private IList<Event> events = new List<Event>(); private string dateTimeFormat; }
Adicione o código a seguir na
@code{}
seção.Add the following code inside the@code{}
section.protected override async Task OnInitializedAsync() { // Get the user var user = (await authenticationStateTask).User; var graphTimeZone = user.GetUserGraphTimeZone(); dateTimeFormat = $"{user.GetUserGraphDateFormat()} {user.GetUserGraphTimeFormat()}"; // Calculate the start and end of the current week in user's time zone var startOfWeek = GetUtcStartOfWeekInTimeZone(DateTime.Today, graphTimeZone); var endOfWeek = startOfWeek.AddDays(7); graphClient = clientFactory.GetAuthenticatedClient(); // Specifies the start and end of the view on the calendar // Translates to: ?startDateTime=""&endDateTime="" var viewOptions = new List<QueryOption> { new QueryOption("startDateTime", startOfWeek.ToString("o")), new QueryOption("endDateTime", endOfWeek.ToString("o")) }; var eventPage = 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=\"{graphTimeZone}\"") // 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(); events = eventPage.CurrentPage; } private DateTime GetUtcStartOfWeekInTimeZone(DateTime today, string timeZoneId) { // Time zone returned by Graph could be Windows or IANA style // TimeZoneConverter can take either TimeZoneInfo userTimeZone = TZConvert.GetTimeZoneInfo(timeZoneId); // Assumes Sunday as first day of week int diff = System.DayOfWeek.Sunday - today.DayOfWeek; // create date as unspecified kind var unspecifiedStart = DateTime.SpecifyKind(today.AddDays(diff), DateTimeKind.Unspecified); // convert to UTC return TimeZoneInfo.ConvertTimeToUtc(unspecifiedStart, userTimeZone); }
Considere o que esse código faz.Consider what this code does.
- Ele obtém o fuso horário, o formato de data e o formato de hora do usuário atual das declarações personalizadas adicionadas ao usuário.It gets the current user's time zone, date format, and time format from the custom claims added to the user.
- Ele calcula o início e o fim da semana atual no fuso horário preferencial do usuário.It calculates the start and end of the current week in the user's preferred time zone.
- Ele obtém uma visão de calendário do Microsoft Graph para a semana atual.It gets a calendar view from Microsoft Graph for the current week.
- Inclui o
Prefer: outlook.timezone
cabeçalho para fazer com que o Microsoft Graph retorne asstart
end
Propriedades e no fuso horário especificado.It includes thePrefer: outlook.timezone
header to cause Microsoft Graph to return thestart
andend
properties in the specified time zone. - Ele usa
Top(50)
para solicitar até 50 eventos na resposta.It usesTop(50)
to request up to 50 events in the response. - Ele usa
Select(u => new {})
para solicitar apenas as propriedades usadas pelo aplicativo.It usesSelect(u => new {})
to request just the properties used by the app. - Ele usa
OrderBy("start/dateTime")
para classificar os resultados por hora de início.It usesOrderBy("start/dateTime")
to sort the results by start time.
- Inclui o
Salve todas as suas alterações e reinicie o aplicativo.Save all of your changes and restart the app. Escolha o item de navegação do calendário .Choose the Calendar nav item. O aplicativo exibe uma representação JSON dos eventos retornados do Microsoft Graph.The app displays a JSON representation of the events returned from Microsoft Graph.
Exibir os resultadosDisplay the results
Agora você pode substituir o despejo JSON por algo mais amigável.Now you can replace the JSON dump with something more user-friendly.
Adicione a função a seguir na
@code{}
seção.Add the following function inside the@code{}
section.private string FormatIso8601DateTime(string iso8601DateTime) { // Load into a DateTime var dateTime = DateTime.Parse(iso8601DateTime); if (dateTime != null) { // Format it using the user's settings return dateTime.ToString(dateTimeFormat); } // Fallback to return original value return iso8601DateTime; }
Este código usa uma cadeia de caracteres de data ISO 8601 e a converte no formato de data e hora preferencial do usuário.This code takes an ISO 8601 date string and converts it into the user's preferred date and time format.
Substitua o
<code>
elemento dentro do<Authorized>
elemento com o seguinte.Replace the<code>
element inside the<Authorized>
element with the following.<Authorized> <h1 class="mb-3">Calendar</h1> <a href="/newevent" class="btn btn-light btn-sm mb-3">New event</a> <table class="table"> <thead> <tr> <th>Organizer</th> <th>Subject</th> <th>Start</th> <th>End</th> </tr> </thead> <tbody> @foreach(var calendarEvent in events) { <tr> <td>@calendarEvent.Organizer.EmailAddress.Name</td> <td>@calendarEvent.Subject</td> <td>@FormatIso8601DateTime(calendarEvent.Start.DateTime)</td> <td>@FormatIso8601DateTime(calendarEvent.End.DateTime)</td> </tr> } </tbody> </table> </Authorized>
Isso cria uma tabela dos eventos retornados pelo Microsoft Graph.This creates a table of the events returned by Microsoft Graph.
Salve suas alterações e reinicie o aplicativo.Save your changes and restart the app. Agora, a página de calendário renderiza uma tabela de eventos.Now the Calendar page renders a table of events.
Criar um novo evento
Nesta seção, você adicionará a capacidade de criar eventos no calendário do usuário.In this section you will add the ability to create events on the user's calendar.
Crie um novo arquivo no diretório ./Pages chamado NewEvent. Razor e adicione o código a seguir.Create a new file in the ./Pages directory named NewEvent.razor and add the following code.
@page "/newevent" @using Microsoft.Graph @inject GraphTutorial.Graph.GraphClientFactory clientFactory <AuthorizeView> <Authorized> @if (!string.IsNullOrEmpty(status)) { <div class="alert @(isError ? "alert-danger" : "alert-success")">@status</div> } <form> <div class="form-group"> <label>Subject</label> <input @bind="subject" class="form-control" /> </div> <div class="form-group"> <label>Attendees</label> <input @bind="attendees" class="form-control" /> </div> <div class="form-row"> <div class="col"> <div class="form-group"> <label>Start</label> <input @bind="start" type="datetime" class="form-control" /> </div> </div> <div class="col"> <div class="form-group"> <label>End</label> <input @bind="end" type="datetime" class="form-control" /> </div> </div> </div> <div class="form-group"> <label>Body</label> <textarea @bind="body" class="form-control"></textarea> </div> </form> <button class="btn btn-primary mr-2" @onclick="CreateEvent">Create</button> <a href="/calendar" class="btn btn-secondrary">Cancel</a> </Authorized> <NotAuthorized> <RedirectToLogin /> </NotAuthorized> </AuthorizeView>
Isso adiciona um formulário à página para que o usuário possa inserir valores para o novo evento.This adds a form to the page so the user can enter values for the new event.
Adicione o código a seguir ao final do arquivo.Add the following code to the end of the file.
@code{ [CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; } private string status; private bool isError; private string userTimeZone; private string subject; private string attendees; private DateTime start = new DateTime(DateTime.Today.Ticks); private DateTime end = new DateTime(DateTime.Today.Ticks); private string body; protected override async Task OnInitializedAsync() { // Get the user's time zone var user = (await authenticationStateTask).User; userTimeZone = user.GetUserGraphTimeZone() ?? "UTC"; } private async Task CreateEvent() { // Initalize an Event object // with user input var newEvent = new Event { Subject = subject, Start = new DateTimeTimeZone { DateTime = start.ToString("o"), TimeZone = userTimeZone }, End = new DateTimeTimeZone { DateTime = end.ToString("o"), TimeZone = userTimeZone }, Body = new ItemBody { Content = body, ContentType = BodyType.Text } }; // If the user provided attendees (semicolon-delimited // list of email addresses) add them if (!string.IsNullOrEmpty(attendees)) { var attendeeList = new List<Attendee>(); var attendeeArray = attendees.Split(";"); foreach (var email in attendeeArray) { Console.WriteLine($"Adding {email}"); attendeeList.Add(new Attendee { // Set to required attendee Type = AttendeeType.Required, EmailAddress = new EmailAddress { Address = email } }); } newEvent.Attendees = attendeeList; } var graphClient = clientFactory.GetAuthenticatedClient(); try { // POST /me/events await graphClient.Me .Events .Request() .AddAsync(newEvent); isError = false; status = "Event created"; } catch (ServiceException exception) { isError = true; status = exception.Message; } } }
Considere o que esse código faz.Consider what this code does.
OnInitializedAsync
Ele obtém o fuso horário do usuário autenticado.InOnInitializedAsync
it gets the authenticated user's time zone.CreateEvent
Ele inicializa um novo objeto Event usando os valores do formulário.InCreateEvent
it initializes a new Event object using the values from the form.- Ele usa o SDK do Graph para adicionar o evento ao calendário do usuário.It uses the Graph SDK to add the event to the user's calendar.
Salve todas as suas alterações e reinicie o aplicativo.Save all of your changes and restart the app. Na página calendário , selecione novo evento.On the Calendar page, select New event. Preencha o formulário e escolha criar.Fill in the form and choose Create.
Parabéns!
Você concluiu o tutorial de Webassembly do Microsoft Graph mais completo.You've completed the Blazor WebAssembly Microsoft Graph tutorial. Agora que você tem um aplicativo de trabalho que chama o Microsoft Graph, você pode experimentar e adicionar novos recursos.Now that you have a working app that calls Microsoft Graph, you can experiment and add new features. Visite a visão geral do Microsoft Graph para ver todos os dados que você pode acessar com o Microsoft Graph.Visit the Overview of Microsoft Graph to see all of the data you can access with Microsoft Graph.
ComentáriosFeedback
Forneça comentários sobre este tutorial no repositório do GitHub.Please provide any feedback on this tutorial in the GitHub repository.
Tem algum problema com essa seção? Se tiver, envie seus comentários para que possamos melhorar esta seção.