管理長時間執行作業Manage a long-running operation

適用于: SDK v4APPLIES TO: SDK v4

長時間執行作業的適當處理是健全 bot 的重要層面。Proper handling of long-running operations is an important aspect of a robust bot. 當 Azure Bot Service 將活動從通道傳送至您的 bot 時,Bot 預期會快速處理活動。When the Azure Bot Service sends an activity to your bot from a channel, the bot is expected to process the activity quickly. 如果 bot 未在10到15秒內完成此作業,則視通道而定,Azure Bot Service 將會超時並回報給用戶端 a 504:GatewayTimeout (如 bot 的運作方式所述)。If the bot does not complete the operation within 10 to 15 seconds, depending on the channel, the Azure Bot Service will timeout and report back to the client a 504:GatewayTimeout, as described in How bots work.

本文說明如何使用外部服務來執行作業,並在 bot 完成時通知 bot。This article describes how to use an external service to execute the operation and to notify the bot when it has completed.

必要條件Prerequisites

關於此範例About this sample

本文一開始會使用多回合提示範例 bot,並新增程式碼來執行長時間執行的作業。This article begins with the multi-turn prompt sample bot and adds code for performing a long-running operations. 它也會示範如何在作業完成後回應使用者。It also demonstrates how to respond to a user after the operation has completed. 在更新的範例中:In the updated sample:

  • Bot 會要求使用者要執行的長時間執行作業。The bot asks the user which long-running operation to perform.
  • Bot 會接收來自使用者的活動,並決定要執行的作業。The bot receives an activity from the user, and determines which operation to perform.
  • Bot 會通知使用者,作業需要一些時間,並將作業傳送至 c # 函式。The bot notifies the user the operation will take some time and sends the operation off to a C# function.
    • Bot 會儲存狀態,表示有作業正在進行中。The bot saves state, indicating there is an operation in progress.
    • 當作業正在執行時,bot 會回應使用者的訊息,通知他們作業仍在進行中。While the operation is running, the bot responds to messages from the user, notifying them the operation is still in progress.
    • Azure Functions 管理長時間執行的作業,並將 event 活動傳送至 bot,以通知該作業已完成。Azure Functions manages the long-running operation and sends an event activity to the bot, notifying it that the operation completed.
  • Bot 會繼續交談並傳送主動式訊息,以通知使用者作業已完成。The bot resumes the conversation and sends a proactive message to notify the user that the operation completed. Bot 接著會清除稍早所述的作業狀態。The bot then clears the operation state mentioned earlier.

這個範例會定義 LongOperationPrompt 衍生自抽象類別的類別 ActivityPromptThis example defines a LongOperationPrompt class, derived from the abstract ActivityPrompt class. LongOperationPrompt 佇列要處理的活動時,它會在活動的 value 屬性中包含使用者的選擇。When the LongOperationPrompt queues the activity to be processed, it includes a choice from the user within the activity's value property. 此活動接著會在不同的活動中使用 Azure Functions、修改和包裝,然後 event 使用 Direct Line 用戶端傳送回 bot。This activity is then consumed by Azure Functions, modified, and wrapped in a different event activity before it is sent back to the bot using a Direct Line client. 在 bot 中,事件活動是藉由呼叫介面卡的 continue 對話 方法來繼續交談。Within the bot, the event activity is used to resume the conversation by calling the adapter's continue conversation method. 接著會載入對話方塊堆疊,並 LongOperationPrompt 完成。The dialog stack is then loaded, and the LongOperationPrompt completes.

本文將介紹許多不同的技術。This article touches on many different technologies. 如需相關文章的連結,請參閱 其他資源 一節。See the additional resources section for links to associated articles.

建立 Azure 儲存體帳戶Create an Azure Storage account

建立 Azure 儲存體帳戶,並取出連接字串。Create an Azure Storage account, and retrieve the connection string. 您必須將連接字串新增至 bot 的設定檔。You will need to add the connection string to your bot's configuration file.

如需詳細資訊,請參閱 建立儲存體帳戶 ,並 從 Azure 入口網站複製您的認證For more information, see create a storage account and copy your credentials from the Azure portal.

建立 Bot 通道註冊Create a Bot Channels Registration

  1. 在建立註冊之前,安裝程式會 ngrok 並取出 URL,以在本機偵錯工具期間用來作為 bot 的 訊息端點Before creating the registration, setup ngrok and retrieve a URL to be used as the bot's messaging endpoint during local debugging. 訊息端點將會是附加的 HTTPS 轉送 URL /api/messages/The messaging endpoint will be the HTTPS forwarding URL with /api/messages/ appended. 請注意,新 bot 的預設埠是3978。Note that the default port for new bots is 3978.

    如需詳細資訊,請參閱如何 使用 ngrok來對 bot 進行 debug。For more information, see how to debug a bot using ngrok.

  2. 在 Azure 入口網站中或使用 Azure CLI 來建立 Bot 通道註冊。Create a Bot Channels Registration in the Azure portal or with the Azure CLI. 將 bot 的訊息端點設定為您使用 ngrok 所建立的端點。Set the bot's messaging endpoint to the one you created with ngrok. 建立 Bot 通道註冊資源之後,請取得 bot 的 Microsoft 應用程式識別碼和密碼。After the Bot Channels Registration resource is created, obtain the bot's Microsoft app ID and password. 啟用 Direct Line 通道,並取得 Direct Line 秘密。Enable the Direct Line channel, and retrieve a Direct Line secret. 您會將這些新增至 bot 程式碼和 c # 函式。You will add these to your bot code and C# function.

    如需詳細資訊,請參閱如何 管理 bot 和如何將 bot 連線到 Direct LineFor more information, see how to manage a bot and how to connect a bot to Direct Line.

建立 c # 函式Create the C# function

  1. 根據 .Net Core 執行時間堆疊建立 Azure Functions 應用程式。Create an Azure Functions app based on the .Net Core runtime stack.

    如需詳細資訊,請參閱如何 建立函數應用程式Azure Functions c # 腳本參考For more information, see how to create a function app and the Azure Functions C# script reference.

  2. DirectLineSecret 應用程式設定新增至函數應用程式。Add a DirectLineSecret application setting to the Function App.

    如需詳細資訊,請參閱如何 管理您的函數應用程式For more information, see how to manage your function app.

  3. 在函式應用程式中,新增以 Azure 佇列儲存體範本為基礎的函式。Within the Function App, add a function based on the Azure Queue Storage template.

    設定所需的佇列名稱,然後選擇 Azure Storage Account 在先前步驟中建立的。Set the desired queue name, and choose the Azure Storage Account created in an earlier step. 此佇列名稱也將放置於 bot 的 appsettings.json file。This queue name will also be placed in the bot's appsettings.json file.

  4. 函數 proj 檔加入至函式。Add a function.proj file to the function.

    <Project Sdk="Microsoft.NET.Sdk">
        <PropertyGroup>
            <TargetFramework>netstandard2.0</TargetFramework>
        </PropertyGroup>
    
        <ItemGroup>
            <PackageReference Include="Microsoft.Bot.Connector.DirectLine" Version="3.0.2" />
            <PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.4" />
        </ItemGroup>
    </Project>
    
  5. 使用下列程式碼更新 執行 .csxUpdate run.csx with the following code:

    #r "Newtonsoft.Json"
    
    using System;
    using System.Net.Http;
    using System.Text;
    using Newtonsoft.Json;
    using Microsoft.Bot.Connector.DirectLine;
    using System.Threading;
    
    public static async Task Run(string queueItem, ILogger log)
    {
        log.LogInformation($"C# Queue trigger function processing");
    
        JsonSerializerSettings jsonSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore };
        var originalActivity =  JsonConvert.DeserializeObject<Activity>(queueItem, jsonSettings);
        // Perform long operation here....
        System.Threading.Thread.Sleep(TimeSpan.FromSeconds(15));
    
        if(originalActivity.Value.ToString().Equals("option 1", CompareOptions.OrdinalIgnoreCase))
        {
            originalActivity.Value = " (Result for long operation one!)";
        }
        else if(originalActivity.Value.ToString().Equals("option 2", CompareOptions.OrdinalIgnoreCase))
        {
            originalActivity.Value = " (A different result for operation two!)";
        }
    
        originalActivity.Value = "LongOperationComplete:" + originalActivity.Value;
        var responseActivity =  new Activity("event");
        responseActivity.Value = originalActivity;
        responseActivity.Name = "LongOperationResponse";
        responseActivity.From = new ChannelAccount("GenerateReport", "AzureFunction");
    
        var directLineSecret = Environment.GetEnvironmentVariable("DirectLineSecret");
        using(DirectLineClient client = new DirectLineClient(directLineSecret))
        {
            var conversation = await client.Conversations.StartConversationAsync();
            await client.Conversations.PostActivityAsync(conversation.ConversationId, responseActivity);
        }
    
        log.LogInformation($"Done...");
    }
    

