Search code examples
c#botframework

How to handle interruption in dialogs with validation?


Attempt to handle 'expected' interruption during one scenario is ignored in favour of validating the user input.

I've got a bot (SDKv4) with 3 distinct scenarios. I've extracted each scenario into their own class inheriting from ComponentDialog. Here, I can contain all waterfall steps relevant for each scenario.

public class ScenarioOne : ComponentDialog
{
    public ScenarioOne()
    {
        var steps = new WaterfallStep[]
        {
            GetInputStepAsync,
            ProcessInputStepAsync
        };

        AddDialog(new WaterfallDialog(nameof(ScenarioOne), steps));
    }

    private async Task<DialogTurnResult> GetInputStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        // Prompt for data here.
    }
}

In one scenario, I require a 17-character input from the user. I applied a custom validator and assigned it to the relevant waterfall step.

public class StepOneValidator
{
    public async Task<bool> ValidateAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
    {
        // Validation logic here.
    }
}

The validator is registered in the Bot:

public ExampleBot(ExampleBotAccessors accessors)
{
    _dialogs = new DialogSet(accessor.Dialog);
    _dialogs
        .Add(new TextPrompt("step-one", new CustomValidator().ValidateAsync));

    _dialogs.Add(new ScenarioOne());
}

I'd like to handle expected interruption for this specific scenario such as more info or help to assist users with the format of the expected input. I've read from the documentation that I can intercept the response globally within the OnTurnAsync handler but this will be applied to all responses which I'd like to avoid.

Documentation that I'm following can be found here.

Something similar can be added to the ProcessInputStepAsync method to check for expected interruptions:

public async Task<DialogTurnResult> ProcessInputStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var response = ((string)stepContext.Result).Trim().ToLowerInvariant();
    if (response.Equals("help")
    {
        // Send activity and replace dialog with main dialog.
    }
}

I expect the validator to ignore expected interruptions. Is there a way to achieve this without hard-coding these in the validator?


Solution

  • You can override the OnContinueDialogAsync method on ComponentDialog and do your interruption logic there. As part of your checks, you can see what the active dialog is and if it is your TextInput then handle your interruption. An example is below.

    public class ScenarioOne : ComponentDialog
    {
        private static string textInputId = "text";
    
        public ScenarioOne()
        {
            var steps = new WaterfallStep[]
            {
                GetInputStepAsync,
                ProcessInputStepAsync
            };
    
            AddDialog(new WaterfallDialog(nameof(ScenarioOne), steps));
            AddDialog(new TextPrompt(textInputId));
        }
    
        private async Task<DialogTurnResult> GetInputStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            // Prompt for data here.
        }
    
        protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
        {
            string text = innerDc.Context.Activity.Text;
    
            if (text.ToLower() == "help" && innerDc.ActiveDialog.Id != textInputId)
            {
                //do interruption
            }
            return await base.OnContinueDialogAsync(innerDc, cancellationToken);
        }
    }