.NET v3 ボットを .NET Framework v4 ボットに移行するMigrate a .NET v3 bot to a .NET Framework v4 bot

適用対象: SDK v4APPLIES TO: SDK v4

この記事では、"プロジェクトの種類を変換せずに"、v3 ContosoHelpdeskChatBot を v4 ボットに変換します。In this article we'll convert the v3 ContosoHelpdeskChatBot into a v4 bot without converting the project type. ボットは、.NET Framework プロジェクトのままになります。It will remain a .NET Framework project. この変換は次の手順に分けられます。This conversion is broken down into these steps:

  1. NuGet パッケージを更新してインストールするUpdate and install NuGet packages
  2. Global.asax.cs ファイルを更新するUpdate your Global.asax.cs file
  3. MessagesController クラスを更新するUpdate your MessagesController class
  4. ダイアログを変換するConvert your dialogs

この変換の結果が、.NET Framework v4 ContosoHelpdeskChatBot です。The result of this conversion is the .NET Framework v4 ContosoHelpdeskChatBot. 新しいプロジェクトで .NET Core v4 ボットに移行するには、「Migrate a .NET v3 bot to a .NET Core v4 bot」(.NET v3 ボットを .NET Core v4 ボットに移行する) を参照してください。To migrate to a .NET Core v4 bot in new project, see Migrate a .NET v3 bot to a .NET Core v4 bot.

Bot Framework SDK v4 は、SDK v3 と同じ基になる REST API に基づいています。Bot Framework SDK v4 is based on the same underlying REST API as SDK v3. ただし、SDK v4 は、開発者がより柔軟にボットを制御できるように、以前のバージョンの SDK をリファクタリングしたものです。However, SDK v4 is a refactoring of the previous version of the SDK to allow developers more flexibility and control over their bots. この SDK の主な変更点は次のとおりです。Major changes in the SDK include:

  • 状態は、状態管理オブジェクトとプロパティ アクセサーを使用して管理されます。State is managed via state management objects and property accessors.
  • ターン ハンドラーの設定とターン ハンドラーへのアクティビティの受け渡しが変更されました。Setting up turn handler and passing activities to it has changed.
  • scorable は存在しなくなりました。Scorables no longer exist. ダイアログに制御を渡す前に、ターン ハンドラー内で "グローバル" コマンドの有無を確認できます。You can check for "global" commands in the turn handler, before passing control to your dialogs.
  • 以前のバージョンのものとは大きく異なる新しい Dialogs ライブラリ。A new Dialogs library that is very different from the one in the previous version. コンポーネント ダイアログ、ウォーターフォール ダイアログ、v4 用の FormFlow ダイアログのコミュニティ実装を使用して、古いダイアログを新しいダイアログ システムに変換する必要があります。You'll need to convert old dialogs to the new dialog system using component and waterfall dialogs and the community implementation of Formflow dialogs for v4.

具体的な変更点の詳細については、.NET SDK v3 と v4 の違いに関する記事をご覧ください。For more information about specific changes, see differences between the v3 and v4 .NET SDK.

NuGet パッケージを更新してインストールするUpdate and install NuGet packages

  1. Microsoft.Bot.Builder.AzureMicrosoft.Bot.Builder.Integration.AspNet.WebApi を最新の安定したバージョンに更新します。Update Microsoft.Bot.Builder.Azure and Microsoft.Bot.Builder.Integration.AspNet.WebApi to the latest stable version.

    Microsoft.Bot.Builder パッケージと Microsoft.Bot.Connector は依存関係であるため、これらのパッケージも更新されます。This will update the Microsoft.Bot.Builder and Microsoft.Bot.Connector packages as well, since they are dependencies.

  2. Microsoft.Bot.Builder.History パッケージを削除します。Delete the Microsoft.Bot.Builder.History package. これは v4 SDK には含まれていません。This is not part of the v4 SDK.

  3. Autofac.WebApi2 を追加します。Add Autofac.WebApi2

    ASP.NET の依存関係挿入に役立つため、これを使用します。We'll use this to help with dependency injection in ASP.NET.

  4. Bot.Builder.Community.Dialogs.Formflow を追加します。Add Bot.Builder.Community.Dialogs.Formflow

    これは、v3 の FormFlow 定義ファイルから v4 のダイアログを構築するためのコミュニティ ライブラリです。This is a community library for building v4 dialogs from v3 Formflow definition files. その依存関係の 1 つとして、Microsoft.Bot.Builder.Dialogs があるので、これも自動的にインストールされます。It has Microsoft.Bot.Builder.Dialogs as one of its dependencies, so this is also installed for us.

ヒント

現在のプロジェクトが .NET Framework 4.6 を対象としている場合は、4.6.1 以降に更新する必要があります。これは、Bot.Builder.Community.Dialogs.Formflow が .NET Standard 2.0 ライブラリであるためです。If your project targets .NET Framework 4.6, you need to update it to 4.6.1 or later because Bot.Builder.Community.Dialogs.Formflow is a .NET Standard 2.0 library. 詳細については、「.NET 実装のサポート」を参照してください。For more information, see .NET implementation support.

この時点でビルドすると、コンパイラ エラーが発生します。If you build at this point, you will get compiler errors. これらは無視してかまいません。You can ignore these. 変換が完了すると、作業コードが作成されます。Once we're finished with our conversion, we'll have working code.

Global.asax.cs ファイルを更新するUpdate your Global.asax.cs file

スキャフォールディングの一部が変更されたので、v4 では状態管理インフラストラクチャの一部を自分で設定する必要があります。Some of the scaffolding has changed, and we have to set up parts of the state management infrastructure ourselves in v4. たとえば、v4 ではボット アダプターを使用して認証を処理し、アクティビティをボット コードに転送します。また、状態プロパティを事前に宣言しておきます。For instance, v4 uses a bot adapter to handle authentication and forward activities to your bot code, and we have declare our state properties up front.

DialogState の状態プロパティを作成します。これは、v4 のダイアログのサポートに必要になります。We'll create a state property for DialogState, which we now need for dialog support in v4. 依存関係挿入を使用して、必要な情報をコントローラーとボット コードに取得します。We'll use dependency injection to get necessary information to the controller and bot code.

