Robots d’infrastructure de génération bot avec Microsoft Graph
Ce didacticiel vous apprend à créer un bot Bot Framework qui utilise l’API Microsoft Graph pour récupérer les informations de calendrier d’un utilisateur.
Conseil
Si vous préférez simplement télécharger le didacticiel terminé, vous pouvez télécharger ou cloner le GitHub complet. Consultez le fichier README dans le dossier de démonstration pour obtenir des instructions sur la configuration de l’application avec un ID d’application et une secret.
Conditions préalables
Avant de commencer ce didacticiel, les instructions suivantes doivent être installées sur votre ordinateur de développement.
Vous devez également avoir un compte Microsoft personnel avec une boîte aux lettres sur Outlook.com, ou un compte scolaire ou scolaire Microsoft. Si vous n’avez pas de compte Microsoft, deux options s’offrent à vous pour obtenir un compte gratuit :
- Vous pouvez vous inscrire à un nouveau compte Microsoft personnel.
- Vous pouvez vous inscrire au programme Microsoft 365 développeur pour obtenir un abonnement Microsoft 365 gratuit.
- Un abonnement Azure. Si vous n’en avez pas, créez un compte gratuit avant de commencer.
Notes
Ce didacticiel a été écrit avec les versions suivantes. Les étapes de ce guide peuvent fonctionner avec d’autres versions, mais elles n’ont pas été testées.
- .NET Core SDK version 5.0.302
- Bot Framework Emulator 4.1.3
- ngrok 2.3.40
Commentaires
N’hésitez pas à nous faire part de vos commentaires sur ce didacticiel dans GitHub référentiel.
Créer un projet d’infrastructure de robot
Dans cette section, vous allez créer un projet Bot Framework.
Ouvrez votre interface de ligne de commande (CLI) dans un répertoire où vous souhaitez créer le projet. Exécutez la commande suivante pour créer un projet à l’aide du modèle Microsoft.Bot.Framework.CSharp.EchoBot .
dotnet new echobot -n GraphCalendarBot
Notes
Si vous recevez une erreur
No templates matched the input template name: echobot.
, installez le modèle avec la commande suivante et ré-exécutez la commande précédente.dotnet new -i Microsoft.Bot.Framework.CSharp.EchoBot
Renommons la classe EchoBot par défaut en CalendarBot. Ouvrez ./Bots/EchoBot.cs et remplacez toutes les instances de
EchoBot
.CalendarBot
Renommons le fichier CalendarBot.cs.Remplacez toutes les instances des
EchoBot
fichiersCalendarBot
.cs restants .Dans votre CLI, modifiez le répertoire actuel en répertoire GraphCalendarBot et exécutez la commande suivante pour confirmer les builds du projet.
dotnet build
Ajouter des packages NuGet
Avant de passer à la suite, installez des packages NuGet supplémentaires que vous utiliserez ultérieurement.
- AdaptiveCards pour permettre au bot d’envoyer des cartes adaptatives dans les réponses.
- Microsoft.Bot.Builder.Dialogs pour ajouter la prise en charge de boîte de dialogue au bot.
- Microsoft.Recognizers.Text.DataTypes.TimexExpression pour convertir les expressions TIMEX renvoyées par les invites du bot en objets DateTime .
- Microsoft.Graph pour effectuer des appels Microsoft Graph.
Exécutez les commandes suivantes dans votre CLI pour installer les dépendances.
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
Tester le bot
Avant d’ajouter du code, testez le bot pour vous assurer qu’il fonctionne correctement et que le Bot Framework Emulator est configuré pour le tester.
Démarrez le bot en exécutant la commande suivante.
dotnet run
Conseil
Bien que vous pouvez utiliser n’importe quel éditeur de texte pour modifier les fichiers sources dans le projet, nous vous recommandons d’utiliser Visual Studio Code. Visual Studio Code offre la prise en charge du débogage, Intellisense, etc. Si vous Visual Studio Code, vous pouvez démarrer le bot à l’aide du menu Débogage RunStart -> .
Confirmez que le bot est en cours d’exécution en ouvrant votre navigateur et en allant à
http://localhost:3978
. Vous devriez voir que votre bot est prêt ! Message.Ouvrez le Bot Framework Emulator. Choisissez le menu Fichier , puis ouvrez bot.
Entrez
http://localhost:3978/api/messages
l’URL du bot, puis sélectionnez Connecter.Le bot répond dans la
Hello and welcome!
fenêtre de conversation. Envoyez un message au bot et confirmez qu’il renvoie un écho.
Enregistrer le robot dans le portail
Dans cet exercice, vous allez créer une inscription de canaux de bot et une inscription Azure AD application web à l’aide du portail Azure.
Créer une inscription de canaux de bot
Ouvrez un navigateur et accédez au portail Azure. Connectez-vous à l’aide du compte associé à votre abonnement Azure.
Sélectionnez le menu supérieur gauche, puis sélectionnez Créer une ressource.
Dans la page Nouveau , recherchez et
Azure Bot
sélectionnez Azure Bot.Dans la page Azure Bot , sélectionnez Créer.
Remplissez les champs requis et laissez le point de terminaison de messagerie vide. Le champ de handle bot doit être unique. N’oubliez pas de passer en revue les différents niveaux de tarification et de sélectionner ce qui est logique pour votre scénario. S’il s’agit simplement d’un exercice d’apprentissage, vous pouvez sélectionner l’option gratuite.
Pour l’ID d’application Microsoft, sélectionnez Créer un ID d’application Microsoft.
Sélectionnez Examiner et créer. Une fois la validation terminée, sélectionnez Créer.
Une fois le déploiement terminé, sélectionnez Aller à la ressource.
Sous Paramètres, sélectionnez Configuration. Sélectionnez le lien Gérer à côté de l’ID d’application Microsoft.
Sélectionnez Nouvelle clé secrète client. Ajoutez une description et choisissez une expiration, puis sélectionnez Ajouter.
Copiez la valeur due la clé secrète client avant de quitter cette page. Vous en aurez besoin dans les étapes suivantes.
Important
Cette clé secrète client n’apparaîtra plus jamais, aussi veillez à la copier maintenant. Vous devrez entrer cette valeur à plusieurs endroits afin de la protéger.
Sélectionnez Vue d’ensemble dans le menu de gauche. Copiez la valeur de l’ID de l’application (client) et enregistrez-la. Vous en aurez besoin dans les étapes suivantes.
Revenir à la fenêtre Inscription du canal bot dans votre navigateur et collez l’ID de l’application dans le champ ID de l’application Microsoft . Collez votre secret client dans le champ Mot de passe. Sélectionnez OK.
Dans la page Inscription des canaux bots , sélectionnez Créer.
Attendez la création de l’inscription des canaux bots. Une fois créé, revenir à la page d’accueil dans le portail Azure, puis sélectionnez Bot Services. Sélectionnez votre nouvelle inscription au canal bots pour afficher ses propriétés.
Créer une inscription d’application web
Revenir à la page d’accueil du portail Azure, puis sélectionnez Azure Active Directory.
Sélectionner les inscriptions d’applications.
Sélectionnez Nouvelle inscription. Sur la page Inscrire une application, définissez les valeurs comme suit.
- Définissez le Nom sur
Graph Calendar Bot Auth
. - Définissez les Types de comptes pris en charge sur Comptes dans un annuaire organisationnel et comptes personnels Microsoft.
- Sous URI de redirection, définissez la première flèche déroulante sur
Web
, et la valeur surhttps://token.botframework.com/.auth/web/redirect
.
- Définissez le Nom sur
Sélectionnez Inscrire. Sur la page Graph auth du bot de calendrier, copiez la valeur de l’ID de l’application (client) et enregistrez-la. Vous en aurez besoin dans les étapes suivantes.
Sélectionnez Certificats et secrets sous Gérer. Sélectionnez le bouton Nouveau secret client. Entrez une valeur dans Description, sélectionnez une des options pour Expire le, puis sélectionnez Ajouter.
Copiez la valeur due la clé secrète client avant de quitter cette page. Vous en aurez besoin dans les étapes suivantes.
Sélectionnez les autorisations d’API, puis sélectionnez Ajouter une autorisation.
Sélectionnez Microsoft Graph, puis les autorisations déléguées.
Sélectionnez les autorisations suivantes, puis sélectionnez Ajouter des autorisations.
- openid
- profil
- Calendars.ReadWrite
- MailboxSettings.Read
À propos des autorisations
Réfléchissez à chacune de ces étendues d’autorisation et à ce pour quoi le bot les utilisera.
- openid et profil : permet au bot de se connecter aux utilisateurs et d’obtenir des informations de base Azure AD dans le jeton d’identité.
- Calendars.ReadWrite : permet au bot de lire le calendrier de l’utilisateur et d’ajouter de nouveaux événements au calendrier de l’utilisateur.
- MailboxSettings.Read : permet au bot de lire les paramètres de boîte aux lettres de l’utilisateur. Le bot l’utilisera pour obtenir le fuseau horaire sélectionné par l’utilisateur.
- User.Read : permet au bot d’obtenir le profil de l’utilisateur auprès de Microsoft Graph. Le bot l’utilisera pour obtenir le nom de l’utilisateur.
Ajouter une connexion OAuth au bot
Accédez à la page Azure Bot de votre bot dans le portail Azure. Sélectionnez Configuration sous Paramètres.
Sélectionnez Ajouter une connexion OAuth Paramètres.
Remplissez le formulaire comme suit, puis sélectionnez Enregistrer.
- Nom :
GraphBotAuth
- Fournisseur : Azure Active Directory v2
- ID client : ID d’application de votre Graph’th du bot de calendrier.
- Secret client : la secret client de votre inscription au bot Graph calendrier.
- URL de Exchange jeton : laisser vide
- ID de client :
common
- Étendues :
openid profile Calendars.ReadWrite MailboxSettings.Read User.Read
- Nom :
Sélectionnez l’entrée GraphBotAuth sous Connexion OAuth Paramètres.
Sélectionnez Connexion test. Cela ouvre une nouvelle fenêtre de navigateur ou un nouvel onglet pour démarrer le flux OAuth.
Si nécessaire, connectez-vous. Examinez la liste des autorisations demandées, puis sélectionnez Accepter.
Vous devriez voir un message « Connexion de test à GraphBotAuth » réussi .
Conseil
Vous pouvez sélectionner le bouton Copier le jeton sur cette page et https://jwt.ms coller le jeton dans pour voir les revendications à l’intérieur du jeton. Cela est utile lors de la résolution des erreurs d’authentification.
Ajouter l’authentification de plateforme d’identité Microsoft
Dans cet exercice, vous allez utiliser OAuthPrompt de Bot Framework pour implémenter l’authentification dans le bot et acquérir des jetons d’accès pour appeler l’API Microsoft Graph.
Ouvrez ./appsettings.json et a apporté les modifications suivantes.
- Modifiez la valeur de l’ID
MicrosoftAppId
d’application de votre Graph’inscription de l’application Calendar Bot. - Modifiez la valeur de votre
MicrosoftAppPassword
Graph client Calendar Bot. - Ajoutez une valeur nommée
ConnectionName
avec une valeur deGraphBotAuth
.
{ "MicrosoftAppId": "YOUR_BOT_APP_ID_HERE", "MicrosoftAppPassword": "YOUR_BOT_CLIENT_SECRET_HERE", "ConnectionName": "GraphBotAuth" }
Notes
Si vous avez utilisé une valeur autre
GraphBotAuth
que le nom de votre entrée dans la connexion OAuth Paramètres dans le portail Azure, utilisez cette valeur pour l’entréeConnectionName
.- Modifiez la valeur de l’ID
Implémenter des boîtes de dialogue
Créez un répertoire à la racine du projet nommé Dialogs. Créez un fichier dans le répertoire ./Dialogs nommé LogoutDialog.cs et ajoutez le code suivant.
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; } } }
Cette boîte de dialogue fournit une classe de base dont dérivent toutes les autres boîtes de dialogue du bot. Cela permet à l’utilisateur de se déconnecter où qu’il se trouve dans les boîtes de dialogue du bot.
Créez un fichier dans le répertoire ./Dialogs nommé MainDialog.cs et ajoutez le code suivant.
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); } } }
Prenez le temps de passer en revue ce code.
- Dans le constructeur, il définit un WaterfallDialog avec un ensemble d’étapes qui se produisent dans l’ordre.
- Il
LoginPromptStepAsync
envoie un message OAuthPrompt. Si l’utilisateur n’est pas connecté, cela envoie une invite d’interface utilisateur à l’utilisateur. - Il vérifie
ProcessLoginStepAsync
si la connexion a réussi et envoie une confirmation. - Il
PromptUserStepAsync
envoie un ChoicePrompt avec les commandes disponibles. - Il
CommandStepAsync
enregistre le choix de l’utilisateur, puis renvoye un OAuthPrompt. - Dans ce
ProcessStepAsync
dernier, une action est entreprise en fonction de la commande reçue. - Dans ce
ReturnToPromptStepAsync
dernier, la cascade s’ouvre, mais transmet un indicateur pour ignorer la connexion initiale de l’utilisateur.
- Il
- Dans le constructeur, il définit un WaterfallDialog avec un ensemble d’étapes qui se produisent dans l’ordre.
Mettre à jour CalendarBot
L’étape suivante consiste à mettre à jour CalendarBot pour utiliser ces nouvelles boîtes de dialogue.
Ouvrez ./Bots/CalendarBot.cs et remplacez tout son contenu par le code suivant.
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); } } }
Voici un bref résumé des modifications.
- A modifié la classe CalendarBot en tant que classe de modèle, en recevant une boîte de dialogue.
- Modifié la classe CalendarBot pour étendre TeamsActivityHandler, ce qui lui permet de se Microsoft Teams.
- Ajout de remplacements de méthodes supplémentaires pour activer l’authentification.
Mettre à jour Startup.cs
La dernière étape consiste à mettre à jour la méthode ConfigureServices
pour ajouter les services requis pour l’authentification et la nouvelle boîte de dialogue.
Ouvrez ./Startup.cs et supprimez la
services.AddTransient<IBot, Bots.CalendarBot>();
ligne de laConfigureServices
méthode.Insérez le code suivant à la fin de la
ConfigureServices
méthode.// 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>>();
Tester l’authentification
Enregistrez toutes vos modifications et démarrez le bot avec
dotnet run
.Ouvrez le Bot Framework Emulator. Sélectionnez l’icône d⚙ en bas à gauche.
Entrez le chemin d’accès local à votre installation de ngrok, activez le contournement ngrok pour les adresses locales et exécutez ngrok lorsque l’Emulator options de démarrage. Sélectionnez Enregistrer.
Sélectionnez le menu Fichier, puis Nouvelle configuration du bot....
Remplissez les champs comme suit.
- Nom du bot :
CalendarBot
- URL du point de terminaison :
https://localhost:3978/api/messages
- ID d’application Microsoft : ID d’application de votre Graph’inscription de l’application Bot du calendrier
- Mot de passe de l’application Microsoft : votre Graph client Calendrier Bot
- Chiffrez les clés stockées dans la configuration de votre bot : Activé
- Nom du bot :
Sélectionnez Enregistrer et vous connecter. Une fois l’émulateur connecté, vous devez voir
Welcome to Microsoft Graph CalendarBot. Type anything to get started.
Tapez du texte et envoyez-le au bot. Le bot répond par une invite de connexion.
Sélectionnez le bouton Connexion . L’émulateur vous invite à confirmer l’URL qui commence par
oauthlink://https://token.botframeworkcom
. Sélectionnez Confirmer pour continuer.Dans la fenêtre pop-up, connectez-vous avec Microsoft 365 compte. Examinez les autorisations demandées et acceptez.
Une fois l’authentification et le consentement terminés, la fenêtre indépendante fournit un code de validation. Copiez le code et fermez la fenêtre.
Entrez le code de validation dans la fenêtre de conversation pour terminer la connexion.
Si vous sélectionnez le bouton Afficher le jeton (ou tapez
show token
), le bot affiche le jeton d’accès. Le bouton Se déconnecter (ou taperlog out
) vous déconnecte.
Conseil
Vous pouvez recevoir le message d’erreur suivant dans le Bot Framework Emulator lors du démarrage d’une conversation avec le 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
Si cela se produit, n’oubliez pas d’activer l’option Exécuter ngrok lorsque l’Emulator démarre dans les paramètres de l’émulateur et redémarrez l’émulateur.
Obtenir les détails de l’utilisateur avec Microsoft Graph
Dans cette section, vous allez utiliser le SDK Microsoft Graph pour obtenir l’utilisateur connecté.
Créer un service Graph service
Commencez par implémenter un service que le bot peut utiliser pour obtenir un GraphServiceClient à partir du SDK Microsoft Graph, puis mettre ce service à la disposition du bot via l’injection de dépendances.
Créez un répertoire à la racine du projet nommé Graph. Créez un fichier dans le répertoire ./Graph nommé IGraphClientService.cs et ajoutez le code suivant.
using Microsoft.Graph; namespace CalendarBot.Graph { public interface IGraphClientService { GraphServiceClient GetAuthenticatedGraphClient(string accessToken); } }
Créez un fichier dans le répertoire ./Graph nommé GraphClientService.cs et ajoutez le code suivant.
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; } )); } } }
Ouvrez ./Startup.cs et ajoutez l’instruction
using
suivante en haut du fichier.using CalendarBot.Graph;
Ajoutez la fonction suivante à la fin de la fonction
ConfigureServices
.// Add the Graph client service services.AddSingleton<IGraphClientService, GraphClientService>();
Ouvrez ./Dialogs/MainDialog.cs. Ajoutez les instructions
using
suivantes en haut du fichier.using System; using System.IO; using AdaptiveCards; using CalendarBot.Graph; using Microsoft.Graph;
Ajoutez la propriété suivante à la classe MainDialog .
private readonly IGraphClientService _graphClientService;
Recherchez le constructeur de la classe MainDialog et mettez à jour sa signature pour prendre un paramètre IGraphServiceClient .
public MainDialog( IConfiguration configuration, ILogger<MainDialog> logger, IGraphClientService graphClientService) : base(nameof(MainDialog), configuration["ConnectionName"])
Ajoutez le code suivant au constructeur.
_graphClientService = graphClientService;
Obtenir l’utilisateur connecté
Dans cette section, vous allez utiliser l’Graph Microsoft pour obtenir le nom, l’adresse e-mail et la photo de l’utilisateur. Ensuite, vous allez créer une carte adaptative pour afficher les informations.
Créez un fichier à la racine du projet nommé CardHelper.cs. Ajoutez le code suivant au fichier.
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)}"); } } }
Ce code utilise le package adaptiveCard NuGet pour créer une carte adaptative pour afficher l’utilisateur.
Ajoutez la fonction suivante à la 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); }
Prenez en compte ce que fait ce code.
- Il utilise graphClient pour obtenir l’utilisateur connecté.
- Il utilise la méthode
Select
pour limiter les champs qui sont renvoyés.
- Il utilise la méthode
- Il utilise graphClient pour obtenir la photo de l’utilisateur, en demandant la plus petite taille prise en charge de 48 x 48 pixels.
- Il utilise la classe CardHelper pour construire une carte adaptative et envoie la carte en pièce jointe.
- Il utilise graphClient pour obtenir l’utilisateur connecté.
Remplacez le code à l’intérieur du
else if (command.StartsWith("show me"))
bloc parProcessStepAsync
ce qui suit.else if (command.StartsWith("show me")) { await DisplayLoggedInUser(tokenResponse.Token, stepContext, cancellationToken); }
Enregistrez toutes vos modifications et redémarrez le bot.
Utilisez le Bot Framework Emulator pour vous connecter au bot et vous connecter. Sélectionnez le bouton Afficher moi pour afficher l’utilisateur connecté.
Obtenir un affichage Calendrier
Dans cette section, vous allez utiliser le SDK Microsoft Graph pour obtenir les 3 prochains événements à venir sur le calendrier de l’utilisateur pour la semaine en cours.
Obtenir un affichage Calendrier
Un affichage Calendrier est une liste d’événements dans le calendrier d’un utilisateur qui se trouve entre deux valeurs de date/heure. L’avantage de l’affichage Calendrier est qu’il inclut toutes les occurrences de réunions périodiques.
Ouvrez ./CardHelper.cs et ajoutez la fonction suivante à la 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; }
Ce code crée une carte adaptative pour restituer un événement de calendrier.
Ouvrez ./Dialogs/MainDialog.cs et ajoutez la fonction suivante à la 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); }
Prenez en compte ce que fait ce code.
- Elle obtient les mailboxsettings de l’utilisateur pour déterminer les formats de fuseau horaire et de date/heure préférés de l’utilisateur.
- Il définit les valeurs startDateTime et endDateTime sur maintenant et la fin de la semaine, respectivement. Cela définit la fenêtre de temps que l’affichage Calendrier utilise.
- Il appelle
graphClient.Me.CalendarView
avec les détails suivants.- Il définit l’en-tête
Prefer: outlook.timezone
sur le fuseau horaire préféré de l’utilisateur, ce qui entraîne les heures de début et de fin des événements dans le fuseau horaire de l’utilisateur. - Il utilise la méthode
Top(3)
pour limiter les résultats aux 3 premiers événements uniquement. - Il sert à
Select
limiter les champs renvoyés uniquement aux champs utilisés par le bot. - Il sert à
OrderBy
trier les événements par heure de début.
- Il définit l’en-tête
- Il ajoute une carte adaptative pour chaque événement au message de réponse.
Remplacez le code à l’intérieur du
else if (command.StartsWith("show calendar"))
bloc parProcessStepAsync
ce qui suit.else if (command.StartsWith("show calendar")) { await DisplayCalendarView(tokenResponse.Token, stepContext, cancellationToken); }
Enregistrez toutes vos modifications et redémarrez le bot.
Utilisez le Bot Framework Emulator pour vous connecter au bot et vous connecter. Sélectionnez le bouton Afficher le calendrier pour afficher l’affichage Calendrier.
Créer un événement
Dans cette section, vous allez utiliser le SDK Microsoft Graph pour ajouter un événement au calendrier de l’utilisateur.
Implémenter une boîte de dialogue
Commencez par créer une boîte de dialogue personnalisée pour invite l’utilisateur à obtenir les valeurs nécessaires pour ajouter un événement à son calendrier. Cette boîte de dialogue utilise un WaterfallDialog pour suivre les étapes suivantes.
- Invite d’un sujet
- Demander si l’utilisateur souhaite inviter des personnes
- Invite des participants (si l’utilisateur a répondu oui à l’étape précédente)
- Invite pour une date et une heure de début
- Invite pour une date et une heure de fin
- Afficher toutes les valeurs collectées et demander à l’utilisateur de confirmer
- Si l’utilisateur confirme, obtenir un jeton d’accès à partir d’OAuthPrompt
- Créer l’événement
Créez un fichier dans le répertoire ./Dialogs nommé NewEventDialog.cs et ajoutez le code suivant.
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); } } }
Il s’agit de l’shell d’une nouvelle boîte de dialogue qui invite l’utilisateur à obtenir les valeurs nécessaires pour ajouter un événement à son calendrier.
Ajoutez la fonction suivante à la classe NewEventDialog pour que l’utilisateur demande un objet.
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); }
Ajoutez la fonction suivante à la classe NewEventDialog pour stocker l’objet que l’utilisateur a donné à l’étape précédente et pour lui demander s’il souhaite ajouter des participants.
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); }
Ajoutez la fonction suivante à la classe NewEventDialog pour vérifier la réponse de l’utilisateur à l’étape précédente et invitez l’utilisateur à obtenir la liste des participants si nécessaire.
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); } }
Ajoutez la fonction suivante à la classe NewEventDialog pour stocker la liste des participants de l’étape précédente (si elle est présente) et invitez l’utilisateur à une date et une heure de début.
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); }
Ajoutez la fonction suivante à la classe NewEventDialog pour stocker la valeur de début de l’étape précédente et invitez l’utilisateur à une date et une heure de fin.
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); }
Ajoutez la fonction suivante à la classe NewEventDialog pour stocker la valeur de fin de l’étape précédente et demandez à l’utilisateur de confirmer toutes les entrées.
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); }
Ajoutez la fonction suivante à la classe NewEventDialog pour vérifier la réponse de l’utilisateur à l’étape précédente. Si l’utilisateur confirme les entrées, utilisez la classe OAuthPrompt pour obtenir un jeton d’accès. Dans le cas contraire, terminez la boîte de dialogue.
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); } }
Ajoutez la fonction suivante à la classe NewEventDialog pour utiliser le SDK Microsoft Graph pour créer le nouvel événement.
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); }
Prenez en compte ce que fait ce code.
- Elle obtient les MailboxSettings de l’utilisateur pour déterminer le fuseau horaire préféré de l’utilisateur.
- Il crée un objet Event à l’aide des valeurs fournies par l’utilisateur. Notez que les propriétés Début et Fin sont définies avec le fuseau horaire de l’utilisateur.
- Il crée l’événement sur le calendrier de l’utilisateur.
Ajouter une validation
Maintenant, ajoutez la validation à l’entrée de l’utilisateur pour éviter les erreurs lors de la création de l’événement avec Microsoft Graph. Nous voulons nous assurer que :
- Si l’utilisateur donne une liste de participants, il doit s’agit d’une liste d’adresses de messagerie valides délimitée par des points-virgules.
- La date/l’heure de début doit être une date et une heure valides.
- La date/l’heure de fin doit être une date et une heure valides et doit être ultérieure au début.
Ajoutez la fonction suivante à la classe NewEventDialog pour valider l’entrée de l’utilisateur pour les participants.
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); }
Ajoutez les fonctions suivantes à la classe NewEventDialog pour valider l’entrée de l’utilisateur pour la date et l’heure de début.
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); }
Ajoutez la fonction suivante à la classe NewEventDialog pour valider l’entrée de l’utilisateur pour la date et l’heure de fin.
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); }
Ajouter des étapes à CascadeDialog
Maintenant que vous avez toutes les « étapes » pour la boîte de dialogue, la dernière étape consiste à les ajouter à un événement CascadeDialog dans le constructeur, puis à ajouter le NewEventDialog au MainDialog.
Recherchez le constructeur NewEventDialog et ajoutez-y le code suivant.
_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);
Cela ajoute toutes les boîtes de dialogue utilisées et toutes les fonctions que vous avez implémentées en tant qu’étapes dans Le Journal des cascades.
Ouvrez ./Dialogs/MainDialog.cs et ajoutez la ligne suivante au constructeur.
AddDialog(new NewEventDialog(configuration, graphClientService));
Remplacez le code à l’intérieur du
else if (command.StartsWith("add event"))
bloc parProcessStepAsync
ce qui suit.else if (command.StartsWith("add event")) { return await stepContext.BeginDialogAsync(nameof(NewEventDialog), null, cancellationToken); }
Enregistrez toutes vos modifications et redémarrez le bot.
Utilisez le Bot Framework Emulator pour vous connecter au bot et vous connecter. Sélectionnez le bouton Ajouter un événement .
Répondez aux invites pour créer un événement. Notez que lorsque vous êtes invité à utiliser des valeurs de début et de fin, vous pouvez utiliser des expressions telles que « aujourd’hui à 15 h » ou « maintenant ».
Félicitations !
Vous avez terminé le didacticiel Microsoft Graph Bot Framework. Maintenant que vous disposez d’un bot de travail qui appelle Microsoft Graph, vous pouvez expérimenter et ajouter de nouvelles fonctionnalités. Consultez la vue d’ensemble de Microsoft Graph pour voir toutes les données accessibles avec Microsoft Graph.
Commentaires
N’hésitez pas à nous faire part de vos commentaires sur ce didacticiel dans GitHub référentiel.
Vous avez un problème avec cette section ? Si c'est le cas, faites-nous part de vos commentaires pour que nous puissions l'améliorer.