建立 BotCreate the bot

  1. 從 c # 多回合提示 範例的複本開始。Start with a copy of the C# Multi-Turn-Prompt sample.

  2. 將 Nuget.exe NuGet 套件新增至您的專案。Add the Azure.Storage.Queues NuGet package to your project.

  3. 將您稍早建立的 Azure 儲存體帳戶和儲存體佇列名稱的連接字串新增至 bot 的設定檔。Add the connection string for the Azure Storage account you created earlier, and the Storage Queue Name, to your bot's configuration file.

    請確定佇列名稱與您稍早用來建立佇列觸發程式函數的名稱相同。Ensure the queue name is the same as the one you used to create the Queue Trigger Function earlier. 此外,也會新增您 MicrosoftAppIdMicrosoftAppPassword 早在建立 Bot 通道註冊資源時所產生之和屬性的值。Also add the values for the MicrosoftAppId and MicrosoftAppPassword properties that you generated earlier when you created the Bot Channels Registration resource.

    appsettings.jsonappsettings.json

    {
      "MicrosoftAppId": "<your-bot-app-id>",
      "MicrosoftAppPassword": "<your-bot-app-password>",
      "StorageQueueName": "<your-azure-storage-queue-name>",
      "QueueStorageConnection": "<your-storage-connection-string>"
    }
    
  4. IConfiguration 參數加入至 DialogBot.cs ,以便取出 MicrsofotAppIdAdd an IConfiguration parameter to DialogBot.cs in order to retrieve the MicrsofotAppId. 此外,也請從 Azure 函式新增 OnEventActivityAsync 的處理常式 LongOperationResponseAlso add an OnEventActivityAsync handler for the LongOperationResponse from the Azure Function.

    Bots\DialogBot.csBots\DialogBot.cs

    protected readonly IStatePropertyAccessor<DialogState> DialogState;
    protected readonly Dialog Dialog;
    protected readonly BotState ConversationState;
    protected readonly ILogger Logger;
    private readonly string _botId;
    
    /// <summary>
    /// Create an instance of <see cref="DialogBot{T}"/>.
    /// </summary>
    /// <param name="configuration"><see cref="IConfiguration"/> used to retrieve MicrosoftAppId
    /// which is used in ContinueConversationAsync.</param>
    /// <param name="conversationState"><see cref="ConversationState"/> used to store the DialogStack.</param>
    /// <param name="dialog">The RootDialog for this bot.</param>
    /// <param name="logger"><see cref="ILogger"/> to use.</param>
    public DialogBot(IConfiguration configuration, ConversationState conversationState, T dialog, ILogger<DialogBot<T>> logger)
    {
        _botId = configuration["MicrosoftAppId"] ?? Guid.NewGuid().ToString();
        ConversationState = conversationState;
        Dialog = dialog;
        Logger = logger;
        DialogState = ConversationState.CreateProperty<DialogState>(nameof(DialogState));
    }
    
    public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
    {
        await base.OnTurnAsync(turnContext, cancellationToken);
    
        // Save any state changes that might have occurred during the turn.
        await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
    }
    
    protected override async Task OnEventActivityAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
    {
        // The event from the Azure Function will have a name of 'LongOperationResponse'
        if (turnContext.Activity.ChannelId == Channels.Directline && turnContext.Activity.Name == "LongOperationResponse")
        {
            // The response will have the original conversation reference activity in the .Value
            // This original activity was sent to the Azure Function via Azure.Storage.Queues in AzureQueuesService.cs.
            var continueConversationActivity = (turnContext.Activity.Value as JObject)?.ToObject<Activity>();
            await turnContext.Adapter.ContinueConversationAsync(_botId, continueConversationActivity.GetConversationReference(), async (context, cancellation) =>
            {
                Logger.LogInformation("Running dialog with Activity from LongOperationResponse.");
    
                // ContinueConversationAsync resets the .Value of the event being continued to Null, 
                //so change it back before running the dialog stack. (The .Value contains the response 
                //from the Azure Function)
                context.Activity.Value = continueConversationActivity.Value;
                await Dialog.RunAsync(context, DialogState, cancellationToken);
    
                // Save any state changes that might have occurred during the inner turn.
                await ConversationState.SaveChangesAsync(context, false, cancellationToken);
            }, cancellationToken);
        }
        else
        {
            await base.OnEventActivityAsync(turnContext, cancellationToken);
        }
    }
    
  5. 建立 Azure 佇列服務來佇列需要處理的活動。Create an Azure Queues service to queue activities which need to be processed.

    AzureQueuesService.csAzureQueuesService.cs

    /// <summary>
    /// Service used to queue messages to an Azure.Storage.Queues.
    /// </summary>
    public class AzureQueuesService
    {
        private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings()
            {
                Formatting = Formatting.Indented,
                NullValueHandling = NullValueHandling.Ignore
            };
    
        private bool _createQueuIfNotExists = true;
        private readonly QueueClient _queueClient;
    
        /// <summary>
        /// Creates a new instance of <see cref="AzureQueuesService"/>.
        /// </summary>
        /// <param name="config"><see cref="IConfiguration"/> used to retrieve
        /// StorageQueueName and QueueStorageConnection from appsettings.json.</param>
        public AzureQueuesService(IConfiguration config)
        {
            var queueName = config["StorageQueueName"];
            var connectionString = config["QueueStorageConnection"];
    
            _queueClient = new QueueClient(connectionString, queueName);
        }
    
        /// <summary>
        /// Queue and Activity, with option in the Activity.Value to Azure.Storage.Queues
        ///
        /// <seealso cref="https://github.com/microsoft/botbuilder-dotnet/blob/master/libraries/Microsoft.Bot.Builder.Azure/Queues/ContinueConversationLater.cs"/>
        /// </summary>
        /// <param name="referenceActivity">Activity to queue after a call to GetContinuationActivity.</param>
        /// <param name="option">The option the user chose, which will be passed within the .Value of the activity queued.</param>
        /// <param name="cancellationToken">Cancellation token for the async operation.</param>
        /// <returns>Queued <see cref="Azure.Storage.Queues.Models.SendReceipt.MessageId"/>.</returns>
        public async Task<string> QueueActivityToProcess(Activity referenceActivity, string option, CancellationToken cancellationToken)
        {
            if (_createQueuIfNotExists)
            {
                _createQueuIfNotExists = false;
                await _queueClient.CreateIfNotExistsAsync().ConfigureAwait(false);
            }
    
            // create ContinuationActivity from the conversation reference.
            var activity = referenceActivity.GetConversationReference().GetContinuationActivity();
            // Pass the user's choice in the .Value
            activity.Value = option;
    
            var message = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(activity, jsonSettings)));
    
            // Aend ResumeConversation event, it will get posted back to us with a specific value, giving us 
            // the ability to process it and do the right thing.
            var reciept = await _queueClient.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
            return reciept.Value.MessageId;
        }
    }
    