Global.asax.cs 内で、次の手順を実行します。In Global.asax.cs:

  1. using ステートメントを更新します。Update the using statements:

    using Autofac;
    using Autofac.Integration.WebApi;
    using ContosoHelpdeskChatBot.Bots;
    using ContosoHelpdeskChatBot.Dialogs;
    using Microsoft.Bot.Builder;
    using Microsoft.Bot.Builder.BotFramework;
    using Microsoft.Bot.Builder.Integration.AspNet.WebApi;
    using Microsoft.Bot.Connector.Authentication;
    using System.Reflection;
    using System.Web.Http;
    
  2. Application_Start メソッドから次の行を削除します。Remove these lines from the Application_Start method:

    BotConfig.UpdateConversationContainer();
    this.RegisterBotModules();
    

    さらに、次の行を挿入します。And, insert this line:

    GlobalConfiguration.Configure(BotConfig.Register);
    
  3. 参照されなくなった RegisterBotModules メソッドを削除します。Remove the RegisterBotModules method, which is no longer referenced.

  4. BotConfig.UpdateConversationContainer メソッドをこの BotConfig.Register メソッドに置換します。ここで、依存関係の挿入をサポートするのに必要なオブジェクトを登録します。Replace the BotConfig.UpdateConversationContainer method with this BotConfig.Register method, where we'll register objects we need to support dependency injection. このボットでは "ユーザー" 状態も "個人的な会話" 状態も使用しないため、会話状態管理オブジェクトのみを作成します。This bot uses neither user nor private conversation state, so we create only the conversation state management object.

    public static void Register(HttpConfiguration config)
    {
        var builder = new ContainerBuilder();
        builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
    
        // The ConfigurationCredentialProvider will retrieve the MicrosoftAppId and
        // MicrosoftAppPassword from Web.config
        builder.RegisterType<ConfigurationCredentialProvider>().As<ICredentialProvider>().SingleInstance();
    
        // Create the Bot Framework Adapter with error handling enabled.
        builder.RegisterType<AdapterWithErrorHandler>().As<IBotFrameworkHttpAdapter>().SingleInstance();
    
        // The Memory Storage used here is for local bot debugging only. When the bot
        // is restarted, everything stored in memory will be gone.
        IStorage dataStore = new MemoryStorage();
    
        // Create Conversation State object.
        // The Conversation State object is where we persist anything at the conversation-scope.
        var conversationState = new ConversationState(dataStore);
        builder.RegisterInstance(conversationState).As<ConversationState>().SingleInstance();
    
        // Register the main dialog, which is injected into the DialogBot class
        builder.RegisterType<RootDialog>().SingleInstance();
    
        // Register the DialogBot with RootDialog as the IBot interface
        builder.RegisterType<DialogBot<RootDialog>>().As<IBot>();
    
        var container = builder.Build();
        var resolver = new AutofacWebApiDependencyResolver(container);
        config.DependencyResolver = resolver;
    }
    

MessagesController クラスを更新するUpdate your MessagesController class

v4 のボットでは、ここでターン ハンドラーが開始されるので、多くの変更を行う必要があります。This is where the bot starts a turn in v4, so this need to change a lot. ボットのターン ハンドラー自体を除き、このほとんどは定型句と考えることができます。Except for the bot's turn handler itself, most of this can be thought of as boilerplate. Controllers\MessagesController.cs ファイル内で、次の手順を実行します。In your Controllers\MessagesController.cs file:

  1. using ステートメントを更新します。Update the using statements:

    using Microsoft.Bot.Builder;
    using Microsoft.Bot.Builder.Integration.AspNet.WebApi;
    using System.Net.Http;
    using System.Threading.Tasks;
    using System.Web.Http;
    
  2. クラスから [BotAuthentication] 属性を削除します。Remove the [BotAuthentication] attribute from the class. v4 では、ボットのアダプターが認証を処理します。In v4, the bot's adapter will handle authentication.

  3. これらのフィールドと、これらのフィールドを初期化するコンス トラクターを追加します。Add these fields and a constructor to initialize them. ASP.NET と Autofac では、依存関係挿入を使用してパラメーター値を取得しますASP.NET and Autofac use dependency injection to get the parameters values. (これに対応するため、アダプター オブジェクトとボット オブジェクトを Global.asax.cs に登録しました)。(We registered adapter and bot objects in Global.asax.cs to support this.)

    private readonly IBotFrameworkHttpAdapter _adapter;
    private readonly IBot _bot;
    
    public MessagesController(IBotFrameworkHttpAdapter adapter, IBot bot)
    {
        _adapter = adapter;
        _bot = bot;
    }
    
  4. Post メソッドの本体を置き換えます。Replace the body of the Post method. アダプターを使用して、ボットのメッセージ ループ (ターン ハンドラー) を呼び出します。We use the adapter to call our bot's message loop (turn handler).

    public async Task<HttpResponseMessage> Post()
    {
        var response = new HttpResponseMessage();
    
        // Delegate the processing of the HTTP POST to the adapter.
        // The adapter will invoke the bot.
        await _adapter.ProcessAsync(Request, response, _bot);
        return response;
    }
    

CancelScorable クラスと GlobalMessageHandlersBotModule クラスを削除するDelete the CancelScorable and GlobalMessageHandlersBotModule classes

v4 には scorable が存在せず、cancel メッセージに対応するようにターン ハンドラーを更新したので、CancelScorable (Dialogs\CancelScorable.cs 内) クラスと GlobalMessageHandlersBotModule クラスを削除できます。Since scorables don't exist in v4 and we've updated the turn handler to react to a cancel message, we can delete the CancelScorable (in Dialogs\CancelScorable.cs) and GlobalMessageHandlersBotModule classes.

ボット クラスを作成するCreate your bot class

v4 では、基本的に、ターン ハンドラーまたはメッセージ ループのロジックはボット ファイル内にあります。In v4, the turn handler or message loop logic is primarily in a bot file. ActivityHandler から派生させます。これは、一般的な種類のアクティビティのハンドラーを定義します。We're deriving from ActivityHandler, which defines handlers for common types of activities.

  1. Bots\DialogBots.cs ファイルを作成します。Create a Bots\DialogBots.cs file.

  2. using ステートメントを更新します。Update the using statements:

    using Microsoft.Bot.Builder;
    using Microsoft.Bot.Builder.Dialogs;
    using Microsoft.Bot.Schema;
    using System.Threading;
    using System.Threading.Tasks;
    
  3. ActivityHandler から DialogBot を派生させて、ダイアログ用のジェネリック パラメーターを追加します。Derive DialogBot from ActivityHandler, and add a generic parameter for the dialog.

    public class DialogBot<T> : ActivityHandler where T : Dialog
    
  4. これらのフィールドと、これらのフィールドを初期化するコンス トラクターを追加します。Add these fields and a constructor to initialize them. 同様に、ASP.NET と Autofac では、依存関係挿入を使用してパラメーター値を取得します。Again, ASP.NET and Autofac use dependency injection to get the parameters values.

    protected readonly Dialog _dialog;
    protected readonly BotState _conversationState;
    
    public DialogBot(ConversationState conversationState, T dialog)
    {
        _conversationState = conversationState;
        _dialog = dialog;
    }
    
  5. OnMessageActivityAsync をオーバーライドしてメイン ダイアログを呼び出します。Override OnMessageActivityAsync to invoke our main dialog. (この後すぐに Run 拡張メソッドを定義します)。(We'll define the Run extension method shortly.)

    protected override async Task OnMessageActivityAsync(
        ITurnContext<IMessageActivity> turnContext,
        CancellationToken cancellationToken)
    {
        // Run the Dialog with the new message Activity.
        await _dialog.Run(
            turnContext,
            _conversationState.CreateProperty<DialogState>("DialogState"),
            cancellationToken);
    }
    
  6. OnTurnAsync をオーバーライドして、ターンの最後に会話状態を保存します。Override OnTurnAsync to save our conversation state at the end of the turn. v4 では、状態を永続化レイヤーに書き込むために、明示的にこれを行う必要があります。In v4, we have to do this explicitly to write state out to the persistence layer. ActivityHandler.OnTurnAsync メソッドでは、受信したアクティビティの種類に基づいて特定のアクティビティ ハンドラー メソッドが呼び出されます。そのため、基本メソッドの呼び出し後の状態を保存します。ActivityHandler.OnTurnAsync method calls specific activity handler methods, based on the type of activity received, so we save state after the call to the base method.

    public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
    {
        await base.OnTurnAsync(turnContext, cancellationToken);
    
        // Save any state changes that might have occured during the turn.
        await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    }
    

Run 拡張メソッドを作成するCreate the Run extension method

拡張メソッドを作成します。この拡張メソッドで、ボットから最小限のコンポーネント ダイアログを実行するために必要なコードを統合します。We're creating an extension method to consolidate the code needed to run a bare component dialog from our bot.

DialogExtensions.cs ファイルを作成し、Run 拡張メソッドを実装します。Create a DialogExtensions.cs file and implement a Run extension method.

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ContosoHelpdeskChatBot
{
    public static class DialogExtensions
    {
        public static async Task Run(
            this Dialog dialog,
            ITurnContext turnContext,
            IStatePropertyAccessor<DialogState> accessor,
            CancellationToken cancellationToken)
        {
            var dialogSet = new DialogSet(accessor);
            dialogSet.Add(dialog);

            var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);

            // Handle 'cancel' interruption
            if (turnContext.Activity.Text.Equals("cancel", StringComparison.InvariantCultureIgnoreCase))
            {
                var reply = turnContext.Activity.CreateReply($"Ok restarting conversation.");
                await turnContext.SendActivityAsync(reply);
                await dialogContext.CancelAllDialogsAsync();
            }

            var results = await dialogContext.ContinueDialogAsync(cancellationToken);
            if (results.Status == DialogTurnStatus.Empty)
            {
                await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken);
            }
        }
    }
}

