Category Archives: FormFlow

How to create dynamic FormFlow

You might’ve guessed it from the title – Yes, this is one of my no-nonsense posts. Strictly business and by business I mean code-talk.

This post is about a building block in Microsoft Bot Framework called FormFlow and namely how to add dynamic behavior to the flow when building bots using C#. If you are not familiar with FormFlow, I suggest your study the basics before reading further. Just the basics though, that’s enough.

Building simple FormFlows is… well, simple! This is a method that creates a basic form:

public static IForm<MyClass> BuildForm()
{
    var builder = new FormBuilder<MyClass>();

    return builder
        .Field(nameof(MyClass.Property1))
        .Field(nameof(MyClass.Property2))
        ...
        .Build();
    }
}

Not difficult at all! And you can influence the behavior using, for instance, property attributes like:

[Serializable]
public class MyClass
{
    [Prompt("What would you like the value of this property to be?")]
    public string Property1 { get; set; }

    ....
}

But what if you want to do some of the following:

  • Skip the questions you already know the answer to based on what the user said earlier?
  • Define the options presented for the user dynamically?
  • Change the way the question and options are presented to the user?
  • Validate the user’s response?
  • Customize the behavior of the form in the fly?

Some of the aforementioned things FormFlow tries to do for you automatically. However, usually to achieve a great experience you have to do a bit more work, and luckily, it is possible. See the resources available under Microsoft.Bot.Builder.FormFlow.Advanced namespace. One very useful class under there is called FieldReflector. Whilst you can still add ActiveDelegate and ValidateAsyncDelegate using the overloaded Field method in FormBuilder, FieldReflector allows you to do more:

.Field(new FieldReflector<MyClass>(nameof(MyClass.PropertyX))
    .SetType(typeof(MyClass.PropertyX))
    .SetActive((state) => SetFieldActive(state, nameof(MyClass.PropertyX)))
    .SetDefine(async (state, field) => await SetOptionsForFieldsAsync(state, nameof(MyClass.PropertyX), field))
    .SetAllowsMultiple(true) // Single selection vs. multi-selection
    .SetPrompt(new PromptAttribute("What type of values should this property have? {||}"))
    .SetValidate(async (state, value) => await ValidateResponseAsync(value, state, nameof(MyClass.PropertyX))))

…where SetFieldActive, SetOptionsForFieldAsync and ValidateResponseAsync are methods defined and implemented by the developer. See this class implementing the building of the form from my Dynamic FormFlow Sample. The snippet provides solution to all the questions presented in the bullet point list above. In case you are curious how this enables customizing the behavior in the fly, notice that you can run any arbitrary code in your response validation method (ValidateResponseAsync in the snippet).

In my Dynamic FormFlow Sample I use FormFlow to narrow down a spaceship selection from a static catalog of spaceships. Therefore, it is important that I don’t bother the user with unnecessary options; the user might’ve already told me that he/she is looking for a small ship and thus I shouldn’t later ask to select from options only available for large ships. That’s simply bad UX! Some queries I can skip in case there is only one option available. In my response validation method I do a search against the catalog using the details I’ve gathered from the user so far and  if the response is valid, I store the search results for later. With this approach I can narrow down the selection with as few questions as possible and without presenting the user with options that make no sense.

The caveats

Playing with FormFlow is not all bed of roses. I had to fight few errors, that for first seemed odd, until I got the hang of it. Here are some of the things to keep in mind:

  • After you build your form, it is built. It now exists. And you cannot really control the instance anymore. Why is this important to understand? Because you cannot say for certain when the methods (delegates) in your form are called. It’s now in the hands of the Bot Framework. So make sure your methods (delegates) work in any situation! For starters, have null checks.
  • Do not use the IList interface as a property type in the class where you are collecting user input (Spaceship.cs in the sample). It won’t work and you’ll get a FormCanceledException with “Cannot create an instance of an interface” message. Use List instead, it works.
  • Realize that you don’t have to do everything in the form; after the form is complete, you can continue with the data in a Dialog and ask further questions etc. That’s what I did in my sample, see SpaceshipSelectionDialog.cs.

And a top tip: If you are new to FormFlow, implement and test one complex field at a time.

So it’s a magic bullet?

No, it is not. FormFlow is a handy building block, but will not solve all problems. Duh.

If you feel like your FormFlow code is turning into horrible, uncontrollable mess and you feel like you need to compromise the UX, stop. Stop using FormFlow. You can do the same using Dialogs too and with really complex scenarios it will be – most likely – much easier too. FormFlow is a solution for fairly straightforward forms – it was never meant to be used with overly complex flows. Or at least I think that’s the case.

I should have a post about managing dialog flows out soon, but in the meanwhile, here’s the big secret: IDialogContext.Wait(<method name>) lets you define the next method that will process the next user response.