Search code examples
c#botframework

How do I get the stepContext from the PromptValidator in Microsoft Bot Framework?


I am working with the DialogPromptBot sample. In the sample there is the following code:

// Create the dialog set and add the prompts, including custom validation.
_dialogSet = new DialogSet(_accessors.DialogStateAccessor);
_dialogSet.Add(new NumberPrompt<int>(PartySizePrompt, PartySizeValidatorAsync));
_dialogSet.Add(new ChoicePrompt(LocationPrompt));
_dialogSet.Add(new DateTimePrompt(ReservationDatePrompt, DateValidatorAsync));

// Define the steps of the waterfall dialog and add it to the set.
WaterfallStep[] steps = new WaterfallStep[]
{
    PromptForPartySizeAsync,
    PromptForLocationAsync,
    PromptForReservationDateAsync,
    AcknowledgeReservationAsync,
};
_dialogSet.Add(new WaterfallDialog(ReservationDialog, steps));

It sets up some prompts, validates the user's input and saves their responses. I don't like the way the code works because the user's input is saved in the next step (i.e., the party size is saved in the prompt for a location and the location is saved in the prompt for date). I would like to save the user's input in the corresponding validation step. This would remove the entanglement between requests and allow me to reorder the questions without making a lot of extraneous changes. It would also allow me to use the same validator for questions of the same type.

How do I access the WaterfallStepContext from the Prompt Validator? This would allow me to save the user's input once I determined that it was valid. In addition, the ChoicePrompt is supposed to take a Prompt Validator as well but I can't seem to get that to work. It seems to have built-in validation but I would also like to save the user's input there as well.


Solution

  • It is not recommended to store any state when performing validation of a prompt value. Instead it should be the responsibility of the caller of the prompt to do something with that value. Specifically, when using WaterfallDialog, the pattern that is recommended is that the "next" step is always responsible for storing the result of the previous step.

    So imagine having a number prompt that ensures a value between 1 and 100 is picked:

    Add(new NumberPrompt<int>("myNumberPrompt", async (pvc, ct) => pvc.Succeeded && pvc.Recognized.Value > 1 && pvc.Recognized.Value < 100); 
    

    And then some waterfall steps that utilize that prompt:

    private async Task<DialogTurnResult> FirstStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        return await stepContext.PromptAsync("myNumberPrompt", new PromptOptions { Prompt = MessageFactory.Text("Please pick a number between 1 and 100") }, cancellationToken);    
    }
    
    private async Task<DialogTurnResult> SecondStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
       // Get the result of the previous step which will be the value from the NumberPrompt<int>
       var chosenNumber = (int)stepContext.Result;
    
       // Store the value into the WaterfallStepContext's Values dictionary
       stepContext.Values["ChosenNumber"] = chosenNumber;
    
       await stepContext.Context.SendActivityAsync($"Ok, {chosenNumber}, got it.");
    
       // ... more code here, maybe prompt for other values, whatever ...
    }