ダイアログを変換するConvert your dialogs

v4 SDK に移行するために、元のダイアログに多くの変更を加えます。We'll make many changes to the original dialogs to migrate them to the v4 SDK. 現時点では、コンパイラ エラーは気にしないでください。Don't worry about the compiler errors for now. これらは変換が完了したら解決します。These will resolve once we've finished the conversion. 元のコードを必要以上に変更しないようにするために、移行の完了後もコンパイラの警告がいくつか残ります。In the interest of not modifying the original code more than necessary, there will remain some compiler warnings after we've finished the migration.

すべてのダイアログは、v3 の IDialog<object> インターフェイスを実装するのではなく、ComponentDialog から派生します。All of our dialogs will derive from ComponentDialog, instead of implementing the IDialog<object> interface of v3.

このボットには、変換する必要があるダイアログが 4 つあります。This bot has four dialogs that we need to convert:

ダイアログDialog DescriptionDescription
RootDialogRootDialog オプションを提示し、他のダイアログを開始します。Presents options and starts the other dialogs.
InstallAppDialogInstallAppDialog コンピューターにアプリをインストールする要求を処理します。Handles requests to install an app on a machine.
LocalAdminDialogLocalAdminDialog ローカル コンピューターの管理者権限の要求を処理します。Handles requests for local machine admin rights.
ResetPasswordDialogResetPasswordDialog パスワードをリセットする要求を処理します。Handles requests to reset your password.

これらは入力を収集しますが、コンピューター上でこれらの操作を実行するわけではありません。These gather input, but do not perform any of these operations on your machine.

ソリューション全体のダイアログを変更するMake solution-wide dialog changes

  1. ソリューション全体で、出現するすべての IDialog<object>ComponentDialog に置き換えます。For the entire solution, Replace all occurrences of IDialog<object> with ComponentDialog.
  2. ソリューション全体で、出現するすべての IDialogContextDialogContext に置き換えます。For the entire solution, Replace all occurrences of IDialogContext with DialogContext.
  3. 各ダイアログ クラスで、[Serializable] 属性を削除します。For each dialog class, remove the [Serializable] attribute.

ダイアログ内の制御フローとメッセージングは同じ方法で処理されなくなったので、各ダイアログを変換するときにこれを変更する必要があります。Control flow and messaging within dialogs are no longer handled the same way, so we'll need to revise this as we convert each dialog.

操作Operation v3 コードv3 code v4 コードv4 code
ダイアログの開始を処理するHandle the start of your dialog IDialog.StartAsync を実装するImplement IDialog.StartAsync これをウォーターフォール ダイアログの最初のステップにするか、Dialog.BeginDialogAsync を実装するMake this the first step of a waterfall dialog, or implement Dialog.BeginDialogAsync
ダイアログの継続を処理するHandle continuation of your dialog IDialogContext.Wait を呼び出すCall IDialogContext.Wait ウォーターフォール ダイアログにステップを追加するか、Dialog.ContinueDialogAsync を実装するAdd additional steps to a waterfall dialog, or implement Dialog.ContinueDialogAsync
ユーザーにメッセージを送信するSend a message to the user IDialogContext.PostAsync を呼び出すCall IDialogContext.PostAsync ITurnContext.SendActivityAsync を呼び出すCall ITurnContext.SendActivityAsync
子ダイアログを開始するStart a child dialog IDialogContext.Call を呼び出すCall IDialogContext.Call DialogContext.BeginDialogAsync を呼び出すCall DialogContext.BeginDialogAsync
現在のダイアログが完了したことを通知するSignal that the current dialog has completed IDialogContext.Done を呼び出すCall IDialogContext.Done DialogContext.EndDialogAsync を呼び出すCall DialogContext.EndDialogAsync
ユーザーの入力を取得するGet the user's input IAwaitable<IMessageActivity> パラメーターを使用するUse an IAwaitable<IMessageActivity> parameter ウォーターフォール内からプロンプトを使用するか、ITurnContext.Activity を使用するUse a prompt from within a waterfall, or use ITurnContext.Activity

v4 コードに関する注意事項を次に示します。Notes about the v4 code:

  • ダイアログ コード内で、DialogContext.Context プロパティを使用して現在のターン コンテキストを取得します。Within dialog code, use the DialogContext.Context property to get the current turn context.
  • ウォーターフォール ステップには、DialogContext から派生した WaterfallStepContext パラメーターがあります。Waterfall steps have a WaterfallStepContext parameter, which derives from DialogContext.
  • 具象ダイアログ クラスおよびプロンプト クラスはすべて抽象 Dialog クラスから派生します。All concrete dialog and prompt classes derive from the abstract Dialog class.
  • コンポーネント ダイアログを作成するときに ID を割り当てます。You assign an ID when you create a component dialog. ダイアログ セットの各ダイアログには、そのセット内で一意の ID を割り当てる必要があります。Each dialog in a dialog set needs to be assigned an ID unique within that set.

ルート ダイアログを更新するUpdate the root dialog

