TL&DR
OnContinueDialogAsync method of ComponentDialog class, should allow to interrupt dialog. But when we have 3 dialogs: MainDialog>SubDialogA>SubDialogAA, and we are inside of SubDialogAA, interruption commands will always be catched by MainDialog. How to allow catching of interruptions "higher" in SubDialogs (when those are active), without removing this logic from MainDialog?
More Details
So as per this article: https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-handle-user-interrupt?view=azure-bot-service-4.0&tabs=csharp we should handle interruptions with CancelAndHelpDialog class containing interruptions handling and then other dialogs should derive from this dialog.
Below is CancelAndHelpDialog
public class CancelAndHelpDialog : ComponentDialog
{
public InterruptionsHandleDialog (string id)
: base(id)
{
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
var result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
private async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken)
{
if (innerDc.Context.Activity.Type == ActivityTypes.Message)
{
var text = innerDc.Context.Activity.Text.ToLowerInvariant();
switch (text)
{
case "help":
case "?":
await innerDc.Context.SendActivityAsync("Help", cancellationToken: cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
case "cancel":
return await innerDc.CancelAllDialogsAsync(cancellationToken);
}
}
return null;
}
}
We have 3 dialogs and each of them derive from CancelAndHelpDialog: Main Dialog > SubDialogA > SubDialogAA (> means that this dialog calls other one with BeginDialogAsync)
Now I've tested 'help' from all three dialogs and it works. But it is called from MainDialog as messages are caught by MainDialog interruptions handling.
But then, 'cancel' will work as expected only in MainDialog, as it will always be caught there and will cancel MainDialog.
What would be elegant way to allow cancelling current dialog (using CancelAllDialogs) when we are in SubDialogA or SubDialogAA?
Also, is there any point for SubDialogA and SubDialogAA to derive from CancelAndHelpDialog, if everything would be caught by MainDialog anyway?
Found out that if dialog is active its DialogContext (innerDc) has Child property equal to null. This allow to execute interruptions only for active dialog:
public class CancelAndHelpDialog : ComponentDialog
{
public InterruptionsHandleDialog (string id)
: base(id)
{
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
var result = await InterruptAsync(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
private async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken)
{
if (innerDc.Context.Activity.Type == ActivityTypes.Message && innerDc.Child == null)
{
var text = innerDc.Context.Activity.Text.ToLowerInvariant();
switch (text)
{
case "help":
case "?":
await innerDc.Context.SendActivityAsync("Help", cancellationToken: cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
case "cancel":
return await innerDc.CancelAllDialogsAsync(cancellationToken);
}
}
return null;
}
}