Practical Dialog State in large Bot Framework v4 projects

State seems to be large topic in v4. I’ve written posts about it before, including; Waterfall Step Patterns and Changes in State Handling, but after more exposure to v4 I coming around to the decision that for more complex projects I’m going to recommend reverting back to how v3 manages state…mostly.

The v4 way, or not?

Using the Accessor Pattern for state is great, it provides a sense of concrete design. You create a dialog, you create one or more state accessors that will be passed to it. The use of the state is very explicit. This is all good. So what’s the problem? The problem is that for complex bot projects your state is going to be filled with little islands of seemingly unrelated data. Consider a simple scenario of a New Contact dialog. You invoke the dialog, which in-turn (pun not intended) invokes a New Address dialog which can also be used from other dialogs. You would write something like;

IStatePropertyAccessor contactStatePropertyAccessor;
IStatePropertyAccessor addressStatePropertyAccessor;

But in a complex Bot Project you may have tens or hundreds of dialogs;

IStatePropertyAccessor s1StatePropertyAccessor;
IStatePropertyAccessor s2StatePropertyAccessor;
...
IStatePropertyAccessor sNStatePropertyAccessor;

That’s not cool.

What about a Generic State Accessor?

So given we want to mitigate all these state islands, could we have a generic accessor? Short answer is, “yes”, actual answer is, “too dirty”.

IStatePropertyAccessor activeDialogStatePropertyAccessor;

Every dialog gets passed the same single accessor and they store their data in it. Ah but what our chain of dialogs, when we invoke our New Address won’t that overwrite the current New Contact state? Yes. So we’ll have to look at creating some form of dictionary/hash to avoid…hang on, this seems a lot like the Active Dialog state from v3. Is that still available?

Active Dialog State

Active Dialog State is the framework supplied state that follows the dialog stack. Or to put it another way, we don’t have to worry about the dialog stack. So how do we gain access to this in v4? Overrides;

public override Task BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
    this.dialogContext = outerDc;
    this.SaveState();
    return base.BeginDialogAsync(outerDc, options, cancellationToken);
}

public override Task ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default(CancellationToken))
{
    this.dialogContext = outerDc;
    this.InitializeFromState();
    return base.ContinueDialogAsync(outerDc, cancellationToken);
}

In this example I’ve duplicated the handling of the state per class, but making this generic with clones or actions would be the way forward;

private void SaveState()
{
    // EDIT: DO NOT use 'this', its like creating a mini sub stack
    // and confuses the dialog rehydration. Use smaller struct/class
    this.dialogContext.ActiveDialog.State["ObjectState"] = this;
}

private void InitializeFromState()
{
    if (this.dialogContext.ActiveDialog.State.TryGetValue("ObjectState", out object rawObject))
    {
        var dialog = contactDialogObject as NewContactDialog;
        if (dialog != null)
        {
            if (dialog != null)
            {
                this.FirstName = dialog.FirstName;
                this.Telephone = dialog.Telephone;
            }
        }
    }
}

So now when the dialog starts it fetches its own state from the Active Dialog (itself), and updates its properties. So we are now at roughly the same point we would be in v3, i.e. we’ve been re-hydrated. We now need to update the state when a values changes;

private async Task PromptForTelephoneStepAsync(
                                        WaterfallStepContext stepContext,
                                        CancellationToken cancellationToken)
{
    // we've passed the validation so save the result
    this.Name = stepContext.Result.ToString();
    this.SaveState();
    ...

That’s it. We now have a fully working state engine for dialogs that doesn’t leave us with lots of data islands. Note, the above can be tidied up through the use of inheritance, generics, etc. Also note that you would need to implement other overrides to support restarting the flow, etc.

I should caution that I don’t know about the limits of state, perhaps I will discover a size restriction, we’ll see.

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