このボットでは、ルート ダイアログがユーザーに一連のオプションからの選択を要求し、その選択に基づいて子ダイアログを開始します。In this bot, the root dialog prompts the user for a choice from a set of options, and then starts a child dialog based on that choice. 会話の有効期間中、これがループします。This then loops for the lifetime of the conversation.

  • メイン フローはウォーターフォール ダイアログとして設定できます。これは、v4 SDK の新しい概念です。We can set the main flow up as a waterfall dialog, which is a new concept in the v4 SDK. 固定された一連のステップが順番に実行されます。It will run through a fixed set of steps in order. 詳細については、「連続して行われる会話フローの実装」をご覧ください。For more information, see Implement sequential conversation flow.
  • プロンプトは、プロンプト クラスで処理されるようになりました。プロンプト クラスは、入力を要求し、最小限の処理と検証を実行して値を返す短い子ダイアログです。Prompting is now handled through prompt classes, which are short child dialogs that prompt for input, do some minimal processing and validation, and return a value. 詳細については、ダイアログ プロンプトを使用したユーザー入力の収集に関するページをご覧ください。For more information, see gather user input using a dialog prompt.

Dialogs/RootDialog.cs ファイルで、次の手順を実行します。In the Dialogs/RootDialog.cs file:

  1. using ステートメントを更新します。Update the using statements:

    using Microsoft.Bot.Builder;
    using Microsoft.Bot.Builder.Dialogs;
    using Microsoft.Bot.Builder.Dialogs.Choices;
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    
  2. HelpdeskOptions オプションを文字列のリストから選択肢のリストに変換する必要があります。We need to convert HelpdeskOptions options from a list of strings to a list of choices. これは選択プロンプトで使用されます。選択プロンプトは、(リスト内で) 選択された番号、選択された値、または選択肢のいずれかの同義語を有効な入力として受け入れます。This will be used with a choice prompt, which will accept the choice number (in the list), the choice value, or any of the choice's synonyms as valid input.

    private static List<Choice> HelpdeskOptions = new List<Choice>()
        {
            new Choice(InstallAppOption) { Synonyms = new List<string> { "install" } },
            new Choice(ResetPasswordOption) { Synonyms = new List<string> { "password" } },
            new Choice(LocalAdminOption)  { Synonyms = new List<string> { "admin" } }
        };
    
  3. コンストラクターを追加します。Add a constructor. このコードは、次の処理を実行します。This code does the following:

    • ダイアログの各インスタンスの作成時に ID を割り当てます。Each instance of a dialog is assigned an ID when it is created. ダイアログ ID は、ダイアログの追加先となるダイアログ セットに含まれます。The dialog ID is part of the dialog set to which the dialog is being added. ボットが MessageController クラス内でダイアログ オブジェクトを使用して初期化されたことを思い出してください。Recall that the bot was initialized with a dialog object in the MessageController class. ComponentDialog には、一連の独自のダイアログ ID が割り当てられた独自の内部ダイアログ セットがあります。Each ComponentDialog has its own internal dialog set, with its own set of dialog IDs.
    • 選択プロンプトなど、他のダイアログを子ダイアログとして追加します。It adds the other dialogs, including the choice prompt, as child dialogs. ここでは、各ダイアログ ID にクラス名を使用します。Here, we're just using the class name for each dialog ID.
    • 3 ステップのウォーターフォール ダイアログを定義します。It defines a three-step waterfall dialog. このダイアログはこの後すぐに実装します。We'll implement those in a moment.
      • ダイアログでは、まず、ユーザーに実行するタスクを選択するよう求めます。The dialog will first prompt the user to choose a task to perform.
      • 次に、その選択肢に関連付けられている子ダイアログを開始します。Then, start the child dialog associated with that choice.
      • 最後に、ダイアログ自体を再開します。And finally, restart itself.
    • ウォーターフォールの各ステップはデリゲートです。次にそれらを実装します。可能であれば、元のダイアログから既存のコードを取得します。Each step of the waterfall is a delegate, and we'll implement those next, taking existing code from the original dialog where we can.
    • コンポーネント ダイアログを開始すると、その initial dialog が開始されます。When you start a component dialog, it will start its initial dialog. 既定では、これはコンポーネント ダイアログに追加された最初の子ダイアログです。By default, this is the first child dialog added to a component dialog. InitialDialogId プロパティを明示的に設定します。つまり、メインのウォーターフォール ダイアログが、セットに追加する最初のダイアログである必要はありません。We're explicitly setting the InitialDialogId property, which means that the main waterfall dialog does not need to be the first one you add to the set. たとえば、最初にプロンプトを追加する方がよい場合は、実行時の問題を引き起こさずに、これを行うことが可能になります。For instance, if you prefer to add prompts first, this would allow you to do so without causing a run-time issue.
    public RootDialog()
        : base(nameof(RootDialog))
    {
        InitialDialogId = nameof(WaterfallDialog);
        AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
        {
            PromptForOptionsAsync,
            ShowChildDialogAsync,
            ResumeAfterAsync,
        }));
        AddDialog(new InstallAppDialog());
        AddDialog(new LocalAdminDialog());
        AddDialog(new ResetPasswordDialog());
        AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
    }
    
  4. StartAsync メソッドを削除できます。We can delete the StartAsync method. コンポーネント ダイアログが開始されると、"初期" ダイアログが自動的に開始されます。When a component dialog begins, it automatically starts its initial dialog. ここでは、コンストラクターで定義したウォーターフォール ダイアログがこれに該当します。In this case, that's the waterfall dialog we defined in the constructor. このダイアログも、その最初のステップで自動的に開始されます。That also automatically starts at its first step.

  5. MessageReceivedAsync メソッドと ShowOptions メソッドを削除し、それらをウォーターフォールの最初のステップに置き換えます。We will delete the MessageReceivedAsync and ShowOptions methods, and replace them with the first step of our waterfall. この 2 つのメソッドは、ユーザーにあいさつし、利用可能なオプションのいずれかを選択するよう求めていました。These two methods greeted the user and asked them to choose one of the available options.

    • ここでは、選択リストが表示されます。あいさつメッセージとエラー メッセージは、選択プロンプトの呼び出しでオプションとして提供されます。Here you can see the choice list and the greeting and error messages are provided as options in the call to our choice prompt.
    • 選択プロンプトが完了すると、ウォーターフォールは次のステップに進むため、ダイアログで次に呼び出すメソッドを指定する必要はありません。We don't have to specify the next method to call in the dialog, as the waterfall will continue to the next step when the choice prompt completes.
    • 選択プロンプトは、有効な入力を受け取るか、ダイアログ スタック全体がキャンセルされるまでループします。The choice prompt will loop until it receives valid input or the whole dialog stack is canceled.
    private async Task<DialogTurnResult> PromptForOptionsAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        // Prompt the user for a response using our choice prompt.
        return await stepContext.PromptAsync(
            nameof(ChoicePrompt),
            new PromptOptions()
            {
                Choices = HelpdeskOptions,
                Prompt = MessageFactory.Text(GreetMessage),
                RetryPrompt = MessageFactory.Text(ErrorMessage)
            },
            cancellationToken);
    }
    
  6. OnOptionSelected をウォーターフォールの 2 番目のステップに置き換えることができます。We can replace OnOptionSelected with the second step of our waterfall. 置換後も、ユーザーの入力に基づいて子ダイアログを開始します。We still start a child dialog based on the user's input.

    • 選択プロンプトは FoundChoice 値を返します。The choice prompt returns a FoundChoice value. これは、ステップ コンテキストの Result プロパティに示されます。This shows up in the step context's Result property. ダイアログ スタックでは、すべての戻り値をオブジェクトとして扱います。The dialog stack treats all return values as objects. 戻り値がダイアログのいずれかから返されている場合、そのオブジェクトがどの種類の値であるかがわかります。If the return value is from one of your dialogs, then you know what type of value the object is. 各プロンプトの種類で返される値の一覧については、プロンプトの種類に関するセクションをご覧ください。See prompt types for a list what each prompt type returns.
    • 選択プロンプトは例外をスローしないため、try-catch ブロックを削除できます。Since the choice prompt won't throw an exception, we can remove the try-catch block.
    • このメソッドが常に適切な値を返すように、フォールスルーを追加する必要があります。We need to add a fall through so that this method always returns an appropriate value. このコードへの到達は回避する必要がありますが、到達しても、ダイアログを "正常に失敗にする" ことができます。This code should never get hit, but if it does it will allow the dialog to "fail gracefully".
    private async Task<DialogTurnResult> ShowChildDialogAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        // string optionSelected = await userReply;
        var optionSelected = (stepContext.Result as FoundChoice).Value;
    
        switch (optionSelected)
        {
            case InstallAppOption:
                //context.Call(new InstallAppDialog(), this.ResumeAfterOptionDialog);
                //break;
                return await stepContext.BeginDialogAsync(
                    nameof(InstallAppDialog),
                    cancellationToken);
            case ResetPasswordOption:
                //context.Call(new ResetPasswordDialog(), this.ResumeAfterOptionDialog);
                //break;
                return await stepContext.BeginDialogAsync(
                    nameof(ResetPasswordDialog),
                    cancellationToken);
            case LocalAdminOption:
                //context.Call(new LocalAdminDialog(), this.ResumeAfterOptionDialog);
                //break;
                return await stepContext.BeginDialogAsync(
                    nameof(LocalAdminDialog),
                    cancellationToken);
        }
    
        // We shouldn't get here, but fail gracefully if we do.
        await stepContext.Context.SendActivityAsync(
            "I don't recognize that option.",
            cancellationToken: cancellationToken);
        // Continue through to the next step without starting a child dialog.
        return await stepContext.NextAsync(cancellationToken: cancellationToken);
    }
    
  7. 最後に、古い ResumeAfterOptionDialog メソッドをウォーターフォールの最後のステップに置き換えます。Finally, replace the old ResumeAfterOptionDialog method with the last step of our waterfall.

    • 元のダイアログで行っていたように、ダイアログを終了してチケット番号を返すのではなく、スタック上で元のインスタンスをそれ自体の新しいインスタンスに置き換えることでウォーターフォールを再開します。Instead of ending the dialog and returning the ticket number as we did in the original dialog, we're restarting the waterfall by replacing on the stack the original instance with a new instance of itself. これが可能なのは、元のアプリでは常に戻り値 (チケット番号) が無視され、ルート ダイアログが再開されていたためです。We can do this, since the original app always ignored the return value (the ticket number) and restarted the root dialog.
    private async Task<DialogTurnResult> ResumeAfterAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        try
        {
            //var message = await userReply;
            var message = stepContext.Context.Activity;
    
            var ticketNumber = new Random().Next(0, 20000);
            //await context.PostAsync($"Thank you for using the Helpdesk Bot. Your ticket number is {ticketNumber}.");
            await stepContext.Context.SendActivityAsync(
                $"Thank you for using the Helpdesk Bot. Your ticket number is {ticketNumber}.",
                cancellationToken: cancellationToken);
    
            //context.Done(ticketNumber);
        }
        catch (Exception ex)
        {
            // await context.PostAsync($"Failed with message: {ex.Message}");
            await stepContext.Context.SendActivityAsync(
                $"Failed with message: {ex.Message}",
                cancellationToken: cancellationToken);
    
            // In general resume from task after calling a child dialog is a good place to handle exceptions
            // try catch will capture exceptions from the bot framework awaitable object which is essentially "userReply"
            logger.Error(ex);
        }
    
        // Replace on the stack the current instance of the waterfall with a new instance,
        // and start from the top.
        return await stepContext.ReplaceDialogAsync(
            nameof(WaterfallDialog),
            cancellationToken: cancellationToken);
    }
    

アプリのインストール ダイアログを更新するUpdate the install app dialog

アプリのインストール ダイアログでは、いくつかの論理タスクを実行します。これを 4 ステップのウォーターフォール ダイアログとして設定します。The install app dialog performs a few logical tasks, which we'll set up as a 4-step waterfall dialog. 既存のコードをウォーターフォール ステップにどのように組み込むかは、ダイアログごとの論理的な作業です。How you factor existing code into waterfall steps is a logical exercise for each dialog. ステップごとに、そのコードが含まれていた元のメソッドに留意します。For each step, the original method the code came from is noted.

  1. ユーザーに検索文字列を要求します。Asks the user for a search string.
  2. 一致候補をデータベースに照会します。Queries a database for potential matches.
    • 一致候補が 1 つの場合は、これを選択して続行します。If there is one hit, select this and continue.
    • 一致候補が複数ある場合は、ユーザーにいずれかを選択するよう求めます。If there are multiple hits, it asks the user to choose one.
    • 一致候補がない場合、ダイアログは終了します。If there are no hits, the dialog exits.
  3. ユーザーにアプリをインストールするコンピューター名の入力を求めます。Asks the user for a machine to install the app on.
  4. 情報をデータベースに書き込み、確認メッセージを送信します。Writes the information to a database and sends a confirmation message.

