將 .NET v3 聊天機器人遷移至 .NET Framework v4 聊天機器人Migrate a .NET v3 bot to a .NET Framework v4 bot

適用于: SDK v4APPLIES TO: SDK v4

我們在本文中將 v3 ContosoHelpdeskChatBot 轉換為 v4 Bot,而 不需轉換專案類型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 ContosoHelpdeskChatBotThe result of this conversion is the .NET Framework v4 ContosoHelpdeskChatBot. 若要遷移至新專案中的 .NET Core v4 聊天機器人,請參閱將 .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 的重構,讓開發人員對於其 Bot 有更多的彈性和控制權。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.
  • 可評分項目已不存在。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.

如需特定變更的詳細資訊,請參閱 v3 和 v4 .NET SDK 之間差異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.BuilderMicrosoft.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.WebApi2Add Autofac.WebApi2

    我們將使用它來協助在 ASP.NET 中插入相依性。We'll use this to help with dependency injection in ASP.NET.

  4. 新增 Bot.Builder.Community.Dialogs.FormflowAdd Bot.Builder.Community.Dialogs.Formflow

    這是一個社群程式庫,用於從 v3 Formflow 定義檔建置 v4 對話。This is a community library for building v4 dialogs from v3 Formflow definition files. 它以 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

某些 Scaffolding 已變更,而且我們必須自行在 v4 中設定部份的狀態管理基礎結構。Some of the scaffolding has changed, and we have to set up parts of the state management infrastructure ourselves in v4. 例如,v4 會使用 Bot 配接器來處理驗證並將活動轉送到 Bot 程式碼,而我們會事先宣告我們的狀態屬性。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. 我們將使用相依性插入來取得控制器和 Bot 程式碼的必要資訊。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.Register 方法取代 BotConfig.UpdateConversationContainer 方法,我們將在其中註冊支援相依性插入所需的物件。Replace the BotConfig.UpdateConversationContainer method with this BotConfig.Register method, where we'll register objects we need to support dependency injection. 此 Bot 不採用 使用者私人交談 狀態,因此我們只建立交談狀態管理物件。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 中 Bot 起始回合的地方,因此需求大幅變更。This is where the bot starts a turn in v4, so this need to change a lot. 除了 Bot 的回合處理常式本身,可將大部分的內容視為重複使用文字。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 中,Bot 的配接器會處理驗證。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 中註冊配接器和 Bot 物件。)(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. 我們可以使用配接器來呼叫我們的 Bot 訊息迴圈 (回合處理常式)。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 中不存在可評分項目,而且我們已更新回合處理常式以回應 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.

建立您的 Bot 類別Create your bot class

在 v4 中,回合處理常式或訊息迴圈邏輯是 Bot 檔案中的主要項目。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);
    }
    

建立執行擴充方法Create the Run extension method

我們將建立擴充方法來合併從 Bot 執行裸機元件對話所需的程式碼。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.

我們所有的對話都將衍生自 ComponentDialog,而不會實作 v3 的 IDialog<object> 介面。All of our dialogs will derive from ComponentDialog, instead of implementing the IDialog<object> interface of v3.

此 Bot 有四個需要我們轉換的對話: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. 針對整個方案,以 ComponentDialog 取代所有出現的 IDialog<object>For the entire solution, Replace all occurrences of IDialog<object> with ComponentDialog.
  2. 針對整個方案,以 DialogContext 取代所有出現的 IDialogContextFor 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.StartAsyncImplement IDialog.StartAsync 讓此項成為瀑布式對話的第一個步驟,或實作 Dialog.BeginDialogAsyncMake this the first step of a waterfall dialog, or implement Dialog.BeginDialogAsync
處理對話的接續Handle continuation of your dialog 呼叫 IDialogContext.WaitCall IDialogContext.Wait 在瀑布式對話中新增其他步驟,或實作 Dialog.ContinueDialogAsyncAdd additional steps to a waterfall dialog, or implement Dialog.ContinueDialogAsync
將訊息傳送給使用者Send a message to the user 呼叫 IDialogContext.PostAsyncCall IDialogContext.PostAsync 呼叫 ITurnContext.SendActivityAsyncCall ITurnContext.SendActivityAsync
開始子對話Start a child dialog 呼叫 IDialogContext.CallCall IDialogContext.Call 呼叫 DialogContext.BeginDialogAsyncCall DialogContext.BeginDialogAsync
表示目前對話已完成的訊號Signal that the current dialog has completed 呼叫 IDialogContext.DoneCall IDialogContext.Done 呼叫 DialogContext.EndDialogAsyncCall DialogContext.EndDialogAsync
取得使用者的輸入Get the user's input 使用 IAwaitable<IMessageActivity> 參數Use an IAwaitable<IMessageActivity> parameter 使用瀑布中的提示,或使用 ITurnContext.ActivityUse 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.
  • 瀑布步驟具有 WaterfallStepContext 參數,其衍生自 DialogContextWaterfall steps have a WaterfallStepContext parameter, which derives from DialogContext.
  • 所有具體的對話和提示類別均衍生自抽象的 Dialog 類別。All concrete dialog and prompt classes derive from the abstract Dialog class.
  • 您會在建立元件對話時指派識別碼。You assign an ID when you create a component dialog. 對話集中的每個對話都需要被指派該集合內唯一的識別碼。Each dialog in a dialog set needs to be assigned an ID unique within that set.

