From 92af3db2c122ed1219a1a8a99ec95cb9d48517f6 Mon Sep 17 00:00:00 2001 From: lauren-mills Date: Wed, 2 Oct 2019 08:40:10 -0700 Subject: [PATCH] [Lib] Refactored routerDialog (#2458) * refactored routerDialog * updated method documentation --- .../CognitiveModelSet.cs | 4 +- .../Dialogs/InterruptableDialog.cs | 4 +- .../Dialogs/InterruptionAction.cs | 12 +- .../Dialogs/RouterDialog.cs | 143 ++++++++---------- 4 files changed, 76 insertions(+), 87 deletions(-) diff --git a/sdk/csharp/libraries/microsoft.bot.builder.solutions/CognitiveModelSet.cs b/sdk/csharp/libraries/microsoft.bot.builder.solutions/CognitiveModelSet.cs index a9ef035c57..b4fd1081e7 100644 --- a/sdk/csharp/libraries/microsoft.bot.builder.solutions/CognitiveModelSet.cs +++ b/sdk/csharp/libraries/microsoft.bot.builder.solutions/CognitiveModelSet.cs @@ -8,8 +8,8 @@ public class CognitiveModelSet { public IRecognizer DispatchService { get; set; } - public Dictionary LuisServices { get; set; } = new Dictionary(); + public Dictionary LuisServices { get; set; } = new Dictionary(); - public Dictionary QnAServices { get; set; } = new Dictionary(); + public Dictionary QnAServices { get; set; } = new Dictionary(); } } \ No newline at end of file diff --git a/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/InterruptableDialog.cs b/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/InterruptableDialog.cs index 31a28bcfae..36a7bda38b 100644 --- a/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/InterruptableDialog.cs +++ b/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/InterruptableDialog.cs @@ -37,13 +37,13 @@ protected override async Task OnContinueDialogAsync(DialogCont { var status = await OnInterruptDialogAsync(dc, cancellationToken).ConfigureAwait(false); - if (status == InterruptionAction.MessageSentToUser) + if (status == InterruptionAction.Resume) { // Resume the waiting dialog after interruption await dc.RepromptDialogAsync().ConfigureAwait(false); return EndOfTurn; } - else if (status == InterruptionAction.StartedDialog) + else if (status == InterruptionAction.Waiting) { // Stack is already waiting for a response, shelve inner stack return EndOfTurn; diff --git a/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/InterruptionAction.cs b/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/InterruptionAction.cs index 1bab9671c6..90f6eda489 100644 --- a/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/InterruptionAction.cs +++ b/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/InterruptionAction.cs @@ -3,17 +3,25 @@ namespace Microsoft.Bot.Builder.Solutions.Dialogs { + /// + /// Indicates the current status of a dialog interruption. + /// public enum InterruptionAction { + /// + /// Indicates that the active dialog was interrupted and should end. + /// + End, + /// /// Indicates that the active dialog was interrupted and needs to resume. /// - MessageSentToUser, + Resume, /// /// Indicates that there is a new dialog waiting and the active dialog needs to be shelved. /// - StartedDialog, + Waiting, /// /// Indicates that no interruption action is required. diff --git a/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/RouterDialog.cs b/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/RouterDialog.cs index 5c9a3f4e62..c0005728c2 100644 --- a/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/RouterDialog.cs +++ b/sdk/csharp/libraries/microsoft.bot.builder.solutions/Dialogs/RouterDialog.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.Bot.Builder.Dialogs; using Microsoft.Bot.Builder.Solutions.Extensions; @@ -14,80 +15,48 @@ public RouterDialog(string dialogId, IBotTelemetryClient telemetryClient) TelemetryClient = telemetryClient; } - protected override Task OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default(CancellationToken)) + protected override Task OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default) { return OnContinueDialogAsync(innerDc, cancellationToken); } - protected override async Task OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)) + protected override async Task OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default) { + // Check for any interruptions. var status = await OnInterruptDialogAsync(innerDc, cancellationToken).ConfigureAwait(false); - if (status == InterruptionAction.MessageSentToUser) + if (status == InterruptionAction.Resume) { - // Resume the waiting dialog after interruption + // Interruption message was sent, and the waiting dialog should resume/reprompt. await innerDc.RepromptDialogAsync().ConfigureAwait(false); - return EndOfTurn; } - else if (status == InterruptionAction.StartedDialog) + else if (status == InterruptionAction.Waiting) { - // Stack is already waiting for a response, shelve inner stack + // Interruption intercepted conversation and is waiting for user to respond. return EndOfTurn; } - else + else if (status == InterruptionAction.End) + { + // Interruption ended conversation, and current dialog should end. + return await innerDc.EndDialogAsync().ConfigureAwait(false); + } + else if (status == InterruptionAction.NoAction) { + // No interruption was detected. Process activity normally. var activity = innerDc.Context.Activity; - if (activity.IsStartActivity()) - { - await OnStartAsync(innerDc).ConfigureAwait(false); - } - switch (activity.Type) { case ActivityTypes.Message: { - // Note: This check is a workaround for adaptive card buttons that should map to an event (i.e. startOnboarding button in intro card) - if (activity.Value != null) - { - await OnEventAsync(innerDc).ConfigureAwait(false); - } - else - { - var result = await innerDc.ContinueDialogAsync().ConfigureAwait(false); - - switch (result.Status) - { - case DialogTurnStatus.Empty: - { - await RouteAsync(innerDc).ConfigureAwait(false); - break; - } - - case DialogTurnStatus.Complete: - { - if (result.Result is RouterDialogTurnResult routerDialogTurnResult && routerDialogTurnResult.Status == RouterDialogTurnStatus.Restart) - { - await RouteAsync(innerDc).ConfigureAwait(false); - break; - } - - // End active dialog - await innerDc.EndDialogAsync().ConfigureAwait(false); - break; - } - - default: - { - break; - } - } - } + // Pass message to waiting child dialog. + var result = await innerDc.ContinueDialogAsync().ConfigureAwait(false); - // If the active dialog was ended on this turn (either on single-turn dialog, or on continueDialogAsync) run CompleteAsync method. - if (innerDc.ActiveDialog == null) + if (result.Status == DialogTurnStatus.Empty + || (result.Result is RouterDialogTurnResult routerDialogTurnResult && routerDialogTurnResult.Status == RouterDialogTurnStatus.Restart)) { - await CompleteAsync(innerDc).ConfigureAwait(false); + // There was no waiting dialog on the stack, process message normally. + await OnMessageActivityAsync(innerDc).ConfigureAwait(false); } break; @@ -95,95 +64,107 @@ public RouterDialog(string dialogId, IBotTelemetryClient telemetryClient) case ActivityTypes.Event: { - await OnEventAsync(innerDc).ConfigureAwait(false); + await OnEventActivityAsync(innerDc).ConfigureAwait(false); break; } - case ActivityTypes.Invoke: + case ActivityTypes.ConversationUpdate: { - // Used by Teams for Authentication scenarios. - await innerDc.ContinueDialogAsync().ConfigureAwait(false); + await OnMembersAddedAsync(innerDc).ConfigureAwait(false); break; } default: { - await OnSystemMessageAsync(innerDc).ConfigureAwait(false); + // All other activity types will be routed here. Custom handling should be added in implementation. + await OnUnhandledActivityTypeAsync(innerDc).ConfigureAwait(false); break; } } + } - return EndOfTurn; + if (innerDc.ActiveDialog == null) + { + // If the inner dialog stack completed during this turn, this component should be ended. + return await innerDc.EndDialogAsync().ConfigureAwait(false); } - } - protected override Task OnEndDialogAsync(ITurnContext context, DialogInstance instance, DialogReason reason, CancellationToken cancellationToken = default(CancellationToken)) - { - return base.OnEndDialogAsync(context, instance, reason, cancellationToken); + return EndOfTurn; } - protected override Task OnRepromptDialogAsync(ITurnContext turnContext, DialogInstance instance, CancellationToken cancellationToken = default(CancellationToken)) + protected override async Task EndComponentAsync(DialogContext outerDc, object result, CancellationToken cancellationToken) { - return base.OnRepromptDialogAsync(turnContext, instance, cancellationToken); + // This happens when an inner dialog ends. Could call complete here + await OnDialogCompleteAsync(outerDc, result, cancellationToken).ConfigureAwait(false); + return await base.EndComponentAsync(outerDc, result, cancellationToken).ConfigureAwait(false); } /// - /// Called when the inner dialog stack is empty. + /// Called on every turn, enabling interruption scenarios. /// /// The dialog context for the component. /// The cancellation token. - /// A representing the asynchronous operation. - protected abstract Task RouteAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)); + /// A returning an + /// which indicates what action should be taken after interruption.. + protected override Task OnInterruptDialogAsync(DialogContext innerDc, CancellationToken cancellationToken) + { + return Task.FromResult(InterruptionAction.NoAction); + } /// - /// Called when the inner dialog stack is complete. + /// Called when an event activity is received. /// /// The dialog context for the component. - /// The dialog result when inner dialog completed. /// The cancellation token. /// A representing the asynchronous operation. - protected virtual Task CompleteAsync(DialogContext innerDc, DialogTurnResult result = null, CancellationToken cancellationToken = default(CancellationToken)) + protected virtual Task OnEventActivityAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)) { - innerDc.EndDialogAsync(result).Wait(cancellationToken); return Task.CompletedTask; } /// - /// Called when an event activity is received. + /// Called when a message activity is received. /// /// The dialog context for the component. /// The cancellation token. /// A representing the asynchronous operation. - protected virtual Task OnEventAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)) + protected virtual Task OnMessageActivityAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)) { return Task.CompletedTask; } /// - /// Called when a system activity is received. + /// Called when a conversationUpdate activity is received. /// /// The dialog context for the component. /// The cancellation token. /// A representing the asynchronous operation. - protected virtual Task OnSystemMessageAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)) + protected virtual Task OnMembersAddedAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)) { return Task.CompletedTask; } /// - /// Called when a conversation update activity is received. + /// Called when an activity type other than event, message, or conversationUpdate is received. /// /// The dialog context for the component. /// The cancellation token. /// A representing the asynchronous operation. - protected virtual Task OnStartAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)) + protected virtual Task OnUnhandledActivityTypeAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken)) { return Task.CompletedTask; } - protected override Task OnInterruptDialogAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken)) + /// + /// Called when the inner dialog stack completes. + /// + /// The dialog context for the component. + /// The dialog turn result for the component. + /// The cancellation token. + /// A representing the asynchronous operation. + protected virtual Task OnDialogCompleteAsync(DialogContext outerDc, object result, CancellationToken cancellationToken) { - return Task.FromResult(InterruptionAction.NoAction); + return Task.CompletedTask; } } -} \ No newline at end of file +}