Dialogs/InstallAppDialog.cs ファイルで、次の手順を実行します。In the Dialogs/InstallAppDialog.cs file:

  1. using ステートメントを更新します。Update the using statements:

    using ContosoHelpdeskChatBot.Models;
    using Microsoft.Bot.Builder;
    using Microsoft.Bot.Builder.Dialogs;
    using Microsoft.Bot.Builder.Dialogs.Choices;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    
  2. 収集した情報を追跡するために使用するキーの定数を定義します。Define a constant for the key we'll use to track collected information.

    // Set up keys for managing collected information.
    private const string InstallInfo = "installInfo";
    
  3. コンストラクターを追加し、コンポーネントのダイアログ セットを初期化します。Add a constructor and initialize the component's dialog set.

    public InstallAppDialog()
        : base(nameof(InstallAppDialog))
    {
        // Initialize our dialogs and prompts.
        InitialDialogId = nameof(WaterfallDialog);
        AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] {
            GetSearchTermAsync,
            ResolveAppNameAsync,
            GetMachineNameAsync,
            SubmitRequestAsync,
        }));
        AddDialog(new TextPrompt(nameof(TextPrompt)));
        AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
    }
    
  4. StartAsync をウォーターフォールの最初のステップに置き換えることができます。We can replace StartAsync with the first step of our waterfall.

    • 状態を自分で管理する必要があるので、ダイアログの状態でアプリのインストール オブジェクトを追跡します。We have to manage state ourselves, so we'll track the install app object in dialog state.
    • ユーザーに入力を求めるメッセージは、プロンプトの呼び出しでのオプションになります。The message asking the user for input becomes an option in the call to the prompt.
    private async Task<DialogTurnResult> GetSearchTermAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        // Create an object in dialog state in which to track our collected information.
        stepContext.Values[InstallInfo] = new InstallApp();
    
        // Ask for the search term.
        return await stepContext.PromptAsync(
            nameof(TextPrompt),
            new PromptOptions
            {
                Prompt = MessageFactory.Text("Ok let's get started. What is the name of the application? "),
            },
            cancellationToken);
    }
    
  5. appNameAsyncmultipleAppsAsync をウォーターフォールの 2 番目のステップに置き換えることができます。We can replace appNameAsync and multipleAppsAsync with the second step of our waterfall.

    • ユーザーの最後のメッセージを単に確認するのではなく、プロンプトの結果を取得します。We're getting the prompt result now, instead of just looking at the user's last message.
    • データベース クエリと if ステートメントは、appNameAsync の場合と同様に構成されています。The database query and if statements are organized the same as in appNameAsync. if ステートメントの各ブロック内のコードは、v4 ダイアログで動作するように更新されました。The code in each block of the if statement has been updated to work with v4 dialogs.
      • 一致候補が 1 つの場合は、ダイアログの状態を更新し、次のステップに進みます。If we have one hit, we'll update dialog state and continue with the next step.
      • 一致候補が複数ある場合は、選択プロンプトを使用して、オプションの一覧から選択するようユーザーに求めます。If we have multiple hits, we'll use our choice prompt to ask the user to choose from the list of options. つまり、multipleAppsAsync は削除できます。This means we can just delete multipleAppsAsync.
      • 一致候補がない場合は、このダイアログを終了し、ルート ダイアログに null を返します。If we have no hits, we'll end this dialog and return null to the root dialog.
    private async Task<DialogTurnResult> ResolveAppNameAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        // Get the result from the text prompt.
        var appname = stepContext.Result as string;
    
        // Query the database for matches.
        var names = await this.GetAppsAsync(appname);
    
        if (names.Count == 1)
        {
            // Get our tracking information from dialog state and add the app name.
            var install = stepContext.Values[InstallInfo] as InstallApp;
            install.AppName = names.First();
    
            return await stepContext.NextAsync();
        }
        else if (names.Count > 1)
        {
            // Ask the user to choose from the list of matches.
            return await stepContext.PromptAsync(
                nameof(ChoicePrompt),
                new PromptOptions
                {
                    Prompt = MessageFactory.Text("I found the following applications. Please choose one:"),
                    Choices = ChoiceFactory.ToChoices(names),
                },
                cancellationToken);
        }
        else
        {
            // If no matches, exit this dialog.
            await stepContext.Context.SendActivityAsync(
                $"Sorry, I did not find any application with the name '{appname}'.",
                cancellationToken: cancellationToken);
    
            return await stepContext.EndDialogAsync(null, cancellationToken);
        }
    }
    
  6. appNameAsync では、クエリの解決後にユーザーにコンピューター名の入力も求めていました。appNameAsync also asked the user for their machine name after it resolved the query. ウォーターフォールの次のステップに、ロジックのこの部分を取り込みます。We'll capture that portion of the logic in the next step of the waterfall.

    • 繰り返しますが、v4 では状態を自分で管理する必要があります。Again, in v4 we have to manage state ourselves. ここで唯一注意が必要なことは、前のステップで 2 つの異なるロジック分岐を通過してこのステップに到達できることです。The only tricky thing here is that we can get to this step through two different logic branches in the previous step.
    • 今回は別のオプションを指定して、以前と同じテキスト プロンプトを使ってユーザーにコンピューター名の入力を求めます。We'll ask the user for a machine name using the same text prompt as before, just supplying different options this time.
    private async Task<DialogTurnResult> GetMachineNameAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        // Get the tracking info. If we don't already have an app name,
        // Then we used the choice prompt to get it in the previous step.
        var install = stepContext.Values[InstallInfo] as InstallApp;
        if (install.AppName is null)
        {
            install.AppName = (stepContext.Result as FoundChoice).Value;
        }
    
        // We now need the machine name, so prompt for it.
        return await stepContext.PromptAsync(
            nameof(TextPrompt),
            new PromptOptions
            {
                Prompt = MessageFactory.Text(
                    $"Found {install.AppName}. What is the name of the machine to install application?"),
            },
            cancellationToken);
    }
    
  7. machineNameAsync のロジックは、ウォーターフォールの最後のステップにまとめられています。The logic from machineNameAsync is wrapped up in the final step of our waterfall.

    • テキスト プロンプトの結果からコンピューター名を取得し、ダイアログの状態を更新します。We retrieve the machine name from the text prompt result and update dialog state.
    • サポート コードは別のプロジェクトにあるため、データベースを更新する呼び出しを削除します。We are removing the call to update the database, as the supporting code is in a different project.
    • その後、成功メッセージをユーザーに送信し、ダイアログを終了します。Then we're sending the success message to the user and ending the dialog.
    private async Task<DialogTurnResult> SubmitRequestAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        var install = default(InstallApp);
        if (stepContext.Reason != DialogReason.CancelCalled)
        {
            // Get the tracking info and add the machine name.
            install = stepContext.Values[InstallInfo] as InstallApp;
            install.MachineName = stepContext.Context.Activity.Text;
    
            //TODO: Save to this information to the database.
        }
    
        await stepContext.Context.SendActivityAsync(
            $"Great, your request to install {install.AppName} on {install.MachineName} has been scheduled.",
            cancellationToken: cancellationToken);
    
        return await stepContext.EndDialogAsync(null, cancellationToken);
    }
    
  8. データベースの呼び出しをシミュレートするために、データベースではなく静的リストを照会するように getAppsAsync をモックアップします。To simulate the database call, we mock up getAppsAsync to query a static list, instead of the database.

    private async Task<List<string>> GetAppsAsync(string Name)
    {
        var names = new List<string>();
    
        // Simulate querying the database for applications that match.
        return (from app in AppMsis
                where app.ToLower().Contains(Name.ToLower())
                select app).ToList();
    }
    
    // Example list of app names in the database.
    private static readonly List<string> AppMsis = new List<string>
    {
        "µTorrent 3.5.0.44178",
        "7-Zip 17.1",
        "Ad-Aware 9.0",
        "Adobe AIR 2.5.1.17730",
        "Adobe Flash Player (IE) 28.0.0.105",
        "Adobe Flash Player (Non-IE) 27.0.0.130",
        "Adobe Reader 11.0.14",
        "Adobe Shockwave Player 12.3.1.201",
        "Advanced SystemCare Personal 11.0.3",
        "Auslogics Disk Defrag 3.6",
        "avast! 4 Home Edition 4.8.1351",
        "AVG Anti-Virus Free Edition 9.0.0.698",
        "Bonjour 3.1.0.1",
        "CCleaner 5.24.5839",
        "Chmod Calculator 20132.4",
        "CyberLink PowerDVD 17.0.2101.62",
        "DAEMON Tools Lite 4.46.1.328",
        "FileZilla Client 3.5",
        "Firefox 57.0",
        "Foxit Reader 4.1.1.805",
        "Google Chrome 66.143.49260",
        "Google Earth 7.3.0.3832",
        "Google Toolbar (IE) 7.5.8231.2252",
        "GSpot 2701.0",
        "Internet Explorer 903235.0",
        "iTunes 12.7.0.166",
        "Java Runtime Environment 6 Update 17",
        "K-Lite Codec Pack 12.1",
        "Malwarebytes Anti-Malware 2.2.1.1043",
        "Media Player Classic 6.4.9.0",
        "Microsoft Silverlight 5.1.50907",
        "Mozilla Thunderbird 57.0",
        "Nero Burning ROM 19.1.1005",
        "OpenOffice.org 3.1.1 Build 9420",
        "Opera 12.18.1873",
        "Paint.NET 4.0.19",
        "Picasa 3.9.141.259",
        "QuickTime 7.79.80.95",
        "RealPlayer SP 12.0.0.319",
        "Revo Uninstaller 1.95",
        "Skype 7.40.151",
        "Spybot - Search & Destroy 1.6.2.46",
        "SpywareBlaster 4.6",
        "TuneUp Utilities 2009 14.0.1000.353",
        "Unlocker 1.9.2",
        "VLC media player 1.1.6",
        "Winamp 5.56 Build 2512",
        "Windows Live Messenger 2009 16.4.3528.331",
        "WinPatrol 2010 31.0.2014",
        "WinRAR 5.0",
    };
    

