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”