Criar bots da estrutura de bot com o Microsoft Graph
Este tutorial ensina como criar um bot da Estrutura de Bots que usa a API do Microsoft Graph para recuperar informações de calendário para um usuário.
Dica
Se você preferir apenas baixar o tutorial concluído, poderá baixar ou clonar o GitHub repositório. Consulte o arquivo README na pasta de demonstração para obter instruções sobre como configurar o aplicativo com uma ID do aplicativo e segredo.
Pré-requisitos
Antes de iniciar este tutorial, você deve ter o seguinte instalado em sua máquina de desenvolvimento.
Você também deve ter uma conta pessoal da Microsoft com uma caixa de correio em Outlook.com, ou uma conta de trabalho ou de estudante da Microsoft. Se você não tiver uma conta da Microsoft, há algumas opções para obter uma conta gratuita:
- Você pode se inscrever em uma nova conta pessoal da Microsoft.
- Você pode se inscrever no programa Microsoft 365 desenvolvedor para obter uma assinatura Microsoft 365 gratuita.
- Uma assinatura do Azure. Se você não tiver uma, crie uma conta gratuita antes de começar.
Observação
Este tutorial foi escrito com as seguintes versões. As etapas neste guia podem funcionar com outras versões, mas que não foram testadas.
- .NET Core SDK versão 5.0.302
- Bot Framework Emulator 4.1.3
- ngrok 2.3.40
Comentários
Forneça qualquer comentário sobre este tutorial no repositório GitHub.
Criar um projeto de estrutura de bot
Nesta seção, você criará um projeto da Estrutura de Bot.
Abra sua interface de linha de comando (CLI) em um diretório onde você deseja criar o projeto. Execute o seguinte comando para criar um novo projeto usando o modelo Microsoft.Bot.Framework.CSharp.EchoBot .
dotnet new echobot -n GraphCalendarBot
Observação
Se você receber um
No templates matched the input template name: echobot.
erro, instale o modelo com o seguinte comando e execute o comando anterior.dotnet new -i Microsoft.Bot.Framework.CSharp.EchoBot
Renomeie a classe padrão EchoBot para CalendarBot. Abra ./Bots/EchoBot.cs e substitua todas as instâncias de
EchoBot
porCalendarBot
. Renomeie o arquivo para CalendarBot.cs.Substitua todas as instâncias dos
EchoBot
CalendarBot
arquivos .cs restantes .Em sua CLI, altere o diretório atual para o diretório GraphCalendarBot e execute o seguinte comando para confirmar as builds do projeto.
dotnet build
Adicionar pacotes NuGet
Antes de continuar, instale alguns pacotes NuGet que você usará posteriormente.
- AdaptiveCards para permitir que o bot envie Cartões Adaptáveis em respostas.
- Microsoft.Bot.Builder.Dialogs para adicionar suporte à caixa de diálogo ao bot.
- Microsoft.Recognizers.Text.DataTypes.TimexExpression para converter as expressões TIMEX retornadas de prompts de bot em objetos DateTime .
- Microsoft.Graph para fazer chamadas para o Microsoft Graph.
Execute os seguintes comandos em sua CLI para instalar as dependências.
dotnet add package AdaptiveCards --version 2.7.1 dotnet add package Microsoft.Bot.Builder.Dialogs --version 4.14.1 dotnet add package Microsoft.Bot.Builder.Integration.AspNet.Core --version 4.14.1 dotnet add package Microsoft.Recognizers.Text.DataTypes.TimexExpression --version 1.8.0 dotnet add package Microsoft.Graph --version 4.1.0
Testar o bot
Antes de adicionar qualquer código, teste o bot para garantir que ele funcione corretamente e se o Bot Framework Emulator está configurado para testá-lo.
Inicie o bot executando o seguinte comando.
dotnet run
Dica
Embora você possa usar qualquer editor de texto para editar os arquivos de origem no projeto, recomendamos o uso Visual Studio Code. Visual Studio Code oferece suporte à depuração, Intellisense e muito mais. Se estiver Visual Studio Code, você poderá iniciar o bot usando o menu Depuração do RunStart -> .
Confirme se o bot está sendo executado abrindo seu navegador e indo para
http://localhost:3978
. Você deve ver que seu bot está pronto! Mensagem.Abra o Bot Framework Emulator. Escolha o menu Arquivo e Abra Bot.
Insira
http://localhost:3978/api/messages
a URL do Bot e selecione Conexão.O bot responde na
Hello and welcome!
janela de chat. Envie uma mensagem para o bot e confirme se ela a ecoa de volta.
Registrar o bot no portal
Neste exercício, você criará um novo registro de Canais bot e um registro de aplicativo Web do Azure AD usando o Portal do Azure.
Criar um registro de Canais de Bot
Abra um navegador e navegue até o Portal do Azure. Faça logon usando a conta associada à sua assinatura do Azure.
Selecione o menu superior esquerdo e selecione Criar um recurso.
Na página Novo , pesquise e
Azure Bot
selecione Bot do Azure.Na página Bot do Azure , selecione Criar.
Preencha os campos necessários e deixe o ponto de extremidade mensagens em branco. O campo de alça bot deve ser exclusivo. Revise as diferentes camadas de preços e selecione o que faz sentido para seu cenário. Se isso for apenas um exercício de aprendizagem, talvez você queira selecionar a opção gratuita.
Para a ID do Aplicativo Microsoft, selecione Criar nova ID do Aplicativo Microsoft.
Selecione Examinar + criar. Depois que a validação é concluída, selecione Criar.
Depois que a implantação for concluída, selecione Ir para o recurso.
Em Configurações, selecione Configuração. Selecione o link Gerenciar ao lado da ID do Microsoft App.
Selecione Novo segredo do cliente. Adicione uma descrição e escolha uma expiração e selecione Adicionar.
Copie o valor de segredo do cliente antes de sair desta página. Você precisará dele nas etapas a seguir.
Importante
Esse segredo do cliente nunca é mostrado novamente, portanto, copie-o agora. Você precisará inserir esse valor em vários locais, portanto, mantenha-o seguro.
Selecione Visão geral no menu à esquerda. Copie o valor da ID do aplicativo (cliente) e salve-o, você precisará dele nas etapas a seguir.
Retorne à janela Registro de Canal bot no navegador e colar a ID do aplicativo no campo ID do Aplicativo microsoft. Colar o segredo do cliente no campo Senha . Selecione OK.
Na página Registro de Canais de Bots , selecione Criar.
Aguarde até que o registro de Canais bots seja criado. Depois de criado, retorne à home page no Portal do Azure e selecione Serviços bot. Selecione seu novo registro do Canal bots para exibir suas propriedades.
Criar um registro de aplicativo Web
Volte para a home page do portal do Azure e selecione Azure Active Directory.
Selecione Registros de aplicativos.
Selecione Novo registro. Na página Registrar um aplicativo, defina os valores da seguinte forma.
- Defina Nome para
Graph Calendar Bot Auth
. - Defina Tipos de conta com suporte para Contas em qualquer diretório organizacional e contas pessoais da Microsoft.
- Em URI de Redirecionamento, defina o primeiro menu suspenso para
Web
e defina o valor comohttps://token.botframework.com/.auth/web/redirect
.
- Defina Nome para
Selecione Registrar. Na página Graph Calendário Bot Auth, copie o valor da ID do Aplicativo (cliente) e salve-a, você precisará dele nas etapas a seguir.
Selecione Certificados e segredos sob Gerenciar. Selecione o botão Novo segredo do cliente. Insira um valor em Descrição e selecione uma das opções para Expira em e selecione Adicionar.
Copie o valor de segredo do cliente antes de sair desta página. Você precisará dele nas etapas a seguir.
Selecione permissões de API e selecione Adicionar uma permissão.
Selecione Microsoft Graph e, em seguida, selecione Permissões delegadas.
Selecione as seguintes permissões e selecione Adicionar permissões.
- openid
- perfil
- Calendars.ReadWrite
- MailboxSettings.Read
Sobre permissões
Considere para que cada um desses escopos de permissão permite que o bot faça e para que o bot os usará.
- openid e perfil: permite que o bot entre usuários e receba informações básicas do Azure AD no token de identidade.
- Calendars.ReadWrite: permite que o bot leia o calendário do usuário e adicione novos eventos ao calendário do usuário.
- MailboxSettings.Read: permite que o bot leia as configurações de caixa de correio do usuário. O bot usará isso para obter o fuso horário selecionado pelo usuário.
- User.Read: permite que o bot receba o perfil do usuário do Microsoft Graph. O bot usará isso para obter o nome do usuário.
Adicionar conexão OAuth ao bot
Navegue até a página bot do seu bot no Portal do Azure. Selecione Configuração em Configurações.
Selecione Adicionar conexão OAuth Configurações.
Preencha o formulário da seguinte maneira e selecione Salvar.
- Nome:
GraphBotAuth
- Provedor: Azure Active Directory v2
- ID do cliente: A ID do aplicativo do seu registro Graph Calendar Bot Auth.
- Segredo do cliente: o segredo do cliente do seu registro Graph Calendário Bot Auth.
- URL Exchange token: deixar em branco
- ID do locatário:
common
- Escopos:
openid profile Calendars.ReadWrite MailboxSettings.Read User.Read
- Nome:
Selecione a entrada GraphBotAuth em OAuth Connection Configurações.
Selecione Conexão de Teste. Isso abre uma nova janela ou guia do navegador para iniciar o fluxo OAuth.
Se necessário, entre. Revise a lista de permissões solicitadas e selecione Aceitar.
Você deve ver uma mensagem conexão de teste com 'GraphBotAuth' Bem-sucedida .
Dica
Você pode selecionar o botão Copiar Token nesta página e colar o token https://jwt.ms para ver as declarações dentro do token. Isso é útil ao solucionar problemas de erros de autenticação.
Adicionar autenticação de plataforma de identidade da Microsoft
Neste exercício, você usará o OAuthPrompt do Bot Framework para implementar a autenticação no bot e adquirir tokens de acesso para chamar a API do Microsoft Graph.
Abra ./appsettings.json e faça as seguintes alterações.
- Altere o valor de
MicrosoftAppId
para a ID do aplicativo do seu Graph de bot de calendário. - Altere o valor do
MicrosoftAppPassword
segredo do cliente bot do calendário Graph calendário. - Adicione um valor nomeado
ConnectionName
com um valor deGraphBotAuth
.
{ "MicrosoftAppId": "YOUR_BOT_APP_ID_HERE", "MicrosoftAppPassword": "YOUR_BOT_CLIENT_SECRET_HERE", "ConnectionName": "GraphBotAuth" }
Observação
Se você usou um valor diferente
GraphBotAuth
do nome da sua entrada no OAuth Connection Configurações no Portal do Azure, use esse valor para aConnectionName
entrada.- Altere o valor de
Implementar caixas de diálogo
Crie um novo diretório na raiz do projeto denominado Dialogs. Crie um novo arquivo no diretório ./Dialogs chamado LogoutDialog.cs e adicione o código a seguir.
using System.Threading; using System.Threading.Tasks; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Schema; namespace CalendarBot.Dialogs { public class LogoutDialog : ComponentDialog { public LogoutDialog(string id, string connectionName) : base(id) { ConnectionName = connectionName; } protected string ConnectionName { get; private set; } // All dialogs should inherit this class so the user // can log out at any time protected override async Task<DialogTurnResult> OnBeginDialogAsync( DialogContext innerDc, object options, CancellationToken cancellationToken) { // Check if this is a logout command var result = await InterruptAsync(innerDc, cancellationToken); if (result != null) { return result; } return await base.OnBeginDialogAsync(innerDc, options, cancellationToken); } protected override async Task<DialogTurnResult> OnContinueDialogAsync( DialogContext innerDc, CancellationToken cancellationToken) { // Check if this is a logout command var result = await InterruptAsync(innerDc, cancellationToken); if (result != null) { return result; } return await base.OnContinueDialogAsync(innerDc, cancellationToken); } private async Task<DialogTurnResult> InterruptAsync( DialogContext innerDc, CancellationToken cancellationToken) { // If this is a logout command, cancel any other activities and log out if (innerDc.Context.Activity.Type == ActivityTypes.Message) { var text = innerDc.Context.Activity.Text.ToLowerInvariant(); if (text.StartsWith("log out") || text.StartsWith("logout")) { // The bot adapter encapsulates the authentication processes. var botAdapter = (BotFrameworkAdapter)innerDc.Context.Adapter; await botAdapter.SignOutUserAsync( innerDc.Context, ConnectionName, null, cancellationToken); await innerDc.Context.SendActivityAsync( MessageFactory.Text("You have been signed out."), cancellationToken); return await innerDc.CancelAllDialogsAsync(); } } return null; } } }
Esta caixa de diálogo fornece uma classe base para todas as outras caixas de diálogo no bot a serem derivadas. Isso permite que o usuário faça logoff independentemente de onde ele está nas caixas de diálogo do bot.
Crie um novo arquivo no diretório ./Dialogs chamado MainDialog.cs e adicione o código a seguir.
using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Dialogs.Choices; using Microsoft.Bot.Schema; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace CalendarBot.Dialogs { public class MainDialog : LogoutDialog { const string NO_PROMPT = "no-prompt"; protected readonly ILogger _logger; public MainDialog(IConfiguration configuration, ILogger<MainDialog> logger) : base(nameof(MainDialog), configuration["ConnectionName"]) { _logger = logger; // OAuthPrompt dialog handles the authentication and token // acquisition AddDialog(new OAuthPrompt( nameof(OAuthPrompt), new OAuthPromptSettings { ConnectionName = ConnectionName, Text = "Please login", Title = "Login", Timeout = 300000, // User has 5 minutes to login })); AddDialog(new ChoicePrompt(nameof(ChoicePrompt))); AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { LoginPromptStepAsync, ProcessLoginStepAsync, PromptUserStepAsync, CommandStepAsync, ProcessStepAsync, ReturnToPromptStepAsync })); // The initial child Dialog to run. InitialDialogId = nameof(WaterfallDialog); } private async Task<DialogTurnResult> LoginPromptStepAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { // If we're going through the waterfall a second time, don't do an extra OAuthPrompt var options = stepContext.Options?.ToString(); if (options == NO_PROMPT) { return await stepContext.NextAsync(cancellationToken: cancellationToken); } return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); } private async Task<DialogTurnResult> ProcessLoginStepAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { // If we're going through the waterfall a second time, don't do an extra OAuthPrompt var options = stepContext.Options?.ToString(); if (options == NO_PROMPT) { return await stepContext.NextAsync(cancellationToken: cancellationToken); } // Get the token from the previous step. If it's there, login was successful if (stepContext.Result != null) { var tokenResponse = stepContext.Result as TokenResponse; if (!string.IsNullOrEmpty(tokenResponse?.Token)) { await stepContext.Context.SendActivityAsync( MessageFactory.Text("You are now logged in."), cancellationToken); return await stepContext.NextAsync(null, cancellationToken); } } await stepContext.Context.SendActivityAsync( MessageFactory.Text("Login was not successful please try again."), cancellationToken); return await stepContext.EndDialogAsync(); } private async Task<DialogTurnResult> PromptUserStepAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { var options = new PromptOptions { Prompt = MessageFactory.Text("Please choose an option below"), Choices = new List<Choice> { new Choice { Value = "Show token" }, new Choice { Value = "Show me" }, new Choice { Value = "Show calendar" }, new Choice { Value = "Add event" }, new Choice { Value = "Log out" }, } }; return await stepContext.PromptAsync( nameof(ChoicePrompt), options, cancellationToken); } private async Task<DialogTurnResult> CommandStepAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { // Save the command the user entered so we can get it back after // the OAuthPrompt completes var foundChoice = stepContext.Result as FoundChoice; // Result could be a FoundChoice (if user selected a choice button) // or a string (if user just typed something) stepContext.Values["command"] = foundChoice?.Value ?? stepContext.Result; // There is no reason to store the token locally in the bot because we can always just call // the OAuth prompt to get the token or get a new token if needed. The prompt completes silently // if the user is already signed in. return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); } private async Task<DialogTurnResult> ProcessStepAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { if (stepContext.Result != null) { var tokenResponse = stepContext.Result as TokenResponse; // If we have the token use the user is authenticated so we may use it to make API calls. if (tokenResponse?.Token != null) { var command = ((string)stepContext.Values["command"] ?? string.Empty).ToLowerInvariant(); if (command.StartsWith("show token")) { // Show the user's token - for testing and troubleshooting // Generally production apps should not display access tokens await stepContext.Context.SendActivityAsync( MessageFactory.Text($"Your token is: {tokenResponse.Token}"), cancellationToken); } else if (command.StartsWith("show me")) { await stepContext.Context.SendActivityAsync( MessageFactory.Text("I don't know how to do this yet!"), cancellationToken); } else if (command.StartsWith("show calendar")) { await stepContext.Context.SendActivityAsync( MessageFactory.Text("I don't know how to do this yet!"), cancellationToken); } else if (command.StartsWith("add event")) { await stepContext.Context.SendActivityAsync( MessageFactory.Text("I don't know how to do this yet!"), cancellationToken); } else { await stepContext.Context.SendActivityAsync( MessageFactory.Text("I'm sorry, I didn't understand. Please try again."), cancellationToken); } } } else { await stepContext.Context.SendActivityAsync( MessageFactory.Text("We couldn't log you in. Please try again later."), cancellationToken); return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); } // Go to the next step return await stepContext.NextAsync(cancellationToken: cancellationToken); } private async Task<DialogTurnResult> ReturnToPromptStepAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { // Restart the dialog, but skip the initial login prompt return await stepContext.ReplaceDialogAsync(InitialDialogId, NO_PROMPT, cancellationToken); } } }
Tome um momento para revisar esse código.
- No construtor, ele configura um WaterfallDialog com um conjunto de etapas que ocorrem em ordem.
- Ele
LoginPromptStepAsync
envia um OAuthPrompt. Se o usuário não estiver conectado, isso enviará um prompt de interface do usuário para o usuário. - Nele
ProcessLoginStepAsync
, verifica se o logon foi bem-sucedido e envia uma confirmação. - Ele
PromptUserStepAsync
envia um ChoicePrompt com os comandos disponíveis. - Nele
CommandStepAsync
, salva a escolha do usuário e, em seguida, reende um OAuthPrompt. - In
ProcessStepAsync
it takes action based on the command received. - In
ReturnToPromptStepAsync
it starts the waterfall over, but passes a flag to skip the initial user login.
- Ele
- No construtor, ele configura um WaterfallDialog com um conjunto de etapas que ocorrem em ordem.
Atualizar CalendarBot
A próxima etapa é atualizar CalendarBot para usar essas novas caixas de diálogo.
Abra ./Bots/CalendarBot.cs e substitua todo o conteúdo pelo código a seguir.
using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Teams; using Microsoft.Bot.Schema; using Microsoft.Extensions.Logging; namespace CalendarBot.Bots { public class CalendarBot<T> : TeamsActivityHandler where T : Dialog { protected readonly BotState ConversationState; protected readonly Dialog Dialog; protected readonly ILogger Logger; protected readonly BotState UserState; public CalendarBot( ConversationState conversationState, UserState userState, T dialog, ILogger<CalendarBot<T>> logger) { ConversationState = conversationState; UserState = userState; Dialog = dialog; Logger = logger; } public override async Task OnTurnAsync( ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)) { Logger.LogInformation("CalendarBot.OnTurnAsync"); await base.OnTurnAsync(turnContext, cancellationToken); // Save any state changes that might have occurred during the turn. await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken); await UserState.SaveChangesAsync(turnContext, false, cancellationToken); } protected override async Task OnMessageActivityAsync( ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken) { Logger.LogInformation("CalendarBot.OnMessageActivityAsync"); await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken); } protected override async Task OnMembersAddedAsync( IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken) { Logger.LogInformation("CalendarBot.OnMembersAddedAsync"); var welcomeText = "Welcome to Microsoft Graph CalendarBot. Type anything to get started."; foreach (var member in membersAdded) { if (member.Id != turnContext.Activity.Recipient.Id) { await turnContext.SendActivityAsync( MessageFactory.Text(welcomeText), cancellationToken); } } } protected override async Task OnTokenResponseEventAsync( ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken) { Logger.LogInformation("CalendarBot.OnTokenResponseEventAsync"); await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken); } protected override async Task OnTeamsSigninVerifyStateAsync( ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken) { Logger.LogInformation("CalendarBot.OnTeamsSigninVerifyStateAsync"); await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken); } } }
Aqui está um breve resumo das alterações.
- Alterou a classe CalendarBot para ser uma classe de modelo, recebendo uma Caixa de Diálogo.
- Alterou a classe CalendarBot para estender o TeamsActivityHandler, permitindo que ele entre Microsoft Teams.
- Adicionadas substituições de método adicionais para habilitar a autenticação.
Atualizar Startup.cs
A etapa final é atualizar o método ConfigureServices
para adicionar os serviços necessários para autenticação e a nova caixa de diálogo.
Abra ./Startup.cs e remova a
services.AddTransient<IBot, Bots.CalendarBot>();
linha doConfigureServices
método.Insira o código a seguir no final do
ConfigureServices
método.// Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) services.AddSingleton<IStorage, MemoryStorage>(); // Create the User state. (Used in this bot's Dialog implementation.) services.AddSingleton<UserState>(); // Create the Conversation state. (Used by the Dialog system itself.) services.AddSingleton<ConversationState>(); // The Dialog that will be run by the bot. services.AddSingleton<Dialogs.MainDialog>(); // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. services.AddTransient<IBot, Bots.CalendarBot<Dialogs.MainDialog>>();
Autenticação de teste
Salve todas as alterações e inicie o bot com
dotnet run
.Abra o Bot Framework Emulator. Selecione o ícone de engrenagem ⚙ à esquerda inferior.
Insira o caminho local para a instalação do ngrok e habilita o ngrok Bypass para endereços locais e Execute ngrok quando o Emulator iniciar opções. Selecione Salvar.
Selecione o menu Arquivo e, em seguida, Nova Configuração de Bot....
Preencha os campos da seguinte forma.
- Nome do bot:
CalendarBot
- URL do ponto de extremidade:
https://localhost:3978/api/messages
- ID do aplicativo microsoft: a ID do aplicativo do seu Graph de bot de calendário
- Senha do Aplicativo microsoft: seu Graph de cliente bot de calendário
- Criptografar chaves armazenadas em sua configuração de bot: Habilitado
- Nome do bot:
Selecione Salvar e conectar. Depois que o emulador se conectar, você deverá ver
Welcome to Microsoft Graph CalendarBot. Type anything to get started.
Digite algum texto e envie-o para o bot. O bot responde com um prompt de logon.
Selecione o botão Logon . O emulador solicita que você confirme a URL que começa com
oauthlink://https://token.botframeworkcom
. Selecione Confirmar para continuar.Na janela pop-up, faça logon com sua Microsoft 365 conta. Revise as permissões solicitadas e aceite.
Depois que a autenticação e o consentimento são concluídos, a janela pop-up fornece um código de validação. Copie o código e feche a janela.
Insira o código de validação na janela de chat para concluir o logon.
Se você selecionar o botão Mostrar token (ou tipo
show token
), o bot exibirá o token de acesso. O botão Sair (ou digitarlog out
) fará logoff.
Dica
Você pode receber a seguinte mensagem de erro no Bot Framework Emulator ao iniciar uma conversa com o bot.
Failed to generate an actual sign-in link: Error: Failed to connect to ngrok instance for OAuth postback URL:
FetchError: request to http://127.0.0.1:4041/api/tunnels failed, reason: connect ECONNREFUSED 127.0.0.1:4041
Se isso acontecer, certifique-se de habilitar a opção Executar ngrok quando o Emulator iniciar nas configurações do emulador e reiniciar o emulador.
Obter detalhes do usuário com o Microsoft Graph
Nesta seção, você usará o Microsoft Graph SDK para obter o usuário conectado.
Criar um serviço de Graph
Comece implementando um serviço que o bot pode usar para obter um GraphServiceClient do SDK do Microsoft Graph e, em seguida, disponibilizar esse serviço para o bot por meio da injeção de dependência.
Crie um novo diretório na raiz do projeto chamado Graph. Crie um novo arquivo no diretório ./Graph chamado IGraphClientService.cs e adicione o código a seguir.
using Microsoft.Graph; namespace CalendarBot.Graph { public interface IGraphClientService { GraphServiceClient GetAuthenticatedGraphClient(string accessToken); } }
Crie um novo arquivo no diretório ./Graph chamado GraphClientService.cs e adicione o código a seguir.
using Microsoft.Graph; using System.Net.Http.Headers; using System.Threading.Tasks; namespace CalendarBot.Graph { public class GraphClientService : IGraphClientService { public GraphServiceClient GetAuthenticatedGraphClient(string accessToken) { return new GraphServiceClient(new DelegateAuthenticationProvider( async (request) => { // Add the access token to the Authorization header // on the outgoing request request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); await Task.CompletedTask; } )); } } }
Abra ./Startup.cs e adicione a seguinte
using
instrução na parte superior do arquivo.using CalendarBot.Graph;
Adicione o seguinte código ao final da função
ConfigureServices
.// Add the Graph client service services.AddSingleton<IGraphClientService, GraphClientService>();
Abra ./Dialogs/MainDialog.cs. Adicione as instruções
using
a seguir à parte superior do arquivo.using System; using System.IO; using AdaptiveCards; using CalendarBot.Graph; using Microsoft.Graph;
Adicione a seguinte propriedade à classe MainDialog .
private readonly IGraphClientService _graphClientService;
Localize o construtor da classe MainDialog e atualize sua assinatura para ter um parâmetro IGraphServiceClient .
public MainDialog( IConfiguration configuration, ILogger<MainDialog> logger, IGraphClientService graphClientService) : base(nameof(MainDialog), configuration["ConnectionName"])
Adicione o código a seguir ao construtor.
_graphClientService = graphClientService;
Obter o usuário conectado
Nesta seção, você usará o microsoft Graph para obter o nome, o endereço de email e a foto do usuário. Em seguida, você criará um Cartão Adaptável para mostrar as informações.
Crie um novo arquivo na raiz do projeto chamado CardHelper.cs. Adicione o código a seguir a esse arquivo.
using AdaptiveCards; using Microsoft.Graph; using System; using System.IO; namespace CalendarBot { public class CardHelper { public static AdaptiveCard GetUserCard(User user, Stream photo) { // Create an Adaptive Card to display the user // See https://adaptivecards.io/designer/ for possibilities var userCard = new AdaptiveCard("1.2"); var columns = new AdaptiveColumnSet(); userCard.Body.Add(columns); var userPhotoColumn = new AdaptiveColumn { Width = AdaptiveColumnWidth.Auto }; columns.Columns.Add(userPhotoColumn); userPhotoColumn.Items.Add(new AdaptiveImage { Style = AdaptiveImageStyle.Person, Size = AdaptiveImageSize.Small, Url = GetDataUriFromPhoto(photo) }); var userInfoColumn = new AdaptiveColumn {Width = AdaptiveColumnWidth.Stretch }; columns.Columns.Add(userInfoColumn); userInfoColumn.Items.Add(new AdaptiveTextBlock { Weight = AdaptiveTextWeight.Bolder, Wrap = true, Text = user.DisplayName }); userInfoColumn.Items.Add(new AdaptiveTextBlock { Spacing = AdaptiveSpacing.None, IsSubtle = true, Wrap = true, Text = user.Mail ?? user.UserPrincipalName }); return userCard; } private static Uri GetDataUriFromPhoto(Stream photo) { // Copy to a MemoryStream to get access to bytes var photoStream = new MemoryStream(); photo.CopyTo(photoStream); var photoBytes = photoStream.ToArray(); return new Uri($"data:image/png;base64,{Convert.ToBase64String(photoBytes)}"); } } }
Este código usa o pacote NuGet AdaptiveCard para criar um Cartão Adaptável para exibir o usuário.
Adicione a seguinte função à classe MainDialog .
private async Task DisplayLoggedInUser( string accessToken, WaterfallStepContext stepContext, CancellationToken cancellationToken) { var graphClient = _graphClientService .GetAuthenticatedGraphClient(accessToken); // Get the user // GET /me?$select=displayName,mail,userPrincipalName var user = await graphClient.Me .Request() .Select(u => new { u.DisplayName, u.Mail, u.UserPrincipalName }) .GetAsync(); // Get the user's photo // GET /me/photos/48x48/$value var userPhoto = await graphClient.Me .Photos["48x48"] .Content .Request() .GetAsync(); // Generate an Adaptive Card var userCard = CardHelper.GetUserCard(user, userPhoto); // Create an attachment message to send the card var userMessage = MessageFactory.Attachment( new Microsoft.Bot.Schema.Attachment { ContentType = AdaptiveCard.ContentType, Content = userCard }); await stepContext.Context.SendActivityAsync(userMessage, cancellationToken); }
Considere o que esse código faz.
- Ele usa o graphClient para obter o usuário conectado.
- Ele usa o método
Select
para limitar quais campos são retornados.
- Ele usa o método
- Ele usa o graphClient para obter a foto do usuário, solicitando o menor tamanho suportado de 48 x 48 pixels.
- Ele usa a classe CardHelper para construir um Cartão Adaptável e envia o cartão como um anexo.
- Ele usa o graphClient para obter o usuário conectado.
Substitua o código dentro do
else if (command.StartsWith("show me"))
bloco peloProcessStepAsync
seguinte.else if (command.StartsWith("show me")) { await DisplayLoggedInUser(tokenResponse.Token, stepContext, cancellationToken); }
Salve todas as alterações e reinicie o bot.
Use o Bot Framework Emulator para se conectar ao bot e fazer logoff. Selecione o botão Mostrar-me para exibir o usuário conectado.
Obter um modo de exibição de calendário
Nesta seção, você usará o Microsoft Graph SDK para obter os próximos 3 eventos futuros no calendário do usuário para a semana atual.
Obter uma exibição de calendário
Uma exibição de calendário é uma lista de eventos no calendário de um usuário que se enquadram entre dois valores de data/hora. A vantagem de usar uma exibição de calendário é que ela inclui quaisquer ocorrências de reuniões recorrentes.
Abra ./CardHelper.cs e adicione a seguinte função à classe CardHelper .
public static AdaptiveCard GetEventCard(Event calendarEvent, string dateTimeFormat) { // Build an Adaptive Card for the event var eventCard = new AdaptiveCard("1.2"); // Add subject as card title eventCard.Body.Add(new AdaptiveTextBlock { Size = AdaptiveTextSize.Medium, Weight = AdaptiveTextWeight.Bolder, Text = calendarEvent.Subject }); // Add organizer eventCard.Body.Add(new AdaptiveTextBlock { Size = AdaptiveTextSize.Default, Weight = AdaptiveTextWeight.Lighter, Spacing = AdaptiveSpacing.None, Text = calendarEvent.Organizer.EmailAddress.Name }); // Add details var details = new AdaptiveFactSet(); details.Facts.Add(new AdaptiveFact { Title = "Start", Value = DateTime.Parse(calendarEvent.Start.DateTime).ToString(dateTimeFormat) }); details.Facts.Add(new AdaptiveFact { Title = "End", Value = DateTime.Parse(calendarEvent.End.DateTime).ToString(dateTimeFormat) }); if (calendarEvent.Location != null && !string.IsNullOrEmpty(calendarEvent.Location.DisplayName)) { details.Facts.Add(new AdaptiveFact { Title = "Location", Value = calendarEvent.Location.DisplayName }); } eventCard.Body.Add(details); return eventCard; }
Este código cria um Cartão Adaptável para renderizar um evento de calendário.
Abra ./Dialogs/MainDialog.cs e adicione a seguinte função à classe MainDialog .
private async Task DisplayCalendarView( string accessToken, WaterfallStepContext stepContext, CancellationToken cancellationToken) { var graphClient = _graphClientService .GetAuthenticatedGraphClient(accessToken); // Get user's preferred time zone and format var user = await graphClient.Me .Request() .Select(u => new { u.MailboxSettings }) .GetAsync(); var dateTimeFormat = $"{user.MailboxSettings.DateFormat} {user.MailboxSettings.TimeFormat}"; if (string.IsNullOrWhiteSpace(dateTimeFormat)) { // Default to a standard format if user's preference not set dateTimeFormat = "G"; } var preferredTimeZone = user.MailboxSettings.TimeZone; var userTimeZone = TimeZoneInfo.FindSystemTimeZoneById(preferredTimeZone); var now = DateTime.UtcNow; // Calculate the end of the week (Sunday, midnight) int diff = 7 - (int)DateTime.Today.DayOfWeek; var weekEndUnspecified = DateTime.SpecifyKind( DateTime.Today.AddDays(diff), DateTimeKind.Unspecified); var endOfWeek = TimeZoneInfo.ConvertTimeToUtc(weekEndUnspecified, userTimeZone); // Set query parameters for the calendar view request var viewOptions = new List<QueryOption> { new QueryOption("startDateTime", now.ToString("o")), new QueryOption("endDateTime", endOfWeek.ToString("o")) }; // Get events happening between right now and the end of the week // GET /me/calendarView?startDateTime=""&endDateTime="" 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=\"{preferredTimeZone}\"") // Get max 3 per request .Top(3) // Only return fields app will use .Select(e => new { e.Subject, e.Organizer, e.Start, e.End, e.Location }) // Order results chronologically .OrderBy("start/dateTime") .GetAsync(); var calendarViewMessage = MessageFactory.Text("Here are your upcoming events"); calendarViewMessage.AttachmentLayout = AttachmentLayoutTypes.List; foreach(var calendarEvent in events.CurrentPage) { var eventCard = CardHelper.GetEventCard(calendarEvent, dateTimeFormat); // Add the card to the message's attachments calendarViewMessage.Attachments.Add(new Microsoft.Bot.Schema.Attachment { ContentType = AdaptiveCard.ContentType, Content = eventCard }); } await stepContext.Context.SendActivityAsync(calendarViewMessage, cancellationToken); }
Considere o que esse código faz.
- Ele obtém os MailboxSettings do usuário para determinar os formatos de fuso horário e data/hora preferidos do usuário.
- Ele define os valores startDateTime e endDateTime como agora e no final da semana, respectivamente. Isso define a janela de tempo que o exibição de calendário usa.
- Ele chama
graphClient.Me.CalendarView
com os detalhes a seguir.- Ele define o
Prefer: outlook.timezone
header como o fuso horário preferencial do usuário, fazendo com que os horários de início e término dos eventos sejam no fuso horário do usuário. - Ele usa o método
Top(3)
para limitar os resultados apenas aos três primeiros eventos. - Ele usa
Select
para limitar os campos retornados apenas aos campos usados pelo bot. - Ele usa
OrderBy
para classificar os eventos por hora de início.
- Ele define o
- Ele adiciona um Cartão Adaptável para cada evento à mensagem de resposta.
Substitua o código dentro do
else if (command.StartsWith("show calendar"))
bloco peloProcessStepAsync
seguinte.else if (command.StartsWith("show calendar")) { await DisplayCalendarView(tokenResponse.Token, stepContext, cancellationToken); }
Salve todas as alterações e reinicie o bot.
Use o Bot Framework Emulator para se conectar ao bot e fazer logoff. Selecione o botão Mostrar calendário para exibir o exibição de calendário.
Criar um novo evento
Nesta seção, você usará o Microsoft Graph SDK para adicionar um evento ao calendário do usuário.
Implementar uma caixa de diálogo
Comece criando uma nova caixa de diálogo personalizada para solicitar ao usuário os valores necessários para adicionar um evento ao calendário. Essa caixa de diálogo usará um WaterfallDialog para fazer as etapas a seguir.
- Prompt for a subject
- Pergunte se o usuário deseja convidar pessoas
- Prompt for attendees (if user said yes to previous step)
- Solicitar uma data e hora de início
- Solicitar uma data e hora de término
- Exibir todos os valores coletados e solicitar que o usuário confirme
- Se o usuário confirmar, obter token de acesso do OAuthPrompt
- Criar o evento
Crie um novo arquivo no diretório ./Dialogs chamado NewEventDialog.cs e adicione o código a seguir.
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using CalendarBot.Graph; using Microsoft.Bot.Builder; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Schema; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Graph; using Microsoft.Recognizers.Text.DataTypes.TimexExpression; using TimexTypes = Microsoft.Recognizers.Text.DataTypes.TimexExpression.Constants.TimexTypes; namespace CalendarBot.Dialogs { public class NewEventDialog : LogoutDialog { protected readonly ILogger _logger; private readonly IGraphClientService _graphClientService; public NewEventDialog( IConfiguration configuration, IGraphClientService graphClientService) : base(nameof(NewEventDialog), configuration["ConnectionName"]) { } // Generate a DateTime from the list of // DateTimeResolutions provided by the DateTimePrompt private static DateTime GetDateTimeFromResolutions(IList<DateTimeResolution> resolutions) { var timex = new TimexProperty(resolutions[0].Timex); // Handle the "now" case if (timex.Now ?? false) { return DateTime.Now; } // Otherwise generate a DateTime return TimexHelpers.DateFromTimex(timex); } } }
Esse é o shell de uma nova caixa de diálogo que solicitará ao usuário os valores necessários para adicionar um evento ao calendário.
Adicione a seguinte função à classe NewEventDialog para solicitar ao usuário um assunto.
private async Task<DialogTurnResult> PromptForSubjectAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { return await stepContext.PromptAsync("subjectPrompt", new PromptOptions{ Prompt = MessageFactory.Text("What's the subject for your event?") }, cancellationToken); }
Adicione a seguinte função à classe NewEventDialog para armazenar o assunto que o usuário deu na etapa anterior e para perguntar se deseja adicionar participantes.
private async Task<DialogTurnResult> PromptForAddAttendeesAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { stepContext.Values["subject"] = (string)stepContext.Result; return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions{ Prompt = MessageFactory.Text("Do you want to invite other people to this event?") }, cancellationToken); }
Adicione a seguinte função à classe NewEventDialog para verificar a resposta do usuário na etapa anterior e solicitar ao usuário uma lista de participantes, se necessário.
private async Task<DialogTurnResult> PromptForAttendeesAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { if ((bool)stepContext.Result) { // user wants to invite attendees // prompt for email addresses return await stepContext.PromptAsync("attendeesPrompt", new PromptOptions{ Prompt = MessageFactory.Text("Enter one or more email addresses of the people you want to invite. Separate multiple addresses with a semi-colon (;)."), RetryPrompt = MessageFactory.Text("One or more email addresses you entered are not valid. Please try again.") }, cancellationToken); } else { // Skip attendees prompt return await stepContext.NextAsync(null, cancellationToken); } }
Adicione a seguinte função à classe NewEventDialog para armazenar a lista de participantes da etapa anterior (se presente) e solicitar ao usuário uma data e hora de início.
private async Task<DialogTurnResult> PromptForStartAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { stepContext.Values["attendees"] = (string)stepContext.Result; return await stepContext.PromptAsync("startPrompt", new PromptOptions{ Prompt = MessageFactory.Text("When does the event start?"), RetryPrompt = MessageFactory.Text("I'm sorry, I didn't get that. Please provide both a day and a time.") }, cancellationToken); }
Adicione a seguinte função à classe NewEventDialog para armazenar o valor inicial da etapa anterior e solicitar ao usuário uma data e hora de término.
private async Task<DialogTurnResult> PromptForEndAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { var dateTimes = stepContext.Result as IList<DateTimeResolution>; var start = GetDateTimeFromResolutions(dateTimes); stepContext.Values["start"] = start; return await stepContext.PromptAsync("endPrompt", new PromptOptions{ Prompt = MessageFactory.Text("When does the event end?"), RetryPrompt = MessageFactory.Text("I'm sorry, I didn't get that. Please provide both a day and a time, and ensure that it is later than the start."), Validations = start }, cancellationToken); }
Adicione a seguinte função à classe NewEventDialog para armazenar o valor final da etapa anterior e peça ao usuário para confirmar todas as entradas.
private async Task<DialogTurnResult> ConfirmNewEventAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { var dateTimes = stepContext.Result as IList<DateTimeResolution>; var end = GetDateTimeFromResolutions(dateTimes); stepContext.Values["end"] = end; // Playback the values as we understand them var subject = stepContext.Values["subject"] as string; var attendees = stepContext.Values["attendees"] as string; var start = stepContext.Values["start"] as DateTime?; // Build a Markdown string var markdown = "Here's what I heard:\n\n"; markdown += $"- **Subject:** {subject}\n"; markdown += $"- **Attendees:** {attendees ?? "none"}\n"; markdown += $"- **Start:** {start?.ToString()}\n"; markdown += $"- **End:** {end.ToString()}"; await stepContext.Context.SendActivityAsync( MessageFactory.Text(markdown)); return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Is this correct?") }, cancellationToken); }
Adicione a seguinte função à classe NewEventDialog para verificar a resposta do usuário da etapa anterior. Se o usuário confirmar as entradas, use a classe OAuthPrompt para obter um token de acesso. Caso contrário, termine a caixa de diálogo.
private async Task<DialogTurnResult> GetTokenAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { if ((bool)stepContext.Result) { return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); } else { await stepContext.Context.SendActivityAsync( MessageFactory.Text("Please try again.")); return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); } }
Adicione a seguinte função à classe NewEventDialog para usar o Microsoft Graph SDK para criar o novo evento.
private async Task<DialogTurnResult> AddEventAsync( WaterfallStepContext stepContext, CancellationToken cancellationToken) { if (stepContext.Result != null) { var tokenResponse = stepContext.Result as TokenResponse; if (tokenResponse?.Token != null) { var subject = stepContext.Values["subject"] as string; var attendees = stepContext.Values["attendees"] as string; var start = stepContext.Values["start"] as DateTime?; var end = stepContext.Values["end"] as DateTime?; // Get an authenticated Graph client using // the access token var graphClient = _graphClientService .GetAuthenticatedGraphClient(tokenResponse?.Token); try { // Get user's preferred time zone var user = await graphClient.Me .Request() .Select(u => new { u.MailboxSettings }) .GetAsync(); // Initialize an Event object var newEvent = new Event { Subject = subject, Start = new DateTimeTimeZone { DateTime = start?.ToString("o"), TimeZone = user.MailboxSettings.TimeZone }, End = new DateTimeTimeZone { DateTime = end?.ToString("o"), TimeZone = user.MailboxSettings.TimeZone } }; // If attendees were provided, add them if (!string.IsNullOrEmpty(attendees)) { // Initialize a list var attendeeList = new List<Attendee>(); // Split the string into an array var emails = attendees.Split(";"); foreach (var email in emails) { // Skip empty strings if (!string.IsNullOrEmpty(email)) { // Build a new Attendee object and // add to the list attendeeList.Add(new Attendee { Type = AttendeeType.Required, EmailAddress = new EmailAddress { Address = email } }); } } newEvent.Attendees = attendeeList; } // Add the event // POST /me/events await graphClient.Me .Events .Request() .AddAsync(newEvent); await stepContext.Context.SendActivityAsync( MessageFactory.Text("Event added"), cancellationToken); } catch (ServiceException ex) { _logger.LogError(ex, "Could not add event"); await stepContext.Context.SendActivityAsync( MessageFactory.Text("Something went wrong. Please try again."), cancellationToken); } return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); } } await stepContext.Context.SendActivityAsync( MessageFactory.Text("We couldn't log you in. Please try again later."), cancellationToken); return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); }
Considere o que esse código faz.
- Ele obtém o MailboxSettings do usuário para determinar o fuso horário preferencial do usuário.
- Ele cria um objeto Event usando os valores fornecidos pelo usuário. Observe que as propriedades Start e End são definidas com o fuso horário do usuário.
- Ele cria o evento no calendário do usuário.
Adicionar validação
Agora adicione validação à entrada do usuário para evitar erros ao criar o evento com o Microsoft Graph. Queremos ter certeza de que:
- Se o usuário der uma lista de participantes, deverá ser uma lista delimitada por ponto-e-vírgula de endereços de email válidos.
- A data/hora de início deve ser uma data e hora válidas.
- A data/hora de término deve ser uma data e hora válidas e deve ser posterior ao início.
Adicione a seguinte função à classe NewEventDialog para validar a entrada do usuário para participantes.
private static Task<bool> AttendeesPromptValidatorAsync( PromptValidatorContext<string> promptContext, CancellationToken cancellationToken) { if (promptContext.Recognized.Succeeded) { // Check if these are emails var emails = promptContext.Recognized.Value.Split(";"); foreach (var email in emails) { // Skip empty entries if (string.IsNullOrEmpty(email)) { continue; } // If there's no '@' symbol it's invalid if (email.IndexOf('@') <= 0) { return Task.FromResult(false); } try { // Let the System.Net.Mail.MailAddress class // validate the rest. If invalid it will throw var mailAddress = new System.Net.Mail.MailAddress(email); if (mailAddress.Address != email) { return Task.FromResult(false); } } catch { return Task.FromResult(false); } } return Task.FromResult(true); } return Task.FromResult(false); }
Adicione as seguintes funções à classe NewEventDialog para validar a entrada do usuário para a data e a hora de início.
private static bool TimexHasDateAndTime(TimexProperty timex) { return timex.Now ?? false || (timex.Types.Contains(TimexTypes.DateTime) && timex.Types.Contains(TimexTypes.Definite)); } private static Task<bool> StartPromptValidatorAsync( PromptValidatorContext<IList<DateTimeResolution>> promptContext, CancellationToken cancellationToken) { if (promptContext.Recognized.Succeeded) { // Initialize a TimexProperty from the first // recognized value var timex = new TimexProperty( promptContext.Recognized.Value[0].Timex); // If it has a definite date and time, it's valid return Task.FromResult(TimexHasDateAndTime(timex)); } return Task.FromResult(false); }
Adicione a seguinte função à classe NewEventDialog para validar a entrada do usuário para a data e a hora de término.
private static Task<bool> EndPromptValidatorAsync( PromptValidatorContext<IList<DateTimeResolution>> promptContext, CancellationToken cancellationToken) { if (promptContext.Recognized.Succeeded) { if (promptContext.Options.Validations is DateTime start) { // Initialize a TimexProperty from the first // recognized value var timex = new TimexProperty( promptContext.Recognized.Value[0].Timex); // Get the DateTime from this value to compare with start var end = GetDateTimeFromResolutions(promptContext.Recognized.Value); // If it has a definite date and time, and // the value is later than start, it's valid return Task.FromResult(TimexHasDateAndTime(timex) && DateTime.Compare(start, end) < 0); } } return Task.FromResult(false); }
Adicionar etapas ao WaterfallDialog
Agora que você tem todas as "etapas" da caixa de diálogo, a última etapa é adicioná-las a um WaterfallDialog no construtor e adicionar o NewEventDialog ao MainDialog.
Localize o construtor NewEventDialog e adicione o seguinte código a ele.
_graphClientService = graphClientService; // OAuthPrompt dialog handles the token // acquisition AddDialog(new OAuthPrompt( nameof(OAuthPrompt), new OAuthPromptSettings { ConnectionName = ConnectionName, Text = "Please login", Title = "Login", Timeout = 300000, // User has 5 minutes to login })); AddDialog(new TextPrompt("subjectPrompt")); // Validator ensures that the input is a semi-colon delimited // list of email addresses AddDialog(new TextPrompt("attendeesPrompt", AttendeesPromptValidatorAsync)); // Validator ensures that the input is a valid date and time AddDialog(new DateTimePrompt("startPrompt", StartPromptValidatorAsync)); // Validator ensures that the input is a valid date and time // and that it is later than the start AddDialog(new DateTimePrompt("endPrompt", EndPromptValidatorAsync)); AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt))); AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { PromptForSubjectAsync, PromptForAddAttendeesAsync, PromptForAttendeesAsync, PromptForStartAsync, PromptForEndAsync, ConfirmNewEventAsync, GetTokenAsync, AddEventAsync })); // The initial child Dialog to run. InitialDialogId = nameof(WaterfallDialog);
Isso adiciona todas as caixas de diálogo usadas e adiciona todas as funções implementadas como etapas no WaterfallDialog.
Abra ./Dialogs/MainDialog.cs e adicione a seguinte linha ao construtor.
AddDialog(new NewEventDialog(configuration, graphClientService));
Substitua o código dentro do
else if (command.StartsWith("add event"))
bloco peloProcessStepAsync
seguinte.else if (command.StartsWith("add event")) { return await stepContext.BeginDialogAsync(nameof(NewEventDialog), null, cancellationToken); }
Salve todas as alterações e reinicie o bot.
Use o Bot Framework Emulator para se conectar ao bot e fazer logoff. Selecione o botão Adicionar evento .
Responda aos prompts para criar um novo evento. Observe que, quando solicitado a valores de início e fim, você pode usar frases como "hoje às 15h" ou "agora".
Parabéns!
Você concluiu o tutorial do Microsoft Graph Bot Framework. Agora que você tem um bot de trabalho que chama a Microsoft Graph, você pode experimentar e adicionar novos recursos. Visite a visão geral do microsoft Graph para ver todos os dados que você pode acessar com o Microsoft Graph.
Comentários
Forneça qualquer comentário sobre este tutorial no repositório GitHub.
Tem algum problema com essa seção? Se tiver, envie seus comentários para que possamos melhorar esta seção.