Search code examples
c#botframework

BotFramework V4: RepromptDialogAsync not working


I can't make RepromptDialogAsync() to work. I am doing it like this. When dialog b is chosen it should re-prompt the choice prompt because dialog B is not yet ready. But when choosing dialog b it is doing nothing. Am I doing it wrong? I can't find any RepromptDialogAsync() tutorial on docs. Thank you!

Main Code:

public class MainDialog : ComponentDialog
{
    private const string InitialId = "mainDialog";
    private const string ChoicePrompt = "choicePrompt";
    private const string DialogAId = "dialogAId";

    public MainDialog(string dialogId)
        : base(dialogId)
    {
        InitialDialogId = InitialId;

        WaterfallStep[] waterfallSteps = new WaterfallStep[]
         {
            FirstStepAsync,
            SecondStepAsync,
            ThirdStepAsync,
            FourthStepAsync
         };
        AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
        AddDialog(new DialogA(DialogAId));
    }

    private static async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        return await stepContext.PromptAsync(
            ChoicePrompt,
            new PromptOptions
            {
                Prompt = MessageFactory.Text($"Here are your choices:"),
                Choices = new List<Choice>
                    {
                        new Choice
                        {
                            Value = "Open Dialog A",
                        },
                        new Choice
                        {
                            Value = "Open Dialog B",
                        },
                    },
                RetryPrompt = MessageFactory.Text($"Please choose one of the options."),
            });
    }

    private static async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        var response = (stepContext.Result as FoundChoice)?.Value.ToLower();

        if (response == "open dialog a")
        {
            return await stepContext.BeginDialogAsync(DialogAId, cancellationToken: cancellationToken);
        }

        if (response == "open dialog b")
        {
            await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Dialog B is not ready need to reprompt previous step."));
            await stepContext.RepromptDialogAsync();
        }

        return await stepContext.NextAsync();
    }

   private static async Task<DialogTurnResult> ThirdStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
       // do something else
        return await stepContext.NextAsync();
    }

    private static async Task<DialogTurnResult> FourthStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        // what is the best way to end this?
        // return await stepContext.ReplaceDialogAsync(InitialId);
        return await stepContext.EndDialogAsync();
    }

Solution

  • I'll address a couple of issues in this answer:

    Answering Your Actual Question

    RepromptDialogAsync() calls reprompt on the currently active dialog, but is meant to be used with Prompts that have a reprompt behavior. ChoicePrompt does have a reprompt option, but isn't meant to be used in this context. Instead, you'd call your prompt, validate the response within OnTurnAsync, and call dc.RepromptDialogAsync() as necessary.

    Your OnTurnAsync might look something like this:

    public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        var activity = turnContext.Activity;
    
        var dc = await Dialogs.CreateContextAsync(turnContext);
    
        if (activity.Type == ActivityTypes.Message)
        {
            if (activity.Text.ToLower() == "open dialog b")
            {
                await dc.RepromptDialogAsync();
            };
    ...
    

    That being said, I wouldn't use RepromptDialogAsync() for your use case, at all. Instead, use one of the following options:

    1. ReplaceDialogAsync()

    The easiest option for you would be to replace:

    await stepContext.RepromptDialogAsync();

    with:

    return await stepContext.ReplaceDialogAsync(InitialId);

    This starts your "mainDialog" over. In your example, this is fine, because you're restarting from the 2nd step back to the first.

    2. Prompt Validation

    ChoicePrompt automatically validates whether or not the user responds with a valid option, but you can pass in your own custom validator. Something like:

    AddDialog(new ChoicePrompt(choicePrompt, ValidateChoice));
    ...
    private async Task<bool> ValidateChoice(PromptValidatorContext<FoundChoice> promptContext, CancellationToken cancellationToken)
    {
        if (promptContext.Recognized.Value.Value.ToLower() == "open dialog b")
        {
            return false;
        }
        else
        {
            return true;
        }
    }
    

    Other Issues

    Really, though, you shouldn't prompt your user with the option to select "dialog b" if it isn't ready. You could, instead, do something like:

    var choices = new List<Choice>
            {
                new Choice
                {
                    Value = "Open Dialog A",
                }
            };
    if (bIsReady)
    {
        choices.Add(new Choice
        {
            Value = "Open Dialog B",
        });
    };