Microsoft Graph を使用して Bot フレームワークのボットを作成する
このチュートリアルでは、Microsoft Graph API を使用してユーザーの予定表情報を取得するボット フレームワーク ボットを構築する方法について説明します。
ヒント
完了したチュートリアルをダウンロードする場合は、リポジトリをダウンロードまたは複製GitHubできます。 アプリ ID とシークレットを使用してアプリを構成する手順については、デモ フォルダーの README ファイルを参照してください。
前提条件
このチュートリアルを開始する前に、開発マシンに次の手順をインストールする必要があります。
また、Outlook.com 上のメールボックスを持つ個人用 Microsoft アカウント、または Microsoft の仕事用または学校用のアカウントを持っている必要があります。 Microsoft アカウントをお持ちでない場合は、無料アカウントを取得するためのオプションが 2 つご利用できます。
- 新しい 個人用 Microsoft アカウントにサインアップできます。
- 開発者プログラムにサインアップして、Microsoft 365サブスクリプションをMicrosoft 365できます。
- Azure サブスクリプション。 アカウントをお持ちではない場合は、開始する 前に無料アカウント を作成してください。
注意
このチュートリアルは、次のバージョンで記述されています。 このガイドの手順は、他のバージョンでも動作しますが、テストされていない場合があります。
- .NET Core SDK バージョン 5.0.302
- Bot Framework Emulator 4.1.3
- ngrok 2.3.40
フィードバック
このチュートリアルに関するフィードバックは、リポジトリのGitHubしてください。
Bot フレームワークプロジェクトを作成する
このセクションでは、Bot Framework プロジェクトを作成します。
プロジェクトを作成するディレクトリでコマンド ライン インターフェイス (CLI) を開きます。 Microsoft.Bot.Framework.CSharp.EchoBot テンプレートを使用して新しいプロジェクトを作成するには、次のコマンドを実行します。
dotnet new echobot -n GraphCalendarBot
注意
エラーが発生した場合は
No templates matched the input template name: echobot.
、次のコマンドを使用してテンプレートをインストールし、前のコマンドを再実行します。dotnet new -i Microsoft.Bot.Framework.CSharp.EchoBot
既定の EchoBot クラスの名前 を CalendarBot に変更します。 ./Bots/EchoBot.cs を開き、すべてのインスタンスをで置換
EchoBot
しますCalendarBot
。 ファイルの名前を CalendarBot.cs に変更します。残りの .
EchoBot
cs ファイル内のすべてのCalendarBot
インスタンスを 置き換 える。CLI で、カレント ディレクトリを GraphCalendarBot ディレクトリに変更し、次のコマンドを実行してプロジェクトのビルドを確認します。
dotnet build
NuGet パッケージを追加する
次に進む前に、後で使用NuGet追加のパッケージをインストールします。
- アダプティブ カードを使用 して、ボットが応答でアダプティブ カードを送信できます。
- ボットにダイアログ のサポートを追加する Microsoft.Bot.Builder.Dialogs 。
- ボット プロンプトから返される TIMEX 式を DateTime オブジェクトに変換する Microsoft.Recognizers.Text.DataTypes.TimexExpression。
- Microsoft.Graph: Microsoft Graph を呼び出すためのものです。
依存関係をインストールするには、CLI で次のコマンドを実行します。
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
ボットをテストする
コードを追加する前に、ボットをテストして、ボットが正しく動作し、Bot Framework Emulatorがテストするように構成されていることを確認します。
次のコマンドを実行してボットを起動します。
dotnet run
ヒント
任意のテキスト エディターを使用してプロジェクト内のソース ファイルを編集することができますが、プロジェクトのソース ファイルをVisual Studio Code。 Visual Studio Codeサポート、Intellisense などをご利用いただけます。 このコマンドをVisual Studio Code、RunStart デバッグ メニューを使用 して -> ボットを起動 できます。
ブラウザーを開いてに行って、ボットが実行されているのを確認します
http://localhost:3978
。 ボットの準備 が完了しているのが表示されます。 メッセージ。ファイルを開Bot Framework Emulator。 [ファイル] メニューの [ ボットを開 く] を選択します。
ボット
http://localhost:3978/api/messages
URL に 入力し、[ボットの URL] をConnect。ボットはチャット ウィンドウで
Hello and welcome!
応答します。 ボットにメッセージを送信し、そのメッセージがエコーバックするのを確認します。
Bot をポータルに登録する
この演習では、Azure Portal を使用して新しいボット チャネル登録Azure AD Web アプリケーション登録を作成します。
ボット チャネル登録の作成
ブラウザーを開き、 Azure Portal に移動します。 Azure サブスクリプションに関連付けられたアカウントを使用してログインします。
左上のメニューを選択し、[リソースの作成 ] を選択します。
[新しい ] ページ で、Azure
Azure Bot
Bot を検索して選択します。[ Azure Bot] ページで、[ 作成] を 選択します。
必要なフィールドに入力し、メッセージング エンドポイント を空白のままに します。 ボット ハンドル フィールドは 一意である必要があります。 さまざまな価格レベルを確認し、シナリオに合った情報を選択してください。 これが単なる学習演習の場合は、無料オプションを選択できます。
[ Microsoft App ID] で、[ 新しい Microsoft アプリ ID の作成] を選択します。
[確認 + 作成] を選びます。 検証が完了したら、[作成] を 選択します。
展開が完了したら、[リソースに移動 ] を選択します。
[構成 設定 構成] を 選択します。 [Microsoft App ID ] の 横にある [管理] リンクを選択します。
[新しいクライアント シークレット] を選択します。 説明を追加し、有効期限を選択してから、[追加] を 選択します。
このページを離れる前に、クライアント シークレットの値をコピーします。 次の手順で必要です。
重要
このクライアント シークレットは今後表示されないため、この段階で必ずコピーするようにしてください。 この値を複数の場所に入力して安全に保つ必要があります。
左側 のメニューで [概要] を選択します。 アプリケーション (クライアント ) ID の値を コピーして保存すると、次の手順で必要になります。
ブラウザーの [ボット チャネル登録] ウィンドウに戻り、アプリケーション ID を [Microsoft App ID] フィールドに貼り付 けます。 クライアント シークレットを [パスワード] フィールドに 貼り付 けます。 [OK] を選択します。
[ボット チャネルの登録] ページで、[ 作成] を 選択します。
ボット チャネルの登録が作成されるのを待ちます。 作成したら、Azure Portal のホーム ページに戻り、[ボット サービス] を選択します。 新しいボット チャネル登録を選択して、そのプロパティを表示します。
Web アプリの登録を作成する
Azure portal のホーム ページに戻り、[次へ] を 選択 Azure Active Directory。
[アプリの登録] を選択します。
[新規登録] を選択します。 [アプリケーションを登録] ページで、次のように値を設定します。
Graph Calendar Bot Auth
に [名前] を設定します。- [サポートされているアカウントの種類] を [任意の組織のディレクトリ内のアカウントと個人用の Microsoft アカウント] に設定します。
- [リダイレクト URI] で、最初のドロップダウン リストを
Web
に設定し、それからhttps://token.botframework.com/.auth/web/redirect
に値を設定します。
[登録] を選択します。 [カレンダー Graphボット認証] ページで、アプリケーション (クライアント) ID の値をコピーして保存します。次の手順で必要になります。
[管理] で [証明書とシークレット] を選択します。 [新しいクライアント シークレット] ボタンを選択します。 [説明] に値を入力して、[有効期限] のオプションのいずれかを選び、[追加] を選択します。
このページを離れる前に、クライアント シークレットの値をコピーします。 次の手順で必要です。
[ API アクセス許可] を選択 し、[アクセス許可 の追加] を選択します。
[Microsoft Graph] を 選択し、[委任されたアクセス許可] を選択します。
次のアクセス許可を選択し、[アクセス許可の 追加] を選択します。
- openid
- profile
- Calendars.ReadWrite
- MailboxSettings.Read
アクセス許可について
これらの各アクセス許可スコープでボットが実行できる操作と、ボットが使用する操作について検討します。
- openid と profile: ボットがユーザーにサインインし、ID トークン内のユーザーからAzure ADを取得できます。
- Calendars.ReadWrite: ボットは、ユーザーの予定表を読み取り、ユーザーの予定表に新しいイベントを追加できます。
- MailboxSettings.Read: ボットは、ユーザーのメールボックス設定を読み取るを許可します。 ボットはこれを使用して、ユーザーの選択したタイム ゾーンを取得します。
- User.Read: ボットは、Microsoft クライアントからユーザーのプロファイルを取得Graph。 ボットはこれを使用して、ユーザーの名前を取得します。
ボットへの OAuth 接続の追加
Azure Portal でボットの Azure Bot ページに移動します。 [構成 ] の下 の [構成設定。
[OAuth 接続の追加] を設定。
次のようにフォームに入力し、[保存] を 選択します。
- 名前:
GraphBotAuth
- プロバイダー: Azure Active Directory v2
- クライアント ID: カレンダー ボット 認証登録Graphアプリケーション ID。
- クライアント シークレット: 予定表ボット Auth 登録Graphクライアント シークレット。
- トークンExchange URL: 空白のままにする
- テナント ID:
common
- スコープ:
openid profile Calendars.ReadWrite MailboxSettings.Read User.Read
- 名前:
[OAuth 接続] で GraphBotAuth エントリ を選択設定。
[テスト 接続] を選択します。 これにより、新しいブラウザー ウィンドウまたはタブが開き、OAuth フローが開始されます。
必要に応じて、サインインします。 要求されたアクセス許可の一覧を確認し、[同意する] を 選択します。
「 GraphBotAuth」成功メッセージへのテスト接続が表示 されます。
ヒント
このページで [トークンのコピー] ボタン https://jwt.ms を選択し、トークンを貼り付け、トークン内のクレームを確認できます。 これは、認証エラーのトラブルシューティングに役立ちます。
Microsoft identity platform 認証を追加する
この演習では、ボット フレームワークの OAuthPrompt を使用してボットに認証を実装し、Microsoft Graph API を呼び出すアクセス トークンを取得します。
./appsettings.json を開き、次の変更を行います。
- カレンダー ボット アプリ登録
MicrosoftAppId
のアプリケーション ID のGraph変更 します。 - 予定表ボット クライアント シークレット
MicrosoftAppPassword
の Graphを 変更します。 - の値を持つ名前
ConnectionName
の値を追加しますGraphBotAuth
。
{ "MicrosoftAppId": "YOUR_BOT_APP_ID_HERE", "MicrosoftAppPassword": "YOUR_BOT_CLIENT_SECRET_HERE", "ConnectionName": "GraphBotAuth" }
注意
Azure Portal の
GraphBotAuth
OAuth Connection 設定エントリの名前以外の値を使用した場合は、その値をエントリに使用ConnectionName
します。- カレンダー ボット アプリ登録
ダイアログの実装
Dialogs という名前のプロジェクトのルートに新しいディレクトリ を作成します。 LogoutDialog.cs という名前の ./Dialogs ディレクトリに新しいファイルを作成し、次のコードを追加します。
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; } } }
このダイアログは、ボット内の他のすべてのダイアログから派生する基本クラスを提供します。 これにより、ボットのダイアログの場所に関係なく、ユーザーはログアウトできます。
MainDialog.cs という名前の ./Dialogs ディレクトリに新しいファイルを作成し、次のコードを追加します。
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); } } }
このコードを確認してください。
- コンストラクターでは、一連の手順を順番に実行して 、WaterfallDialog を設定します。
- OAuthPrompt
LoginPromptStepAsync
を送信します。 ユーザーがログインしていない場合は、UI プロンプトがユーザーに送信されます。 - ログイン
ProcessLoginStepAsync
が成功したのか確認し、確認を送信します。 - この
PromptUserStepAsync
コマンドでは、 使用可能なコマンドを使用して ChoicePrompt を送信します。 - ユーザー
CommandStepAsync
の選択が保存された後、 OAuthPrompt を再送信します。 - 受信
ProcessStepAsync
したコマンドに基づいてアクションを実行します。 - この
ReturnToPromptStepAsync
設定では、ウォーターフォールオーバーが開始されますが、フラグを渡して最初のユーザー ログインをスキップします。
- OAuthPrompt
- コンストラクターでは、一連の手順を順番に実行して 、WaterfallDialog を設定します。
CalendarBot の更新
次の手順では、 CalendarBot を更新して 、これらの新しいダイアログを使用します。
./Bots/CalendarBot.cs を開き、その内容全体を次のコードに置き換えます。
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); } } }
変更の簡単な概要を次に示します。
- CalendarBot クラスを テンプレート クラスに変更し、Dialog を受け取 ります。
- CalendarBot クラスを変更 して TeamsActivityHandler を拡張し、このクラスにサインインMicrosoft Teams。
- 認証を有効にするメソッドのオーバーライドを追加しました。
Startup.cs の更新
最後の手順では、認証に必要 ConfigureServices
なサービスと新しいダイアログを追加するメソッドを更新します。
./Startup.cs を開 き、メソッドから
services.AddTransient<IBot, Bots.CalendarBot>();
行を削除ConfigureServices
します。メソッドの最後に次のコードを挿入
ConfigureServices
します。// 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>>();
テスト認証
すべての変更を保存し、ボットを起動します
dotnet run
。ファイルを開Bot Framework Emulator。 左下の歯車⚙アイコンを選択します。
ngrok のインストールへのローカル パスを入力し、ローカル アドレスに対して ngrok をバイパスし、オプションを起動するときに ngrok Emulatorを有効 にします。 [保存] を選択します。
[ファイル] メニューの [新しい ボット構成....] を選択します。
フィールドに次のように入力します。
- ボット名:
CalendarBot
- エンドポイント URL:
https://localhost:3978/api/messages
- Microsoft App ID: カレンダー ボット アプリ登録Graph アプリケーション ID
- Microsoft App のパスワード: カレンダー ボット Graphシークレット
- ボット構成に格納されているキーを暗号化します。 有効
- ボット名:
[保存 して接続] を選択します。 エミュレーターが接続すると、次の情報が表示されます。
Welcome to Microsoft Graph CalendarBot. Type anything to get started.
テキストを入力し、ボットに送信します。 ボットはログイン プロンプトで応答します。
[ログイン] ボタンを選択 します。 エミュレーターでは、で始まる URL の確認を求めるメッセージが表示されます
oauthlink://https://token.botframeworkcom
。 [確認 ] を選択 して続行します。ポップアップ ウィンドウで、自分のアカウントでログインMicrosoft 365します。 要求されたアクセス許可を確認し、同意します。
認証と同意が完了すると、ポップアップ ウィンドウに検証コードが表示されます。 コードをコピーし、ウィンドウを閉じます。
ログインを完了するには、チャット ウィンドウに検証コードを入力します。
[トークンの表示] ボタン (または 種類) を選択すると
show token
、ボットにアクセス トークンが表示されます。 [ ログアウト] ボタン (または入力log out
) でログアウトします。
ヒント
ボットとの会話を開始するときに、Bot Framework Emulatorエラー メッセージが表示される場合があります。
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
この場合は、エミュレーターの設定で [Emulator起動するときに ngrok を 実行する] オプションを有効にし、エミュレーターを再起動してください。
Microsoft Graph を使用してユーザーの詳細を取得する
このセクションでは、Microsoft Graph SDK を使用して、ログインしているユーザーを取得します。
サービスのGraphする
まず、ボットが Microsoft Graph SDK から GraphServiceClient を取得するために使用できるサービスを実装し、そのサービスを依存関係の挿入によってボットで利用できます。
プロジェクトのルートに新しいディレクトリを作成します。Graph。 IGraphClientService.cs という名前の ./Graph ディレクトリに新しいファイルを作成し、次のコードを追加します。
using Microsoft.Graph; namespace CalendarBot.Graph { public interface IGraphClientService { GraphServiceClient GetAuthenticatedGraphClient(string accessToken); } }
GraphClientService.cs という 名前の ./Graph ディレクトリに新しいファイルを作成し、次のコードを追加します。
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; } )); } } }
./Startup.cs を 開き、ファイルの
using
上部に次のステートメントを追加します。using CalendarBot.Graph;
次の関数を
ConfigureServices
関数の最後に追加します。// Add the Graph client service services.AddSingleton<IGraphClientService, GraphClientService>();
./Dialogs/MainDialog.cs を開きます。 ファイルの上部
using
に次のステートメントを追加します。using System; using System.IO; using AdaptiveCards; using CalendarBot.Graph; using Microsoft.Graph;
MainDialog クラスに次 のプロパティを追加 します。
private readonly IGraphClientService _graphClientService;
MainDialog クラスのコンストラクターを見つけて、署名を更新して IGraphServiceClient パラメーターを取得 します。
public MainDialog( IConfiguration configuration, ILogger<MainDialog> logger, IGraphClientService graphClientService) : base(nameof(MainDialog), configuration["ConnectionName"])
コンストラクターに次のコードを追加します。
_graphClientService = graphClientService;
ログオンしているユーザーを取得する
このセクションでは、Microsoft Graphを使用して、ユーザーの名前、電子メール アドレス、および写真を取得します。 次に、情報を表示するアダプティブ カードを作成します。
CardHelper.cs という名前のプロジェクトのルートに新しいファイルを作成します。 次のコードをファイルに追加します。
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)}"); } } }
このコードでは、AdaptiveCard NuGet を使用してアダプティブ カードを作成し、ユーザーを表示します。
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); }
このコードの動作を検討します。
- graphClient を使用して、ログインしているユーザーを取得します。
- このメソッドを使用
Select
して、返されるフィールドを制限します。
- このメソッドを使用
- graphClient を使用してユーザーの写真を取得し、サポートされている最小サイズの 48x48 ピクセルを要求します。
- CardHelper クラスを使用して アダプティブ カードを作成し、カードを添付ファイルとして送信します。
- graphClient を使用して、ログインしているユーザーを取得します。
ブロック内のコードを次
else if (command.StartsWith("show me"))
のコードにProcessStepAsync
置き換えます。else if (command.StartsWith("show me")) { await DisplayLoggedInUser(tokenResponse.Token, stepContext, cancellationToken); }
すべての変更を保存し、ボットを再起動します。
ボットにBot Framework Emulatorログインするには、次の情報を使用します。 [表示 ] ボタンを 選択して、ログオンしているユーザーを表示します。
予定表ビューを取得する
このセクションでは、Microsoft Graph SDK を使用して、現在の週のユーザーの予定表で次の 3 つの今後のイベントを取得します。
予定表ビューを取得する
予定表ビューは、2 つの日付/時刻の値の間に入るユーザーの予定表のイベントの一覧です。 予定表ビューを使用する利点は、定期的な会議の発生が含まれるという利点があります。
./CardHelper.cs を 開き、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; }
このコードでは、予定表イベントをレンダリングするためのアダプティブ カードを作成します。
./Dialogs/MainDialog.cs を開き、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); }
このコードの動作を検討します。
- ユーザーの MailboxSettings を取得して、ユーザーの優先タイム ゾーンと日付/時刻の形式を決定します。
- startDateTime 値と endDateTime 値をそれぞれ now および end end the week に設定します。 これにより、カレンダー ビューで使用される時間のウィンドウが定義されます。
- 次の詳細
graphClient.Me.CalendarView
を呼び出します。- ヘッダーをユーザー
Prefer: outlook.timezone
の優先タイム ゾーンに設定し、イベントの開始時刻と終了時刻をユーザーのタイム ゾーンに設定します。 - このメソッドを使用
Top(3)
して、結果を最初の 3 つのイベントにのみ制限します。 - ボットが
Select
使用するフィールドに対して返されるフィールドを制限するために使用されます。 - イベントを開始
OrderBy
時刻で並べ替える場合に使用します。
- ヘッダーをユーザー
- 各イベントのアダプティブ カードが返信メッセージに追加されます。
ブロック内のコードを次
else if (command.StartsWith("show calendar"))
のコードにProcessStepAsync
置き換えます。else if (command.StartsWith("show calendar")) { await DisplayCalendarView(tokenResponse.Token, stepContext, cancellationToken); }
すべての変更を保存し、ボットを再起動します。
ボットにBot Framework Emulatorログインするには、次の情報を使用します。 [予定表の 表示] ボタンを 選択して、予定表ビューを表示します。
新しいイベントを作成する
このセクションでは、Microsoft Graph SDK を使用して、ユーザーの予定表にイベントを追加します。
ダイアログの実装
まず、新しいカスタム ダイアログを作成して、カレンダーにイベントを追加するために必要な値をユーザーに求めるプロンプトを表示します。 このダイアログでは、 WaterfallDialog を使用して 次の手順を実行します。
- 件名の入力を求める
- ユーザーがユーザーを招待する場合の確認
- 出席者の入力を求めるメッセージ (ユーザーが前の手順に 「はい」と答えた場合)
- 開始日と時刻の入力を求めるプロンプト
- 終了日時の入力を求めるメッセージ
- 収集された値を表示し、ユーザーに確認を求める
- ユーザーが確認した場合は、 OAuthPrompt からアクセス トークンを取得する
- イベントを作成する
NewEventDialog.cs という 名前の ./Dialogs ディレクトリに新しいファイルを作成し、次のコードを追加します。
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); } } }
これは、カレンダーにイベントを追加するために必要な値をユーザーに求める新しいダイアログのシェルです。
NewEventDialog クラスに次の関数を追加 して、ユーザーに件名の入力を求めるプロンプトを表示します。
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); }
NewEventDialog クラスに次の関数を追加して、前の手順でユーザーが与えた件名を格納し、出席者を追加する必要がある場合に確認します。
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); }
NewEventDialog クラスに次の関数を追加して、前の手順からのユーザーの応答を確認し、必要に応じて出席者の一覧をユーザーに求めるプロンプトを表示します。
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); } }
NewEventDialog クラスに次の関数を追加して、前の手順 (存在する場合) の出席者のリストを格納し、開始日時を求めるプロンプトを表示します。
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); }
次の関数を NewEventDialog クラスに追加して、前の手順の開始値を格納し、ユーザーに終了日時の入力を求めるメッセージを表示します。
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); }
次の関数を NewEventDialog クラスに追加して、前の手順の終了値を格納し、すべての入力を確認する必要があります。
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); }
NewEventDialog クラスに次の関数を追加して、前の手順からのユーザーの応答を確認します。 ユーザーが入力を確認した場合は、 OAuthPrompt クラスを使用してアクセス トークンを取得します。 それ以外の場合は、ダイアログを終了します。
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); } }
NewEventDialog クラスに次の関数を追加して、Microsoft Graph SDK を使用して新しいイベントを作成します。
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); }
このコードの動作を検討します。
- ユーザーの MailboxSettings を取得して 、ユーザーの優先タイム ゾーンを決定します。
- ユーザーが指定した 値を 使用して Event オブジェクトを作成します。 Start プロパティと End プロパティは、ユーザーのタイム ゾーンで設定されます。
- ユーザーの予定表にイベントを作成します。
検証の追加
次に、ユーザーの入力に検証を追加して、Microsoft のイベント を使用してイベントを作成するときにエラーが発生しないようにGraph。 次の情報が必要です。
- ユーザーが出席者リストを指定する場合は、有効な電子メール アドレスのセミコロンで区切られたリストである必要があります。
- 開始日/時刻は有効な日付と時刻である必要があります。
- 終了日時は有効な日付と時刻で、開始時刻より後である必要があります。
NewEventDialog クラスに次の関数を追加して、出席者のユーザーのエントリを検証します。
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); }
NewEventDialog クラスに次の関数を追加して、開始日時のユーザーのエントリを検証します。
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); }
NewEventDialog クラスに次の関数を追加して、終了日時のユーザーのエントリを検証します。
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); }
WaterfallDialog に手順を追加する
ダイアログのすべての "手順" が完了したら、最後の手順はコンストラクターの WaterfallDialog に追加してから 、NewEventDialog を MainDialog に追加します。
NewEventDialog コンストラクターを見 つけて、次のコードを追加します。
_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);
これにより、使用されるダイアログすべてが追加され、 ウォーターフォールDialog で手順として実装したすべての関数が追加されます。
./Dialogs/MainDialog.cs を開き、コンストラクターに次の行を追加します。
AddDialog(new NewEventDialog(configuration, graphClientService));
ブロック内のコードを次
else if (command.StartsWith("add event"))
のコードにProcessStepAsync
置き換えます。else if (command.StartsWith("add event")) { return await stepContext.BeginDialogAsync(nameof(NewEventDialog), null, cancellationToken); }
すべての変更を保存し、ボットを再起動します。
ボットにBot Framework Emulatorログインするには、次の情報を使用します。 [イベントの 追加] ボタンを 選択します。
プロンプトに応答して新しいイベントを作成します。 開始値と終了値の入力を求めるメッセージが表示されたら、"today at 3PM"、または "now" のような語句を使用できます。
おめでとうございます。
Bot Framework Microsoft のチュートリアルをGraphしました。 Microsoft Graphを呼び出す作業ボットが作成されたので、新しい機能を試して追加できます。 Microsoft Graphの概要を参照して、Microsoft Graph でアクセスできるすべてのデータを確認Graph。
フィードバック
このチュートリアルに関するフィードバックは、リポジトリのGitHubしてください。
このセクションに問題がある場合 このセクションを改善できるよう、フィードバックをお送りください。