更新根對話Update the root dialog

在此 Bot 中,根對話會提示使用者從一組選項中進行選擇,然後根據該選擇開始進行子對話。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:

    • 每個對話執行個體會在建立時被指派識別碼。Each instance of a dialog is assigned an ID when it is created. 對話識別碼是對話要新增到其中的對話集的一部分。The dialog ID is part of the dialog set to which the dialog is being added. 還記得 Bot 已透過 MessageController 類別中的對話方塊物件初始化。Recall that the bot was initialized with a dialog object in the MessageController class. 每個 ComponentDialog 都有自己的內部對話集,以及自己的對話識別碼集。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. 在此,我們只會使用每個對話識別碼的類別名稱。Here, we're just using the class name for each dialog ID.
    • 它會定義包含三個步驟的瀑布式對話。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.
    • 當您啟動元件對話時,就會啟動其「初始對話」。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. 我們將會刪除 MessageReceivedAsyncShowOptions 方法,並以瀑布的第一個步驟取代。We will delete the MessageReceivedAsync and ShowOptions methods, and replace them with the first step of our waterfall. 這兩種方法會先映入使用者的眼簾,並要求他們選擇其中一個可用的選項。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. 我們可以使用瀑布的第二個步驟取代 OnOptionSelectedWe 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.
    • 如果有一項命中,請選取此項目並繼續執行。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. 我們可以使用瀑布的第一個步驟取代 StartAsyncWe 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. 我們可以使用瀑布的第二個步驟取代 appNameAsyncmultipleAppsAsyncWe 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.
      • 如果我們有一次命中,我們將會更新對話狀態並繼續進行下一個步驟。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. 這表示我們可以刪除 multipleAppsAsyncThis 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. 唯一比較麻煩的事,就是我們可以透過上一個步驟中的兩個不同邏輯分支來抵達此步驟。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. 這可輕鬆地轉換成包含兩個步驟的瀑布。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. 我們可以使用瀑布的第一個步驟取代 StartAsyncWe can replace StartAsync with the first step of our waterfall. 我們已經在建構函式中建立 Formflow,而其他兩個陳述式會轉譯為此陳述式。We already created the Formflow in the constructor, and the other two statements translate to this. 請注意,FormBuilder 會將模型類型名稱指派為所產生對話方塊的識別碼,而此模型的對話是 LocalAdminPromptNote 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. 我們可以使用瀑布的第二個步驟取代 ResumeAfterLocalAdminFormDialogWe 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. BuildLocalAdminForm 大致維持相同,但我們沒有讓 Formflow 更新執行個體屬性。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. 我們可以使用瀑布的第一個步驟取代 StartAsyncWe 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. 此外,原始 Bot 中並未使用電子郵件地址,所以可再次予以移除。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,並將從步驟內容中取得傳回值。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.configUpdate Web.config

註解排除 MicrosoftAppIdMicrosoftAppPassword 的組態金鑰。Comment out the configuration keys for MicrosoftAppId and MicrosoftAppPassword. 這可讓您在本機進行 Bot 偵錯,而不需將這些值提供給模擬器。This will allow you to debug the bot locally without needing to provide these values to the Emulator.

在模擬器中執行並測試您的 BotRun and test your bot in the Emulator

此時,我們應該能夠在 IIS 中本機執行 Bot,然後將它與模擬器連結。At this point, we should be able to run the bot locally in IIS and attach to it with the Emulator.

  1. 在 IIS 中執行 Bot。Run the bot in IIS.
  2. 啟動模擬器並連接到 bot 的端點 (例如 http://localhost:3978/api/messages) 。Start the Emulator and connect to the bot's endpoint (for example, http://localhost:3978/api/messages).
    • 如果這是您第一次執行 Bot,請按一下 [檔案] > [新的 Bot],並遵循畫面上的指示。If this is the first time you are running the bot then click File > New Bot and follow the instructions on screen. 否則,請按一下 [檔案] > [開啟 Bot] 以開啟現有的 Bot。Otherwise, click File > Open Bot to open an existing bot.
    • 在組態中再次檢查連接埠設定。Double check your port settings in the configuration. 例如,如果 Bot 在瀏覽器中開啟至 http://localhost:3979/,則在模擬器中,將 Bot 的端點設定為 http://localhost:3979/api/messagesFor 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. 四個對話應該都可以運作,而且您可以在瀑布步驟中設定中斷點,以檢查哪個對話內容和對話狀態是在這些點上。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: