Freigeben über


Verwalten eines Vorgangs mit langer Ausführungszeit

GILT FÜR: SDK v4

Die ordnungsgemäße Handhabung von lang andauernden Vorgängen ist ein wichtiger Aspekt eines robusten Bots. Wenn der Azure AI Bot-Dienst eine Aktivität von einem Kanal an Ihren Bot sendet, wird erwartet, dass der Bot die Aktivität schnell verarbeitet. Wenn der Bot den Vorgang je nach Kanal nicht innerhalb von 10 bis 15 Sekunden abschließt, meldet der Azure AI Bot-Dienst einen Timeout und meldet den Client zurück, 504:GatewayTimeoutwie in "Funktionsweise von Bots" beschrieben.

In diesem Artikel wird beschrieben, wie Sie einen externen Dienst zum Ausführen des Vorgangs verwenden und den Bot benachrichtigen, wenn er abgeschlossen ist.

Voraussetzungen

Informationen zu diesem Beispiel

Dieser Artikel beginnt mit dem Multi-Turn-Eingabeaufforderungs-Beispiel-Bot und fügt Code für die Ausführung lang ausgeführter Vorgänge hinzu. Außerdem wird veranschaulicht, wie sie auf einen Benutzer reagieren, nachdem der Vorgang abgeschlossen wurde. Im aktualisierten Beispiel:

  • Der Bot fordert den Benutzer auf, welche lang ausgeführte Operation ausgeführt werden soll.
  • Der Bot empfängt eine Aktivität des Benutzers und bestimmt, welcher Vorgang ausgeführt werden soll.
  • Der Bot benachrichtigt den Benutzer, dass der Vorgang einige Zeit in Anspruch nimmt und den Vorgang an eine C#-Funktion sendet.
    • Der Bot speichert den Zustand, der angibt, dass ein Vorgang ausgeführt wird.
    • Während der Vorgang ausgeführt wird, antwortet der Bot auf Nachrichten des Benutzers, und benachrichtigt sie, dass der Vorgang noch ausgeführt wird.
    • Azure Functions verwaltet den lang ausgeführten Vorgang und sendet eine event Aktivität an den Bot, und benachrichtigt ihn darüber, dass der Vorgang abgeschlossen wurde.
  • Der Bot setzt die Unterhaltung fort und sendet eine proaktive Nachricht, um den Benutzer darüber zu informieren, dass der Vorgang abgeschlossen ist. Der Bot löscht dann den zuvor erwähnten Vorgangszustand.

In diesem Beispiel wird eine LongOperationPrompt Von der abstrakten ActivityPrompt Klasse abgeleitete Klasse definiert. Wenn die zu verarbeitende Aktivität in die LongOperationPrompt Warteschlange eingereiht wird, enthält sie eine Auswahl des Benutzers innerhalb der Werteigenschaft der Aktivität. Diese Aktivität wird dann von Azure-Funktionen genutzt, geändert und in eine andere event Aktivität eingeschlossen, bevor sie mithilfe eines Direct Line-Clients an den Bot zurückgesendet wird. Innerhalb des Bots wird die Ereignisaktivität verwendet, um die Unterhaltung fortzusetzen, indem die Fortsetzungsunterhaltungsmethode des Adapters aufgerufen wird. Der Dialogstapel wird dann geladen, und die LongOperationPrompt Fertigstellungen.

Dieser Artikel berührt viele verschiedene Technologien. Links zu zugehörigen Artikeln finden Sie im Abschnitt "Zusätzliche Informationen ".

Wichtig

Dieser Artikel enthält Beispiele für Legacy-Code mit Verbindungszeichenfolgen in Konfigurationsdateien für die interne Verbindung zum Speicher. Microsoft empfiehlt, den sichersten Authentifizierungsfluss zu verwenden. Wenn Sie eine Verbindung mit einer Azure-Ressource herstellen, ist Verwaltete Identitäten für Azure-Ressourcen die empfohlene Methode zur Authentifizierung.

Erstellen Sie ein Azure Storage-Konto.

Erstellen Sie ein Azure Storage-Konto, und rufen Sie die Verbindungszeichenfolge ab. Sie müssen die Verbindungszeichenfolge zur Konfigurationsdatei Ihres Bots hinzufügen.

Weitere Informationen finden Sie unter Erstellen eines Speicherkontos und Kopieren Ihrer Anmeldeinformationen aus dem Azure-Portal.

Erstellen einer Botressource

  1. Richten Sie Dev Tunnels ein, und rufen Sie eine URL ab, die während des lokalen Debuggens als Messaging-Endpunkt des Bots verwendet werden soll. Der Messagingendpunkt ist die HTTPS-Weiterleitungs-URL, die /api/messages/ angefügt wird. Der Standardport für neue Bots ist 3978.

    Weitere Informationen finden Sie unter Debuggen eines Bots mithilfe von Devtunnel.

  2. Erstellen Sie eine Azure Bot-Ressource im Azure-Portal oder mit der Azure CLI. Legen Sie den Messaging-Endpunkt des Bots auf den Endpunkt fest, den Sie mit Dev Tunnels erstellt haben. Rufen Sie nach dem Erstellen der Bot-Ressource die Microsoft-App-ID und das Kennwort des Bots ab. Aktivieren Sie den Direct Line-Kanal, und rufen Sie einen geheimen Direct Line-Schlüssel ab. Sie fügen diese dem Botcode und der C#-Funktion hinzu.

    Weitere Informationen finden Sie in der Verwaltung eines Bots und zum Verbinden eines Botsmit Direct Line.

Erstellen der C#-Funktion

  1. Erstellen Sie eine Azure Functions-App basierend auf dem .NET Core-Laufzeitstapel.

    Weitere Informationen finden Sie unter Erstellen einer Funktions-App und der Referenz zu Azure Functions C#.For more information, see how to create a function app and the Azure Functions C# script reference.

  2. Fügen Sie der Funktions-App eine DirectLineSecret Anwendungseinstellung hinzu.

    Weitere Informationen finden Sie in der Verwaltung Ihrer Funktions-App.

  3. Fügen Sie in der Funktions-App eine Funktion basierend auf der Azure Queue Storage-Vorlage hinzu.

    Legen Sie den gewünschten Warteschlangennamen fest, und wählen Sie den Azure Storage Account in einem früheren Schritt erstellten Eintrag aus. Dieser Warteschlangenname wird auch in der appsettings.json-Datei des Bots platziert.

  4. Fügen Sie der Funktion eine Datei "function.proj " hinzu.

    <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. Aktualisieren Sie run.csx mit dem folgenden 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", StringComparison.OrdinalIgnoreCase))
        {
            originalActivity.Value = " (Result for long operation one!)";
        }
        else if(originalActivity.Value.ToString().Equals("option 2", StringComparison.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...");
    }
    

Den Bot erstellen

  1. Beginnen Sie mit einer Kopie des C# Multi-Turn-Prompt-Beispiels .

  2. Fügen Sie ihrem Projekt das NuGet-Paket "Azure.Storage.Queues " hinzu.

  3. Fügen Sie die Verbindungszeichenfolge für das zuvor erstellte Azure Storage-Konto und den Namen der Speicherwarteschlange zur Konfigurationsdatei Ihres Bots hinzu.

    Stellen Sie sicher, dass der Warteschlangenname mit dem namen identisch ist, den Sie zum Erstellen der Warteschlangentriggerfunktion verwendet haben. Fügen Sie außerdem die Werte für die MicrosoftAppId zuvor generierten Eigenschaften hinzu MicrosoftAppPassword , wenn Sie die Azure Bot-Ressource erstellt haben.

    appsettings.json

    {
      "MicrosoftAppId": "<your-bot-app-id>",
      "MicrosoftAppPassword": "<your-bot-app-password>",
      "StorageQueueName": "<your-azure-storage-queue-name>",
      "QueueStorageConnection": "<your-storage-connection-string>"
    }
    
  4. Fügen Sie einen IConfiguration Parameter zu DialogBot.cs hinzu, um die MicrsofotAppId. Fügen Sie außerdem einen OnEventActivityAsync Handler für die LongOperationResponse aus der Azure-Funktion hinzu.

    Bots\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. Erstellen Sie einen Azure Queues-Dienst, um Aktivitäten in die Warteschlange zu stellen, die verarbeitet werden sollen.

    AzureQueuesService.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;
        }
    }
    

Dialogfelder

Entfernen Sie das alte Dialogfeld, und ersetzen Sie es durch neue Dialogfelder, um die Vorgänge zu unterstützen.

  1. Entfernen Sie die UserProfileDialog.cs Datei.

  2. Fügen Sie ein benutzerdefiniertes Eingabeaufforderungsdialogfeld hinzu, in dem der Benutzer gefragt wird, welcher Vorgang ausgeführt werden soll.

    Dialogfelder\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. Fügen Sie eine Eingabeaufforderungsoptionenklasse für die benutzerdefinierte Eingabeaufforderung hinzu.

    Dialogfelder\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. Fügen Sie das Dialogfeld hinzu, das die benutzerdefinierte Eingabeaufforderung verwendet, um die Auswahl des Benutzers zu erhalten und den vorgang mit langer Ausführung zu initiieren.

    Dialogfelder\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's 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);
        }
    }
    

Registrieren von Diensten und Dialogfeld

Aktualisieren Sie in Startup.cs die ConfigureServices Methode, um die LongOperationDialog Datei zu registrieren und hinzuzufügenAzureQueuesService.

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>>();
}

So testen Sie den Bot

  1. Installieren Sie Bot Framework Emulator, sofern noch nicht geschehen.
  2. Führen Sie das Beispiel lokal auf Ihrem Computer aus.
  3. Starten des Emulators und Herstellen einer Verbindung mit Ihrem Bot.
  4. Wählen Sie einen langen Vorgang aus, der gestartet werden soll.
    • Der Bot sendet einen Moment, bitte nachrichten und stellt die Azure-Funktion in die Warteschlange.
    • Wenn der Benutzer versucht, vor Abschluss des Vorgangs mit dem Bot zu interagieren, antwortet der Bot mit einer noch funktionierenden Nachricht.
    • Nach Abschluss des Vorgangs sendet der Bot eine proaktive Nachricht an den Benutzer, um ihn darüber zu informieren, dass er abgeschlossen ist.

Beispieltranskript mit dem Benutzer, der einen langen Vorgang initiiert und schließlich eine proaktive Nachricht empfängt, dass der Vorgang abgeschlossen wurde.

Weitere Informationen

Tool oder Feature Ressourcen
Azure-Funktionen Erstellen einer Funktions-App
Azure Functions C#-Skript
Verwalten Ihrer Funktions-App
Azure-Portal Verwalten eines Bots
Mit einem Bot verbinden mit Direct Line
Azure Storage Azure Queue Storage
Erstellen eines Speicherkontos
Kopieren Ihrer Anmeldeinformationen aus der Azure-Portal
Verwenden von Warteschlangen
Grundlagen von Bots Funktionsweise von Bots
Aufforderungen in Wasserfalldialogfeldern
Proaktives Messaging
Dev Tunnels Debuggen eines Bots mit devtunnel