Erstellen von bot-Framework-Bots mit Microsoft Graph
In diesem Lernprogramm erfahren Sie, wie Sie einen Bot Framework-Bot erstellen, der die Microsoft Graph-API verwendet, um Kalenderinformationen für einen Benutzer abzurufen.
Tipp
Wenn Sie es vorziehen, nur das abgeschlossene Lernprogramm herunterzuladen, können Sie das GitHub Repository herunterladen oder klonen. Anweisungen zum Konfigurieren der App mit einer App-ID und einem geheimen Schlüssel finden Sie in der README-Datei im Demoordner .
Voraussetzungen
Bevor Sie mit diesem Lernprogramm beginnen, sollten Sie Folgendes auf Ihrem Entwicklungscomputer installiert haben.
Sie sollten auch über ein persönliches Microsoft-Konto mit einem Postfach auf Outlook.com oder ein Microsoft-Geschäfts-, Schul- oder Unikonto verfügen. Wenn Sie kein Microsoft-Konto haben, gibt es einige Optionen, um ein kostenloses Konto zu erhalten:
- Sie können sich für ein neues persönliches Microsoft-Konto registrieren.
- Sie können sich für das Microsoft 365-Entwicklerprogramm registrieren, um ein kostenloses Microsoft 365 Abonnement zu erhalten.
- Ein Azure-Abonnement. Wenn Sie nicht über ein Konto verfügen, erstellen Sie ein kostenloses Konto , bevor Sie beginnen.
Hinweis
Dieses Lernprogramm wurde mit den folgenden Versionen geschrieben. Die Schritte in diesem Handbuch funktionieren möglicherweise mit anderen Versionen, die jedoch nicht getestet wurden.
- .NET Core SDK Version 5.0.302
- Bot Framework Emulator 4.1.3
- ngrok 2.3.40
Feedback
Bitte geben Sie Feedback zu diesem Lernprogramm im GitHub Repository.
Erstellen eines bot-Framework-Projekts
In diesem Abschnitt erstellen Sie ein Bot Framework-Projekt.
Öffnen Sie die Befehlszeilenschnittstelle (CLI) in einem Verzeichnis, in dem Sie das Projekt erstellen möchten. Führen Sie den folgenden Befehl aus, um ein neues Projekt mithilfe der Vorlage "Microsoft.Bot.Framework.CSharp.EchoBot " zu erstellen.
dotnet new echobot -n GraphCalendarBot
Hinweis
Wenn ein
No templates matched the input template name: echobot.
Fehler auftritt, installieren Sie die Vorlage mit dem folgenden Befehl, und führen Sie den vorherigen Befehl erneut aus.dotnet new -i Microsoft.Bot.Framework.CSharp.EchoBot
Benennen Sie die Standardmäßige EchoBot-Klasse in CalendarBot um. Öffnen Sie ./Bots/EchoBot.cs , und ersetzen Sie alle Instanzen von
EchoBot
durchCalendarBot
. Rename the file to CalendarBot.cs.Ersetzen Sie alle Instanzen von
EchoBot
durchCalendarBot
in den verbleibenden CS-Dateien .Ändern Sie in Ihrer CLI das aktuelle Verzeichnis in das GraphCalendarBot-Verzeichnis , und führen Sie den folgenden Befehl aus, um die Projektbuilds zu bestätigen.
dotnet build
Hinzufügen von NuGet-Paketen
Bevor Sie fortfahren, installieren Sie einige zusätzliche NuGet Pakete, die Sie später verwenden werden.
- AdaptiveCards , damit der Bot adaptive Karten in Antworten senden kann.
- Microsoft.Bot.Builder.Dialogs zum Hinzufügen der Dialogunterstützung für den Bot.
- Microsoft.Recognizers.Text.DataTypes.TimexExpression zum Konvertieren der von Bot-Eingabeaufforderungen zurückgegebenen TIMEX-Ausdrücke in DateTime-Objekte .
- Microsoft.Graph zum Aufrufen von Microsoft Graph
Führen Sie die folgenden Befehle in Der CLI aus, um die Abhängigkeiten zu installieren.
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
Testen des Bots
Testen Sie vor dem Hinzufügen von Code den Bot, um sicherzustellen, dass er ordnungsgemäß funktioniert und dass die Bot Framework Emulator so konfiguriert ist, dass er getestet wird.
Starten Sie den Bot, indem Sie den folgenden Befehl ausführen.
dotnet run
Tipp
Sie können zwar einen beliebigen Text-Editor verwenden, um die Quelldateien im Projekt zu bearbeiten, es wird jedoch empfohlen, Visual Studio Code zu verwenden. Visual Studio Code bietet Debugging-Unterstützung, IntelliSense und vieles mehr. Wenn Sie Visual Studio Code verwenden, können Sie den Bot über das Menü "RunStart -> Debugging" starten.
Bestätigen Sie, dass der Bot ausgeführt wird, indem Sie Ihren Browser öffnen und zu
http://localhost:3978
. Sie sollten sehen, dass Ihr Bot bereit ist! Nachricht.Öffnen Sie die Bot Framework Emulator. Wählen Sie das Menü "Datei " und dann " Bot öffnen" aus.
Geben Sie
http://localhost:3978/api/messages
die Bot-URL ein, und wählen Sie dann Verbinden aus.Der Bot antwortet im Chatfenster mit
Hello and welcome!
. Senden Sie eine Nachricht an den Bot, und bestätigen Sie, dass er die Nachricht zurückgibt.
Registrieren des bot im Portal
In dieser Übung erstellen Sie eine neue Bot-Kanalregistrierung und eine Azure AD Webanwendungsregistrierung über das Azure-Portal.
Erstellen einer Bot-Kanalregistrierung
Öffnen Sie einen Browser, und navigieren Sie zum Azure-Portal. Melden Sie sich mit dem Ihrem Azure-Abonnement zugeordneten Konto an.
Wählen Sie oben links das Menü aus, und wählen Sie dann Ressource erstellen aus.
Suchen Sie auf der Seite "Neu " nach
Azure Bot
Azure Bot, und wählen Sie sie aus.Wählen Sie auf der Azure Bot-Seite " Erstellen" aus.
Füllen Sie die erforderlichen Felder aus, und lassen Sie den Messaging-Endpunkt leer. Das Bot-Handle-Feld muss eindeutig sein. Überprüfen Sie unbedingt die verschiedenen Preisstufen, und wählen Sie aus, was für Ihr Szenario sinnvoll ist. Wenn dies nur eine Lernübung ist, sollten Sie die kostenlose Option auswählen.
Wählen Sie für Microsoft App-ID die Option "Neue Microsoft-App-ID erstellen" aus.
Wählen Sie Überprüfen + erstellen aus. Nachdem die Überprüfung abgeschlossen ist, wählen Sie " Erstellen" aus.
Nachdem die Bereitstellung abgeschlossen ist, wählen Sie "Zur Ressource wechseln" aus.
Wählen Sie unter Einstellungen die Option "Konfiguration" aus. Wählen Sie den Link "Verwalten" neben der Microsoft-App-ID aus.
Wählen Sie Neues Clientgeheimnis aus. Fügen Sie eine Beschreibung hinzu, und wählen Sie einen Ablauf aus, und wählen Sie dann "Hinzufügen" aus.
Kopieren Sie den Wert des geheimen Clientschlüssels, bevor Sie diese Seite verlassen. Sie benötigen ihn in den folgenden Schritten.
Wichtig
Dieser geheime Clientschlüssel wird nicht noch einmal angezeigt, stellen Sie daher sicher, dass Sie ihn jetzt kopieren. Sie müssen diesen Wert an mehreren Stellen eingeben, damit er sicher bleibt.
Wählen Sie im linken Menü die Option "Übersicht " aus. Kopieren Sie den Wert der Anwendungs-ID (Client-ID), und speichern Sie ihn. Sie benötigen ihn in den folgenden Schritten.
Kehren Sie in Ihrem Browser zum Bot-Kanalregistrierungsfenster zurück, und fügen Sie die Anwendungs-ID in das Feld "Microsoft-App-ID " ein. Fügen Sie Ihren geheimen Clientschlüssel in das Kennwortfeld ein. Wählen Sie OK aus.
Wählen Sie auf der Registrierungsseite für Bots-Kanäle die Option "Erstellen" aus.
Warten Sie, bis die Bot-Kanalregistrierung erstellt wurde. Kehren Sie nach der Erstellung zur Startseite im Azure-Portal zurück, und wählen Sie dann Bot Services aus. Wählen Sie Ihre neue Bots Channel-Registrierung aus, um deren Eigenschaften anzuzeigen.
Erstellen einer Web-App-Registrierung
Kehren Sie zur Startseite des Azure-Portals zurück, und wählen Sie dann Azure Active Directory aus.
Wählen Sie App-Registrierungen aus.
Wählen Sie Neue Registrierung aus. Legen Sie auf der Seite Anwendung registrieren die Werte wie folgt fest.
- Legen Sie Name auf
Graph Calendar Bot Auth
fest. - Legen Sie Unterstützte Kontotypen auf Konten in allen Organisationsverzeichnissen und persönliche Microsoft-Konten fest.
- Legen Sie unter Umleitungs-URI die erste Dropdownoption auf
Web
fest, und legen Sie den Wert aufhttps://token.botframework.com/.auth/web/redirect
fest.
- Legen Sie Name auf
Wählen Sie Registrieren aus. Kopieren Sie auf der Seite Graph Kalender-Bot-Authentifizierung den Wert der Anwendungs-ID (Client-ID), und speichern Sie ihn. Sie benötigen ihn in den folgenden Schritten.
Wählen Sie unter Verwalten die Option Zertifikate und Geheime Clientschlüssel aus. Wählen Sie die Schaltfläche Neuen geheimen Clientschlüssel aus. Geben Sie einen Wert in Beschreibung ein, wählen Sie eine der Optionen für Gilt bis aus, und wählen Sie dann Hinzufügen aus.
Kopieren Sie den Wert des geheimen Clientschlüssels, bevor Sie diese Seite verlassen. Sie benötigen ihn in den folgenden Schritten.
Wählen Sie API-Berechtigungen aus, und wählen Sie dann "Berechtigung hinzufügen" aus.
Wählen Sie Microsoft Graph und dann delegierte Berechtigungen aus.
Wählen Sie die folgenden Berechtigungen aus, und wählen Sie dann "Berechtigungen hinzufügen" aus.
- openid
- profile
- Calendars.ReadWrite
- MailboxSettings.Read
Informationen zu Berechtigungen
Überlegen Sie, wozu jeder dieser Berechtigungsbereiche es dem Bot ermöglicht und wofür der Bot sie verwenden wird.
- openid und profile: Ermöglicht es dem Bot, Benutzer anzumelden und grundlegende Informationen von Azure AD im Identitätstoken abzurufen.
- Calendars.ReadWrite: Ermöglicht es dem Bot, den Kalender des Benutzers zu lesen und dem Kalender des Benutzers neue Ereignisse hinzuzufügen.
- MailboxSettings.Read: Ermöglicht es dem Bot, die Postfacheinstellungen des Benutzers zu lesen. Der Bot verwendet dies, um die vom Benutzer ausgewählte Zeitzone abzurufen.
- User.Read: Ermöglicht es dem Bot, das Profil des Benutzers von Microsoft Graph abzurufen. Der Bot verwendet dies, um den Namen des Benutzers abzurufen.
Hinzufügen einer OAuth-Verbindung zum Bot
Navigieren Sie im Azure-Portal zur Azure Bot-Seite Ihres Bots. Wählen Sie unter Einstellungen die Option "Konfiguration" aus.
Wählen Sie OAuth-Verbindungs-Einstellungen hinzufügen aus.
Füllen Sie das Formular wie folgt aus, und wählen Sie dann Speichern aus.
- Name:
GraphBotAuth
- Anbieter: Azure Active Directory v2
- Client-ID: Die Anwendungs-ID Ihrer Graph Kalender-Bot-Authentifizierungsregistrierung.
- Geheimer Clientschlüssel: Der geheime Clientschlüssel Ihrer Graph Kalender-Bot-Authentifizierungsregistrierung.
- Token-Exchange-URL: Leer lassen
- Mandanten-ID:
common
- Bereiche:
openid profile Calendars.ReadWrite MailboxSettings.Read User.Read
- Name:
Wählen Sie den GraphBotAuth-Eintrag unter OAuth Connection Einstellungen aus.
Wählen Sie "Verbindung testen" aus. Dadurch wird ein neues Browserfenster oder eine neue Registerkarte geöffnet, um den OAuth-Fluss zu starten.
Melden Sie sich bei Bedarf an. Überprüfen Sie die Liste der angeforderten Berechtigungen, und wählen Sie dann "Annehmen" aus.
Es sollte eine Erfolgreiche Testverbindung mit "GraphBotAuth" angezeigt werden.
Tipp
Sie können die Schaltfläche " Token kopieren " auf dieser Seite auswählen und das Token https://jwt.ms einfügen, um die Ansprüche im Token anzuzeigen. Dies ist hilfreich bei der Problembehandlung bei Authentifizierungsfehlern.
Hinzufügen der Microsoft Identity Platform-Authentifizierung
In dieser Übung verwenden Sie OAuthPrompt des Bot-Frameworks, um die Authentifizierung im Bot zu implementieren, und rufen Zugriffstoken zum Aufrufen der Microsoft Graph-API ab.
Öffnen Sie "./appsettings.json" , und nehmen Sie die folgenden Änderungen vor.
- Ändern Sie den Wert in
MicrosoftAppId
die Anwendungs-ID Ihrer Graph Kalender-Bot-App-Registrierung. - Ändern Sie den Wert ihres
MicrosoftAppPassword
Graph Geheimen Kalender-Bot-Clientschlüssels. - Add a value named
ConnectionName
with a value ofGraphBotAuth
.
{ "MicrosoftAppId": "YOUR_BOT_APP_ID_HERE", "MicrosoftAppPassword": "YOUR_BOT_CLIENT_SECRET_HERE", "ConnectionName": "GraphBotAuth" }
Hinweis
Wenn Sie einen anderen Wert als
GraphBotAuth
den Namen Ihres Eintrags in OAuth Connection Einstellungen im Azure-Portal verwendet haben, verwenden Sie diesen Wert für denConnectionName
Eintrag.- Ändern Sie den Wert in
Implementieren von Dialogfeldern
Erstellen Sie ein neues Verzeichnis im Stammverzeichnis des Projekts namens Dialogs. Erstellen Sie eine neue Datei im Verzeichnis ./Dialogs mit dem Namen "LogoutDialog.cs" , und fügen Sie den folgenden Code hinzu.
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; } } }
Dieses Dialogfeld stellt eine Basisklasse bereit, von der alle anderen Dialogfelder im Bot abgeleitet werden können. Dadurch kann sich der Benutzer unabhängig davon abmelden, wo er sich in den Dialogfeldern des Bots befindet.
Erstellen Sie eine neue Datei im Verzeichnis ./Dialogs mit dem Namen "MainDialog.cs" , und fügen Sie den folgenden Code hinzu.
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); } } }
Nehmen Sie sich einen Moment Zeit, um diesen Code zu überprüfen.
- Im Konstruktor wird ein "WaterfallDialog " mit einer Reihe von Schritten eingerichtet, die in der richtigen Reihenfolge ausgeführt werden.
- Darin
LoginPromptStepAsync
wird ein OAuthPrompt gesendet. Wenn der Benutzer nicht angemeldet ist, wird eine Eingabeaufforderung der Benutzeroberfläche an den Benutzer gesendet. - Darin
ProcessLoginStepAsync
wird überprüft, ob die Anmeldung erfolgreich war, und eine Bestätigung gesendet. - Darin
PromptUserStepAsync
wird ein ChoicePrompt mit den verfügbaren Befehlen gesendet. - Darin
CommandStepAsync
wird die Auswahl des Benutzers gespeichert, und anschließend wird ein OAuthPrompt erneut angezeigt. - Dabei
ProcessStepAsync
wird eine Aktion basierend auf dem empfangenen Befehl ausgeführt. - Darin
ReturnToPromptStepAsync
beginnt der Wasserfall, übergibt jedoch ein Flag, um die anfängliche Benutzeranmeldung zu überspringen.
- Darin
- Im Konstruktor wird ein "WaterfallDialog " mit einer Reihe von Schritten eingerichtet, die in der richtigen Reihenfolge ausgeführt werden.
CalendarBot aktualisieren
Der nächste Schritt besteht darin, CalendarBot so zu aktualisieren, dass diese neuen Dialogfelder verwendet werden.
Öffnen Sie ./Bots/CalendarBot.cs , und ersetzen Sie den gesamten Inhalt durch den folgenden Code.
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); } } }
Hier ist eine kurze Zusammenfassung der Änderungen.
- Die CalendarBot-Klasse wurde in eine Vorlagenklasse geändert, wobei ein Dialogfeld empfangen wurde.
- Die CalendarBot-Klasse wurde geändert, um TeamsActivityHandler zu erweitern, sodass sie sich Microsoft Teams anmelden kann.
- Zusätzliche Methodenüberschreibungen zum Aktivieren der Authentifizierung hinzugefügt.
Aktualisieren von Startup.cs
Der letzte Schritt besteht darin, die ConfigureServices
Methode zu aktualisieren, um die für die Authentifizierung erforderlichen Dienste und das neue Dialogfeld hinzuzufügen.
Öffnen Sie "./Startup.cs", und entfernen Sie die
services.AddTransient<IBot, Bots.CalendarBot>();
Zeile aus derConfigureServices
Methode.Fügen Sie den folgenden Code am Ende der
ConfigureServices
Methode ein.// 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>>();
Testen der Authentifizierung
Speichern Sie alle Ihre Änderungen, und starten Sie den Bot mit
dotnet run
.Öffnen Sie die Bot Framework Emulator. Wählen Sie das Zahnradsymbol ⚙ unten links aus.
Geben Sie den lokalen Pfad zu Ihrer Installation von ngrok ein, und aktivieren Sie "Bypass ngrok" für lokale Adressen, und führen Sie "ngrok" aus, wenn die Emulator Optionen startet. Wählen Sie Speichern aus.
Wählen Sie das Menü "Datei " und dann "Neue Botkonfiguration"... aus.
Füllen Sie die Felder wie folgt aus.
- Botname:
CalendarBot
- Endpunkt-URL:
https://localhost:3978/api/messages
- Microsoft App-ID: Die Anwendungs-ID Ihrer Graph Kalender-Bot-App-Registrierung
- Microsoft App-Kennwort: Ihr Graph geheimer Kalender-Bot-Clientschlüssel
- Verschlüsseln von Schlüsseln, die in Ihrer Bot-Konfiguration gespeichert sind: Aktiviert
- Botname:
Wählen Sie "Speichern und verbinden" aus. Nachdem der Emulator eine Verbindung hergestellt hat, sollte folgendes angezeigt werden:
Welcome to Microsoft Graph CalendarBot. Type anything to get started.
Geben Sie Text ein, und senden Sie ihn an den Bot. Der Bot antwortet mit einer Anmeldeaufforderung.
Wählen Sie die Schaltfläche "Anmelden " aus. Der Emulator fordert Sie auf, die URL zu bestätigen, die mit
oauthlink://https://token.botframeworkcom
beginnt. Wählen Sie "Bestätigen " aus, um fortzufahren.Melden Sie sich im Popupfenster mit Ihrem Microsoft 365 Konto an. Überprüfen Sie die angeforderten Berechtigungen, und akzeptieren Sie sie.
Nach Abschluss der Authentifizierung und Zustimmung wird im Popupfenster ein Überprüfungscode bereitgestellt. Kopieren Sie den Code, und schließen Sie das Fenster.
Geben Sie den Überprüfungscode in das Chatfenster ein, um die Anmeldung abzuschließen.
Wenn Sie die Schaltfläche " Token anzeigen " (oder "Typ
show token
") auswählen, zeigt der Bot das Zugriffstoken an. Über die Schaltfläche " Abmelden " (oder "log out
Eingeben") werden Sie abgemeldet.
Tipp
Möglicherweise wird die folgende Fehlermeldung im Bot Framework Emulator angezeigt, wenn Sie eine Unterhaltung mit dem Bot starten.
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
Aktivieren Sie in diesem Fall unbedingt die Option "ngrok ausführen", wenn die Option Emulator in den Emulatoreinstellungen gestartet wird, und starten Sie den Emulator neu.
Abrufen von Benutzer Details mit Microsoft Graph
In diesem Abschnitt verwenden Sie das Microsoft Graph SDK, um den angemeldeten Benutzer abzurufen.
Erstellen eines Graph Diensts
Beginnen Sie mit der Implementierung eines Diensts, den der Bot verwenden kann, um einen GraphServiceClient aus dem Microsoft Graph SDK abzurufen, und stellen Sie diesen Dienst dann dem Bot per Abhängigkeitsinjektion zur Verfügung.
Erstellen Sie ein neues Verzeichnis im Stammverzeichnis des Projekts mit dem Namen Graph. Erstellen Sie eine neue Datei im Verzeichnis "./Graph" mit dem Namen "IGraphClientService.cs", und fügen Sie den folgenden Code hinzu.
using Microsoft.Graph; namespace CalendarBot.Graph { public interface IGraphClientService { GraphServiceClient GetAuthenticatedGraphClient(string accessToken); } }
Erstellen Sie eine neue Datei im Verzeichnis "./Graph" mit dem Namen "GraphClientService.cs", und fügen Sie den folgenden Code hinzu.
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; } )); } } }
Öffnen Sie "./Startup.cs" , und fügen Sie die folgende
using
Anweisung am Anfang der Datei hinzu.using CalendarBot.Graph;
Fügen Sie am Ende der
ConfigureServices
-Funktion den folgenden Code hinzu.// Add the Graph client service services.AddSingleton<IGraphClientService, GraphClientService>();
Öffnen Sie ./Dialogs/MainDialog.cs. Fügen Sie die folgenden
using
Anweisungen am Anfang der Datei hinzu.using System; using System.IO; using AdaptiveCards; using CalendarBot.Graph; using Microsoft.Graph;
Fügen Sie der MainDialog-Klasse die folgende Eigenschaft hinzu.
private readonly IGraphClientService _graphClientService;
Suchen Sie den Konstruktor für die MainDialog-Klasse , und aktualisieren Sie die Signatur so, dass ein IGraphServiceClient-Parameter verwendet wird .
public MainDialog( IConfiguration configuration, ILogger<MainDialog> logger, IGraphClientService graphClientService) : base(nameof(MainDialog), configuration["ConnectionName"])
Fügen Sie dem Konstruktor den folgenden Code hinzu.
_graphClientService = graphClientService;
Abrufen des angemeldeten Benutzers
In diesem Abschnitt verwenden Sie die Microsoft Graph, um den Namen, die E-Mail-Adresse und das Foto des Benutzers abzurufen. Anschließend erstellen Sie eine adaptive Karte, um die Informationen anzuzeigen.
Erstellen Sie eine neue Datei im Stammverzeichnis des Projekts mit dem Namen CardHelper.cs. Fügen Sie den folgenden Code in die Datei ein:
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)}"); } } }
Dieser Code verwendet das AdaptiveCard-NuGet-Paket, um eine adaptive Karte zum Anzeigen des Benutzers zu erstellen.
Fügen Sie der MainDialog-Klasse die folgende Funktion hinzu.
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); }
Überlegen Sie, was dieser Code bewirkt.
- Es verwendet den graphClient , um den angemeldeten Benutzer abzurufen.
- Sie verwendet die
Select
Methode, um zu begrenzen, welche Felder zurückgegeben werden.
- Sie verwendet die
- Es verwendet den graphClient , um das Foto des Benutzers abzurufen und die kleinste unterstützte Größe von 48 x 48 Pixeln anzufordern.
- Sie verwendet die CardHelper-Klasse zum Erstellen einer adaptiven Karte und sendet die Karte als Anlage.
- Es verwendet den graphClient , um den angemeldeten Benutzer abzurufen.
Ersetzen Sie den Code innerhalb des
else if (command.StartsWith("show me"))
Blocks durchProcessStepAsync
Folgendes.else if (command.StartsWith("show me")) { await DisplayLoggedInUser(tokenResponse.Token, stepContext, cancellationToken); }
Speichern Sie alle Änderungen, und starten Sie den Bot neu.
Verwenden Sie die Bot Framework Emulator, um eine Verbindung mit dem Bot herzustellen und sich anzumelden. Wählen Sie die Schaltfläche " Mich anzeigen " aus, um den angemeldeten Benutzer anzuzeigen.
Abrufen einer Kalenderansicht
In diesem Abschnitt verwenden Sie das Microsoft Graph SDK, um die nächsten 3 bevorstehenden Ereignisse im Kalender des Benutzers für die aktuelle Woche abzurufen.
Abrufen einer Kalenderansicht
Eine Kalenderansicht ist eine Liste von Ereignissen im Kalender eines Benutzers, die zwischen zwei Datums-/Uhrzeitwerten liegen. Der Vorteil der Verwendung einer Kalenderansicht besteht darin, dass sie alle Vorkommen von Besprechungsserien einschließt.
Öffnen Sie "./CardHelper.cs" , und fügen Sie der CardHelper-Klasse die folgende Funktion hinzu.
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; }
Dieser Code erstellt eine adaptive Karte zum Rendern eines Kalenderereignisses.
Öffnen Sie ./Dialogs/MainDialog.cs , und fügen Sie der MainDialog-Klasse die folgende Funktion hinzu.
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); }
Überlegen Sie, was dieser Code bewirkt.
- Es ruft die MailboxSettings des Benutzers ab, um die bevorzugten Zeitzonen- und Datums-/Uhrzeitformate des Benutzers zu ermitteln.
- It sets the startDateTime and endDateTime values to now and the end of the week, respectively. Dadurch wird das Zeitfenster definiert, das von der Kalenderansicht verwendet wird.
- Es wird mit den folgenden Details aufgerufen
graphClient.Me.CalendarView
.- Die Kopfzeile wird auf die bevorzugte Zeitzone des Benutzers festgelegt
Prefer: outlook.timezone
, wodurch sich die Start- und Endzeiten für die Ereignisse in der Zeitzone des Benutzers befinden. - Die Methode wird verwendet
Top(3)
, um die Ergebnisse auf die ersten drei Ereignisse zu beschränken. - Es wird verwendet
Select
, um die zurückgegebenen Felder auf die vom Bot verwendeten Felder zu beschränken. - Es wird
OrderBy
verwendet, um die Ereignisse nach Startzeit zu sortieren.
- Die Kopfzeile wird auf die bevorzugte Zeitzone des Benutzers festgelegt
- Der Antwortnachricht wird eine adaptive Karte für jedes Ereignis hinzugefügt.
Ersetzen Sie den Code innerhalb des
else if (command.StartsWith("show calendar"))
Blocks durchProcessStepAsync
Folgendes.else if (command.StartsWith("show calendar")) { await DisplayCalendarView(tokenResponse.Token, stepContext, cancellationToken); }
Speichern Sie alle Änderungen, und starten Sie den Bot neu.
Verwenden Sie die Bot Framework Emulator, um eine Verbindung mit dem Bot herzustellen und sich anzumelden. Wählen Sie die Schaltfläche "Kalender anzeigen " aus, um die Kalenderansicht anzuzeigen.
Erstellen eines neuen Ereignisses
In diesem Abschnitt verwenden Sie das Microsoft Graph SDK, um dem Kalender des Benutzers ein Ereignis hinzuzufügen.
Implementieren eines Dialogfelds
Erstellen Sie zunächst ein neues benutzerdefiniertes Dialogfeld, um den Benutzer aufzufordern, die Werte einzugeben, die zum Hinzufügen eines Ereignisses zum Kalender erforderlich sind. In diesem Dialogfeld wird ein "WaterfallDialog " verwendet, um die folgenden Schritte auszuführen.
- Eingabeaufforderung für einen Betreff
- Fragen, ob der Benutzer Personen einladen möchte
- Eingabeaufforderung für Teilnehmer (wenn der Benutzer "Ja" zum vorherigen Schritt gesagt hat)
- Eingabeaufforderung für ein Startdatum und eine Startzeit
- Eingabeaufforderung für Enddatum und -uhrzeit
- Zeigen Sie alle erfassten Werte an, und bitten Sie den Benutzer, dies zu bestätigen.
- Wenn der Benutzer dies bestätigt, rufen Sie das Zugriffstoken von OAuthPrompt ab.
- Erstellen des Ereignisses
Erstellen Sie eine neue Datei im Verzeichnis "./Dialogs" mit dem Namen "NewEventDialog.cs", und fügen Sie den folgenden Code hinzu.
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); } } }
Dies ist die Shell eines neuen Dialogfelds, das den Benutzer zur Eingabe der Werte auffordert, die zum Hinzufügen eines Ereignisses zum Kalender erforderlich sind.
Fügen Sie der NewEventDialog-Klasse die folgende Funktion hinzu, um den Benutzer zur Eingabe eines Betreffs aufzufordern.
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); }
Fügen Sie der NewEventDialog-Klasse die folgende Funktion hinzu, um den Betreff zu speichern, den der Benutzer im vorherigen Schritt angegeben hat, und um zu fragen, ob er Teilnehmer hinzufügen möchte.
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); }
Fügen Sie der NewEventDialog-Klasse die folgende Funktion hinzu, um die Antwort des Benutzers aus dem vorherigen Schritt zu überprüfen, und fordern Sie den Benutzer bei Bedarf auf, eine Liste der Teilnehmer einzugeben.
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); } }
Fügen Sie der NewEventDialog-Klasse die folgende Funktion hinzu, um die Liste der Teilnehmer aus dem vorherigen Schritt (sofern vorhanden) zu speichern, und fordern Sie den Benutzer auf, ein Startdatum und eine Startzeit einzugeben.
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); }
Fügen Sie der NewEventDialog-Klasse die folgende Funktion hinzu, um den Startwert aus dem vorherigen Schritt zu speichern, und fordern Sie den Benutzer auf, ein Enddatum und eine Endzeit anzugeben.
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); }
Fügen Sie der NewEventDialog-Klasse die folgende Funktion hinzu, um den Endwert aus dem vorherigen Schritt zu speichern, und bitten Sie den Benutzer, alle Eingaben zu bestätigen.
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); }
Fügen Sie der NewEventDialog-Klasse die folgende Funktion hinzu, um die Antwort des Benutzers aus dem vorherigen Schritt zu überprüfen. Wenn der Benutzer die Eingaben bestätigt, verwenden Sie die OAuthPrompt-Klasse , um ein Zugriffstoken abzurufen. Andernfalls beenden Sie das Dialogfeld.
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); } }
Fügen Sie der NewEventDialog-Klasse die folgende Funktion hinzu, um das Microsoft Graph SDK zum Erstellen des neuen Ereignisses zu verwenden.
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); }
Überlegen Sie, was dieser Code bewirkt.
- Es ruft die MailboxSettings des Benutzers ab, um die bevorzugte Zeitzone des Benutzers zu ermitteln.
- Es erstellt ein Event-Objekt mithilfe der vom Benutzer bereitgestellten Werte. Beachten Sie, dass die Start - und End-Eigenschaften mit der Zeitzone des Benutzers festgelegt werden.
- Das Ereignis wird im Kalender des Benutzers erstellt.
Hinzufügen einer Überprüfung
Fügen Sie nun der Eingabe des Benutzers eine Überprüfung hinzu, um Fehler beim Erstellen des Ereignisses mit Microsoft Graph zu vermeiden. Wir möchten folgendes sicherstellen:
- Wenn der Benutzer eine Teilnehmerliste angibt, sollte es sich um eine durch Semikolons getrennte Liste gültiger E-Mail-Adressen handeln.
- Startdatum/-uhrzeit sollte ein gültiges Datum und eine gültige Uhrzeit sein.
- Das Enddatum/die Endzeit sollte ein gültiges Datum und eine gültige Uhrzeit sein und später als der Anfang sein.
Fügen Sie der NewEventDialog-Klasse die folgende Funktion hinzu, um den Benutzereintrag für Teilnehmer zu überprüfen.
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); }
Fügen Sie der NewEventDialog-Klasse die folgenden Funktionen hinzu, um den Benutzereintrag auf Startdatum und -uhrzeit zu überprüfen.
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); }
Fügen Sie der NewEventDialog-Klasse die folgende Funktion hinzu, um den Benutzereintrag auf Enddatum und -uhrzeit zu überprüfen.
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); }
Hinzufügen von Schritten zu "WaterfallDialog"
Nachdem Sie nun über alle "Schritte" für das Dialogfeld verfügen, besteht der letzte Schritt darin, sie einem "WaterfallDialog " im Konstruktor hinzuzufügen und dann " NewEventDialog " zum MainDialog hinzuzufügen.
Suchen Sie den NewEventDialog-Konstruktor , und fügen Sie ihm den folgenden Code hinzu.
_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);
Dadurch werden alle verwendeten Dialogfelder hinzugefügt und alle Funktionen hinzugefügt, die Sie als Schritte in "WaterfallDialog" implementiert haben.
Öffnen Sie ./Dialogs/MainDialog.cs , und fügen Sie dem Konstruktor die folgende Zeile hinzu.
AddDialog(new NewEventDialog(configuration, graphClientService));
Ersetzen Sie den Code innerhalb des
else if (command.StartsWith("add event"))
Blocks durchProcessStepAsync
Folgendes.else if (command.StartsWith("add event")) { return await stepContext.BeginDialogAsync(nameof(NewEventDialog), null, cancellationToken); }
Speichern Sie alle Änderungen, und starten Sie den Bot neu.
Verwenden Sie die Bot Framework Emulator, um eine Verbindung mit dem Bot herzustellen und sich anzumelden. Wählen Sie die Schaltfläche "Ereignis hinzufügen " aus.
Antworten Sie auf die Aufforderungen zum Erstellen eines neuen Ereignisses. Wenn Sie zur Eingabe von Start- und Endwerten aufgefordert werden, können Sie Ausdrücke wie "heute um 15:00 Uhr" oder "jetzt" verwenden.
Herzlichen Glückwunsch!
Sie haben das Lernprogramm von Bot Framework Microsoft Graph abgeschlossen. Da Sie nun über einen funktionierenden Bot verfügen, der Microsoft Graph aufruft, können Sie experimentieren und neue Features hinzufügen. Besuchen Sie die Übersicht über Microsoft Graph, um alle Daten anzuzeigen, auf die Sie mit Microsoft Graph zugreifen können.
Feedback
Bitte geben Sie Feedback zu diesem Lernprogramm im GitHub Repository.
Liegt ein Problem mit diesem Abschnitt vor? Wenn ja, senden Sie uns Feedback, damit wir den Abschnitt verbessern können.