對話方塊Dialogs

移除舊的對話方塊,並將其取代為新的對話方塊以支援作業。Remove the old dialog and replace it with new dialogs to support the operations.

  1. 移除 UserProfileDialog.cs 檔案。Remove the UserProfileDialog.cs file.

  2. 加入自訂提示對話方塊,詢問使用者要執行的作業。Add a custom prompt dialog that asks the user which operation to perform.

    Dialogs\LongOperationPrompt.csDialogs\LongOperationPrompt.cs

    /// <summary>
    /// <see cref="ActivityPrompt"/> implementation which will queue an activity,
    /// along with the <see cref="LongOperationPromptOptions.LongOperationOption"/>,
    /// and wait for an <see cref="ActivityTypes.Event"/> with name of "ContinueConversation"
    /// and Value containing the text: "LongOperationComplete".
    ///
    /// The result of this prompt will be the received Event Activity, which is sent by
    /// the Azure Function after it finishes the long operation.
    /// </summary>
    public class LongOperationPrompt : ActivityPrompt
    {
        private readonly AzureQueuesService _queueService;
    
        /// <summary>
        /// Create a new instance of <see cref="LongOperationPrompt"/>.
        /// </summary>
        /// <param name="dialogId">Id of this <see cref="LongOperationPrompt"/>.</param>
        /// <param name="validator">Validator to use for this prompt.</param>
        /// <param name="queueService"><see cref="AzureQueuesService"/> to use for Enqueuing the activity to process.</param>
        public LongOperationPrompt(string dialogId, PromptValidator<Activity> validator, AzureQueuesService queueService) 
            : base(dialogId, validator)
        {
            _queueService = queueService;
        }
    
        public async override Task<DialogTurnResult> BeginDialogAsync(DialogContext dc, object options, CancellationToken cancellationToken = default)
        {
            // When the dialog begins, queue the option chosen within the Activity queued.
            await _queueService.QueueActivityToProcess(dc.Context.Activity, (options as LongOperationPromptOptions).LongOperationOption, cancellationToken);
    
            return await base.BeginDialogAsync(dc, options, cancellationToken);
        }
    
        protected override Task<PromptRecognizerResult<Activity>> OnRecognizeAsync(ITurnContext turnContext, IDictionary<string, object> state, PromptOptions options, CancellationToken cancellationToken = default)
        {
            var result = new PromptRecognizerResult<Activity>() { Succeeded = false };
    
            if(turnContext.Activity.Type == ActivityTypes.Event
                && turnContext.Activity.Name == "ContinueConversation"
                && turnContext.Activity.Value != null
                // Custom validation within LongOperationPrompt.  
                // 'LongOperationComplete' is added to the Activity.Value in the Queue consumer (See: Azure Function)
                && turnContext.Activity.Value.ToString().Contains("LongOperationComplete", System.StringComparison.InvariantCultureIgnoreCase))
            {
                result.Succeeded = true;
                result.Value = turnContext.Activity;
            }
    
            return Task.FromResult(result);
        }
    }
    
  3. 為自訂提示新增提示選項類別。Add a prompt options class for the custom prompt.

    Dialogs\LongOperationPromptOptions.csDialogs\LongOperationPromptOptions.cs

    /// <summary>
    /// Options sent to <see cref="LongOperationPrompt"/> demonstrating how a value
    /// can be passed along with the queued activity.
    /// </summary>
    public class LongOperationPromptOptions : PromptOptions
    {
        /// <summary>
        /// This is a property sent through the Queue, and is used
        /// in the queue consumer (the Azure Function) to differentiate 
        /// between long operations chosen by the user.
        /// </summary>
        public string LongOperationOption { get; set; }
    }
    
  4. 新增使用自訂提示的對話方塊,以取得使用者的選擇並起始長時間執行的作業。Add the dialog that uses the custom prompt to get the user's choice and initiates the long-running operation.

    Dialogs\LongOperationDialog.csDialogs\LongOperationDialog.cs

    /// <summary>
    /// This dialog demonstrates how to use the <see cref="LongOperationPrompt"/>.
    ///
    /// The user is provided an option to perform any of three long operations.
    /// Their choice is then sent to the <see cref="LongOperationPrompt"/>.
    /// When the prompt completes, the result is received as an Activity in the
    /// final Waterfall step.
    /// </summary>
    public class LongOperationDialog : ComponentDialog
    {
        public LongOperationDialog(AzureQueuesService queueService)
            : base(nameof(LongOperationDialog))
        {
            // This array defines how the Waterfall will execute.
            var waterfallSteps = new WaterfallStep[]
            {
                OperationTimeStepAsync,
                LongOperationStepAsync,
                OperationCompleteStepAsync,
            };
    
            // Add named dialogs to the DialogSet. These names are saved in the dialog state.
            AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
            AddDialog(new LongOperationPrompt(nameof(LongOperationPrompt), (vContext, token) =>
            {
                return Task.FromResult(vContext.Recognized.Succeeded);
            }, queueService));
            AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
    
            // The initial child Dialog to run.
            InitialDialogId = nameof(WaterfallDialog);
        }
    
        private static async Task<DialogTurnResult> OperationTimeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
            // Running a prompt here means the next WaterfallStep will be run when the user's response is received.
            return await stepContext.PromptAsync(nameof(ChoicePrompt),
                new PromptOptions
                {
                    Prompt = MessageFactory.Text("Please select a long operation test option."),
                    Choices = ChoiceFactory.ToChoices(new List<string> { "option 1", "option 2", "option 3" }),
                }, cancellationToken);
        }
    
        private static async Task<DialogTurnResult> LongOperationStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            var value = ((FoundChoice)stepContext.Result).Value;
            stepContext.Values["longOperationOption"] = value;
    
            var prompt = MessageFactory.Text("...one moment please....");
            // The reprompt will be shown if the user messages the bot while the long operation is being performed.
            var retryPrompt = MessageFactory.Text($"Still performing the long operation: {value} ... (is the Azure Function executing from the queue?)");
            return await stepContext.PromptAsync(nameof(LongOperationPrompt),
                                                        new LongOperationPromptOptions
                                                        {
                                                            Prompt = prompt,
                                                            RetryPrompt = retryPrompt,
                                                            LongOperationOption = value,
                                                        }, cancellationToken);
        }
    
        private static async Task<DialogTurnResult> OperationCompleteStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            stepContext.Values["longOperationResult"] = stepContext.Result;
            await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Thanks for waiting. { (stepContext.Result as Activity).Value}"), cancellationToken);
    
            // Start over by replacing the dialog with itself.
            return await stepContext.ReplaceDialogAsync(nameof(WaterfallDialog), null, cancellationToken);
        }
    }
    

