The Waterfallstep is a crucial part of the v4 Bot Framework. When you review the samples there are two basic patterns;
Lambda style
The usual advantages apply, but mainly; reduces ‘unnecessary’ code and everything is one place.
dialogs.Add("reserveTable", new WaterfallStep[]
{
async (dc, args, next) =>
{
// Prompt for the guest's name.
await dc.Context.SendActivity("Welcome to the reservation service.");
dc.ActiveDialog.State = new Dictionary();
await dc.Prompt("dateTimePrompt", "Please provide a reservation date and time.");
},
async(dc, args, next) =>
{
var dateTimeResult = ((DateTimeResult)args).Resolution.First();
dc.ActiveDialog.State["date"] = Convert.ToDateTime(dateTimeResult.Value);
// Ask for next info
await dc.Prompt("partySizePrompt", "How many people are in your party?");
},
...
Step as Function style
Requires more writing but also has it’s advantages; easier to read the intent of the flow (especially for complex step logic) and easier to re-use steps (e.g. asking for a name).
var waterfallSteps = new WaterfallStep[]
{
PromptForNameStepAsync,
PromptForCityStepAsync,
DisplayGreetingStateStepAsync,
};
State In Step Pattern
Previously in State Handling I wrote about the v4 state pattern that initializes state in the setup phase of the Bot. However, as your Bot increases in complexity you should consider using a State-In-Step initialize step. E.g. changing the above example to (taken from Bot Samples);
var waterfallSteps = new WaterfallStep[]
{
InitializeStateStepAsync
PromptForNameStepAsync,
PromptForCityStepAsync,
DisplayGreetingStateStepAsync,
};
public IStatePropertyAccessor GreetingStateAccessor { get; }
private async Task InitializeStateStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var greetingState = await GreetingStateAccessor.GetAsync(stepContext.Context, () => null);
if (greetingState == null)
{
var greetingStateOpt = stepContext.Options as GreetingState;
if (greetingStateOpt != null)
{
await GreetingStateAccessor.SetAsync(stepContext.Context, greetingStateOpt);
}
else
{
await GreetingStateAccessor.SetAsync(stepContext.Context, new GreetingState());
}
}
return await stepContext.NextAsync();
}
I think I would still move that into a separate Accessor class but the step pattern keeps the responsibility clearly defined.
Ultimately the choice is yours, but for a reasonably complex Bot I would suggest using the Step-As-Function combined with the State-In-Step patterns.
One thought on “Waterfallstep patterns in Bot Framework”