Waterfallstep patterns in Bot Framework

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.
 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s