註冊服務和對話Register services and Dialog

Startup.cs 中,更新 ConfigureServices 方法以註冊 LongOperationDialog 並加入 AzureQueuesServiceIn Startup.cs, update the ConfigureServices method to register the LongOperationDialog and add the AzureQueuesService.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers().AddNewtonsoftJson();

    // Create the Bot Framework Adapter with error handling enabled.
    services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();

    // In production, this should be a persistent storage provider.bot
    services.AddSingleton<IStorage>(new MemoryStorage());

    // Create the Conversation state. (Used by the Dialog system itself.)
    services.AddSingleton<ConversationState>();

    // The Dialog that will be run by the bot.
    services.AddSingleton<LongOperationDialog>();

    // Service used to queue into Azure.Storage.Queues
    services.AddSingleton<AzureQueuesService>();

    // Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
    services.AddTransient<IBot, DialogBot<LongOperationDialog>>();
}

測試 BotTo test the bot

  1. 如果您尚未安裝 Bot Framework Emulator,請進行安裝。If you have not done so already, install the Bot Framework Emulator.

  2. 在您的電腦本機執行範例。Run the sample locally on your machine.

  3. 啟動模擬器、連線到您的 Bot 並傳送如下所示的訊息。Start the Emulator, connect to your bot, and send messages as shown below.

    Bot 範例

其他資源Additional resources

工具或功能Tool or feature 資源Resources
Azure FunctionsAzure Functions 建立函數應用程式Create a function app
Azure Functions c # 腳本Azure Functions C# script
管理您的函數應用程式Manage your function app
Azure 入口網站Azure portal 管理 BotManage a bot
將 Bot 連線至 Direct LineConnect a bot to Direct Line
Azure 儲存體Azure Storage Azure 佇列儲存體Azure Queue Storage
建立儲存體帳戶Create a storage account
從 Azure 入口網站複製您的認證Copy your credentials from the Azure portal
如何使用佇列How to Use Queues
Bot 基本概念Bot basics Bot 的運作方式How bots work
瀑布式對話中的提示Prompts in waterfall dialogs
主動式傳訊Proactive messaging
ngrokngrok 使用 ngrok 來對 bot 進行 DebugDebug a bot using ngrok