ローカル管理者ダイアログを更新するUpdate the local admin dialog

v3 では、このダイアログでユーザーにあいさつし、FormFlow ダイアログを開始して、結果をデータベースに保存していました。In v3, this dialog greeted the user, started the Formflow dialog, and then saved the result off to a database. これは 2 ステップのウォーターフォールに簡単に変換できます。This translates easily into a two-step waterfall.

  1. using ステートメントを更新します。Update the using statements. このダイアログには、v3 の FormFlow ダイアログが含まれています。Note that this dialog includes a v3 Formflow dialog. v4 では、FormFlow コミュニティ ライブラリを使用できます。In v4 we can use the community Formflow library.

    using Bot.Builder.Community.Dialogs.FormFlow;
    using ContosoHelpdeskChatBot.Models;
    using Microsoft.Bot.Builder.Dialogs;
    using System.Threading;
    using System.Threading.Tasks;
    
  2. 結果はダイアログの状態で提供されるので、LocalAdmin のインスタンス プロパティを削除できます。We can remove the instance property for LocalAdmin, as the result will be available in dialog state.

  3. コンストラクターを追加し、コンポーネントのダイアログ セットを初期化します。Add a constructor and initialize the component's dialog set. FormFlow ダイアログも同じ方法で作成されます。The Formflow dialog is created in the same way. コンストラクター内でコンポーネントのダイアログ セットに追加するだけです。We're just adding it to the dialog set of our component in the constructor.

    public LocalAdminDialog() : base(nameof(LocalAdminDialog))
    {
        InitialDialogId = nameof(WaterfallDialog);
        AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
        {
            BeginFormflowAsync,
            SaveResultAsync,
        }));
        AddDialog(FormDialog.FromForm(BuildLocalAdminForm, FormOptions.PromptInStart));
    }
    
  4. StartAsync をウォーターフォールの最初のステップに置き換えることができます。We can replace StartAsync with the first step of our waterfall. コンストラクター内で FormFlow を既に作成しているので、他の 2 つのステートメントはこれに変換されます。We already created the Formflow in the constructor, and the other two statements translate to this. なお、FormBuilder では、生成されたダイアログの ID としてモデルの種類の名前が割り当てられますが、このモデルでは LocalAdminPrompt がそれにあたります。Note that FormBuilder assigns the model's type name as the ID of the generated dialog, which is LocalAdminPrompt for this model.

    private async Task<DialogTurnResult> BeginFormflowAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        await stepContext.Context.SendActivityAsync("Great I will help you request local machine admin.");
    
        // Begin the Formflow dialog.
        return await stepContext.BeginDialogAsync(
            nameof(LocalAdminPrompt),
            cancellationToken: cancellationToken);
    }
    
  5. ResumeAfterLocalAdminFormDialog をウォーターフォールの 2 番目のステップに置き換えることができます。We can replace ResumeAfterLocalAdminFormDialog with the second step of our waterfall. インスタンス プロパティからではなく、ステップ コンテキストから戻り値を取得する必要があります。We have to get the return value from the step context, instead of from an instance property.

    private async Task<DialogTurnResult> SaveResultAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        // Get the result from the Formflow dialog when it ends.
        if (stepContext.Reason != DialogReason.CancelCalled)
        {
            var admin = stepContext.Result as LocalAdminPrompt;
    
            //TODO: Save to this information to the database.
        }
    
        return await stepContext.EndDialogAsync(null, cancellationToken);
    }
    
  6. FormFlow でインスタンス プロパティを更新しない点を除き、BuildLocalAdminForm の変更はほとんどありません。BuildLocalAdminForm remains largely the same, except we don't have the Formflow update the instance property.

    // Nearly the same as before.
    private IForm<LocalAdminPrompt> BuildLocalAdminForm()
    {
        // Here's an example of how validation can be used with FormBuilder.
        return new FormBuilder<LocalAdminPrompt>()
            .Field(nameof(LocalAdminPrompt.MachineName),
            validate: async (state, value) =>
            {
                var result = new ValidateResult { IsValid = true, Value = value };
                //add validation here
    
                //this.admin.MachineName = (string)value;
                return result;
            })
            .Field(nameof(LocalAdminPrompt.AdminDuration),
            validate: async (state, value) =>
            {
                var result = new ValidateResult { IsValid = true, Value = value };
                //add validation here
    
                //this.admin.AdminDuration = Convert.ToInt32((long)value) as int?;
                return result;
            })
            .Build();
    }
    

パスワードのリセット ダイアログを更新するUpdate the reset password dialog

v3 では、このダイアログでユーザーにあいさつし、パス コードでユーザーを承認して、失敗にするかまたは FormFlow ダイアログを開始した後、パスワードをリセットしていました。In v3, this dialog greeted the user, authorized the user with a pass code, failed out or started the Formflow dialog, and then reset the password. これもウォーターフォールに適切に変換できます。This still translates well into a waterfall.

  1. using ステートメントを更新します。Update the using statements. このダイアログには、v3 の FormFlow ダイアログが含まれています。Note that this dialog includes a v3 Formflow dialog. v4 では、FormFlow コミュニティ ライブラリを使用できます。In v4 we can use the community Formflow library.

    using Bot.Builder.Community.Dialogs.FormFlow;
    using ContosoHelpdeskChatBot.Models;
    using Microsoft.Bot.Builder.Dialogs;
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
  2. コンストラクターを追加し、コンポーネントのダイアログ セットを初期化します。Add a constructor and initialize the component's dialog set. FormFlow ダイアログも同じ方法で作成されます。The Formflow dialog is created in the same way. コンストラクター内でコンポーネントのダイアログ セットに追加するだけです。We're just adding it to the dialog set of our component in the constructor.

    public ResetPasswordDialog()
        : base(nameof(ResetPasswordDialog))
    {
        InitialDialogId = nameof(WaterfallDialog);
        AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
        {
            BeginFormflowAsync,
            ProcessRequestAsync,
        }));
        AddDialog(FormDialog.FromForm(BuildResetPasswordForm, FormOptions.PromptInStart));
    }
    
  3. StartAsync をウォーターフォールの最初のステップに置き換えることができます。We can replace StartAsync with the first step of our waterfall. ここでは、コンストラクター内で FormFlow を既に作成しました。We already created the Formflow in the constructor. それ以外は、v3 の呼び出しから v4 の対応する呼び出しへの変換だけを行い、同じロジックを維持します。Otherwise, we're keeping the same logic, just translating the v3 calls to their v4 equivalents.

    private async Task<DialogTurnResult> BeginFormflowAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        await stepContext.Context.SendActivityAsync("Alright I will help you create a temp password.");
    
        // Check the passcode and fail out or begin the Formflow dialog.
        if (SendPassCode(stepContext))
        {
            return await stepContext.BeginDialogAsync(
                nameof(ResetPasswordPrompt),
                cancellationToken: cancellationToken);
        }
        else
        {
            //here we can simply fail the current dialog because we have root dialog handling all exceptions
            throw new Exception("Failed to send SMS. Make sure email & phone number has been added to database.");
        }
    }
    
  4. sendPassCode は主に演習として残してあります。sendPassCode is left mainly as an exercise. 元のコードはコメント アウトされています。このメソッドは true を返すだけです。The original code is commented out, and the method just returns true. また、メール アドレスは、元のボットで使用されていなかったので削除することもできます。Also, we can remove the email address again, as it wasn't used in the original bot.

    private bool SendPassCode(DialogContext context)
    {
        //bool result = false;
    
        //Recipient Id varies depending on channel
        //refer ChannelAccount class https://docs.botframework.com/en-us/csharp/builder/sdkreference/dd/def/class_microsoft_1_1_bot_1_1_connector_1_1_channel_account.html#a0b89cf01fdd73cbc00a524dce9e2ad1a
        //as well as Activity class https://docs.botframework.com/en-us/csharp/builder/sdkreference/dc/d2f/class_microsoft_1_1_bot_1_1_connector_1_1_activity.html
        //int passcode = new Random().Next(1000, 9999);
        //Int64? smsNumber = 0;
        //string smsMessage = "Your Contoso Pass Code is ";
        //string countryDialPrefix = "+1";
    
        // TODO: save PassCode to database
        //using (var db = new ContosoHelpdeskContext())
        //{
        //    var reset = db.ResetPasswords.Where(r => r.EmailAddress == email).ToList();
        //    if (reset.Count >= 1)
        //    {
        //        reset.First().PassCode = passcode;
        //        smsNumber = reset.First().MobileNumber;
        //        result = true;
        //    }
    
        //    db.SaveChanges();
        //}
    
        // TODO: send passcode to user via SMS.
        //if (result)
        //{
        //    result = Helper.SendSms($"{countryDialPrefix}{smsNumber.ToString()}", $"{smsMessage} {passcode}");
        //}
    
        //return result;
        return true;
    }
    
  5. BuildResetPasswordForm に変更はありません。BuildResetPasswordForm has no changes.

  6. ResumeAfterResetPasswordFormDialog をウォーターフォールの 2 番目のステップに置き換えることができます。戻り値はステップ コンテキストから取得します。We can replace ResumeAfterResetPasswordFormDialog with the second step of our waterfall, and we'll get the return value from the step context. 元のダイアログで何にも使用されていなかった電子メール アドレスを削除しました。また、データベースに照会する代わりにダミーの結果を用意しました。We've removed the email address that the original dialog didn't do anything with, and we've provided a dummy result instead of querying the database. v3 の呼び出しから v4 の対応する呼び出しへの変換だけを行い、同じロジックを維持します。We're keeping the same logic, just translating the v3 calls to their v4 equivalents.

    private async Task<DialogTurnResult> ProcessRequestAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        // Get the result from the Formflow dialog when it ends.
        if (stepContext.Reason != DialogReason.CancelCalled)
        {
            var prompt = stepContext.Result as ResetPasswordPrompt;
            int? passcode;
    
            // TODO: Retrieve the passcode from the database.
            passcode = 1111;
    
            if (prompt.PassCode == passcode)
            {
                string temppwd = "TempPwd" + new Random().Next(0, 5000);
                await stepContext.Context.SendActivityAsync(
                    $"Your temp password is {temppwd}",
                    cancellationToken: cancellationToken);
            }
        }
    
        return await stepContext.EndDialogAsync(null, cancellationToken);
    }
    

必要に応じてモデルを更新するUpdate models as necessary

FormFlow ライブラリを参照する一部のモデルで、using ステートメントを更新する必要があります。We need to update using statements in some of the models that reference the Formflow library.

  1. LocalAdminPrompt で、これらを次のように変更します。In LocalAdminPrompt, change them to this:

    using Bot.Builder.Community.Dialogs.FormFlow;
    
  2. ResetPasswordPrompt で、これらを次のように変更します。In ResetPasswordPrompt, change them to this:

    using Bot.Builder.Community.Dialogs.FormFlow;
    using System;
    

Web.config の更新Update Web.config

MicrosoftAppIdMicrosoftAppPassword の構成キーをコメント アウトします。Comment out the configuration keys for MicrosoftAppId and MicrosoftAppPassword. これにより、これらの値をエミュレーターに提供しなくても、ローカルでボットをデバッグできるようになります。This will allow you to debug the bot locally without needing to provide these values to the Emulator.

ボットを実行してエミュレーターでテストするRun and test your bot in the Emulator

この時点で、ボットをローカルの IIS で実行し、エミュレーターを使用してボットに接続できます。At this point, we should be able to run the bot locally in IIS and attach to it with the Emulator.

  1. IIS でボットを実行します。Run the bot in IIS.
  2. エミュレーターを起動し、ボットのエンドポイント (例: ) に接続します http://localhost:3978/api/messagesStart the Emulator and connect to the bot's endpoint (for example, http://localhost:3978/api/messages).
    • 初めてボットを実行する場合は、 [File](ファイル) > [New Bot](新しいボット) をクリックして、画面の指示に従います。If this is the first time you are running the bot then click File > New Bot and follow the instructions on screen. それ以外の場合は、 [File](ファイル) > [Open Bot](ボットを開く) をクリックして既存のボットを開きます。Otherwise, click File > Open Bot to open an existing bot.
    • 構成のポート設定を再確認します。Double check your port settings in the configuration. たとえば、ブラウザーで http://localhost:3979/ にアクセスしてボットを開いている場合は、エミュレーターでボットのエンドポイントを http://localhost:3979/api/messages に設定します。For example, if the bot opened in your browser to http://localhost:3979/, then in the Emulator, set the bot's endpoint to http://localhost:3979/api/messages.
  3. 4 つのダイアログはすべて機能するはずです。ウォーターフォール ステップにブレークポイントを設定して、これらのポイントでダイアログ コンテキストとダイアログの状態を確認できます。All four dialogs should work, and you can set breakpoints in the waterfall steps to check what the dialog context and dialog state is at these points.

その他のリソースAdditional resources

v4 の概念トピックv4 conceptual topics:

v4 の使用方法のトピックv4 how-to topics: