CHAPTER 6. Using FormFlow

In Chapter 5, you learned how to manage conversations with basic dialogs, IDialog<T>. This chapter continues the dialog discussion by introducing a different type of dialog, FormFlow. With an IDialog<T> dialog, you specify the flow of a conversation through a chain of methods where each method accepts an answer from its antecedent, and then asks another question until you’re finally at the end of the conversation. A FormFlow dialog is different, because rather than procedural logic, it’s more declarative. Essentially, you specify what you want the conversation to be, rather than how to perform the conversation.

To support the declarative nature of a FormFlow dialog, this chapter starts with the basics of how to create a FormFlow class. Next, you’ll learn how to apply attributes to control the chatbot-to-user interface. Some of these attributes affect how the chatbot asks a question, whereas others define the layout of the options, and there are other attributes to help in easy customization. FormFlow also has a pattern language that you’ll learn about to help guide the output of some attributes.

Out-of-the-Box Features

This chapter continues with the WineBot chatbot concept introduced in Chapter 5, Building Dialogs. FormFlow has several out-of-the-box features that streamline the appearance and user interface of a chatbot, which is different than the default text of IDialog<T>. This section walks you through the user experience for a FormFlow chatbot. One of the first things you’ll notice is that menus appear as cards with buttons, as shown in Figure 6-1.

Images

FIGURE 6-1 FormFlow Menu Options as Buttons.

Figure 6-1 shows a subset of the menu options. In this case they appear as button lists because that’s the default behavior of FormFlow in the Bot Emulator. Clicking a button results in the text associated with that button being sent to the chatbot – notice the blue responses.


Images Tip

Buttons are a nice user interface feature, because they let users click through options quickly instead of typing. The choice between buttons and text is yours and what you think works best for your chatbot. You’ll learn more about making this choice later in the chapter.


Instead of clicking buttons, the user is welcome to type, as shown in Figure 6-2.

Images

FIGURE 6-2 FormFlow Input Error Correction.

Notice how in Figure 6-2 the user input, med, resulted in an error message, explaining “med is not a rating option.” Later in this chapter, you’ll learn how to customize messages. FormFlow also lets users type in any case and it will still work. For example, notice how FormFlow recognized lower case medium as the Medium menu option.


Images Note

As the FormFlow developer, you don’t have to write code to respond to the user when they enter incorrect input. Much of the FormFlow interaction is out-of-the-box and automatic.


Another subtlety is when users type a single word matching one or two options, as shown in Figure 6-3.

Images

FIGURE 6-3 Typing Partial Options.

In Figure 6-3, both Champaign And Sparkling and Desert Sherry And Port have the word And in their titles. Typing and makes FormFlow respond with a choice among the two matching menu items. FormFlow has no idea what the word and means, except that it matches two menu options.

Also, notice that typing a single unique word, matching an entire word in a menu item, results in a match. That’s what happened when typing sherry, matching the Dessert Sherry And Port option. The reason typing a partial menu name didn’t work in Figure 6-3 is because med was not an entire word, but sherry is. Later in this chapter, you’ll learn how to specify terms, which can be parts of an answer, to match a menu item.

In addition to menu options, users can ask for help anywhere during the operation of a FormFlow, as shown in Figure 6-4.

Images

FIGURE 6-4 Getting Help From a FormFlow Chatbot.

Figure 6-4 shows that typing help gives a menu of options. Many of the options, like Back, Reset, or typing an option name support navigation. Users can even type go to wine or another field name to navigate. When the user is done filling out the form, FormFlow prompts with a summary of choices, as shown in Figure 6-5.

Images

FIGURE 6-5 Finishing a Form.

Users can type yes or no in response to the summary. As shown in Figure 6-5, Typing no makes FormFlow prompt for which item to change and takes the user there to make a new choice for that item. Typing or clicking the No Preference button takes the user back to the summary so they can eventually say yes and WineBot does the wine search.

That was the user experience. Now let’s look at the code that makes it work.

A Basic FormFlow Chatbot

Since FormFlow is a different type of dialog, a few things are different, like having a plain old class that doesn’t derive from an interface, properties that are an enum type, and how FormFlow starts up. This section walks through the basic WineBot code so you can see the differences between IDialog<T> and FormFlow code.

The Wine API Interface

This chapter uses the same WineApi class from Chapter 5. Because of that, we won’t need to describe it again. You can review the WineApi listing in Chapter 5 to get up to speed on what it does. Essentially, it’s an abstraction that makes a REST call to the Wine.com Wine API to perform serarches.


Images Note

You need your own Wine.com API key to run the code. You can obtain an API key from https://api.wine.com/ for free.


WineForm: A FormFlow Form

At its most basic level, a FormFlow form is a C# class containing properties. Each of the properties are enum types. This chapter creates a form, called WineForm, that also has a method for building the form. The properties hold the state, which are choices users make. Listing 6-1 through Listing 6-4 show the enums and the WineForm, which you can find in the WineBot2 project in the accompanying code. You’ll see how to use WineForm in a later section.

LISTING 6-1 WineBot – WineType Enum

namespace WineBot2
{
    public enum WineType
    {
        None = 0,
        RedWine = 124,
        WhiteWine = 125,
        ChampagneAndSparkling = 123,
        RoseWine = 126,
        DessertSherryAndPort = 128,
        Sake = 134
    }
}

Listing 6-1 contains the WineType enum, where each member has an explicit value. These values correspond to category numbers for the Wine API. While class state holds the enum type, the search logic can cast the enum to int to get the category.

LISTING 6-2 WineBot – RatingType Enum

namespace WineBot2
{
    public enum RatingType
    {
        None = 0,
        Low = 25,
        Medium = 50,
        High = 75
    }
}

Listing 6-2 breaks RatingType into 3 values: Low, Medium, and High. The Wine API takes a rating between 1 and 100, but I designed WineBot to work with fewer values. Like WineType, the code can cast the value to an int for search input.

LISTING 6-3 WineBot – StockingType Enum

namespace WineBot2
{
    public enum StockingType
    {
        None = 0,
        InStock,
        OutOfStock
    }
}

StockingType, in Listing 6-3 handles a bool condition where a wine is either in stock or not. The enum values are there to give the user a choice.

None is a common member of all the enums and explicitly set to 0 for documentation. FormFlow expects the None, with value 0, to be present. The None is optional, but if you choose to omit it, or are using an enum from a library that you can’t change, FormFlow requires that you make the property type nullable in the form. The DoSearch method, from Listing 6-4, uses this value to filter the search based on whether a matching wine is in stock.

Listing 6-4 WineBot – WineForm Class

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;

namespace WineBot2
{
    [Serializable]
    public class WineForm
    {
        public WineType WineType { get; set; }
        public RatingType Rating { get; set; }
        public StockingType InStock { get; set; }

        public IForm<WineForm> BuildForm()
        {
            return new FormBuilder<WineForm>()
                .Message(
                    “I have a few questions on your wine search. “ +
                    “You can type ”help” at any time for more info.”)
                .OnCompletion(DoSearch)
                .Build();
        }

        async Task DoSearch(IDialogContext context, WineForm wineInfo)
        {
            List[] wines =
                await new WineApi().SearchAsync(
                    (int)WineType, 
                    (int)Rating, 
                    InStock == StockingType.InStock, 
                    “”);

            string message;

            if (wines.Any())
                message = “Here are the top matching wines: “ +
                          string.Join(“, “, wines.Select(w => w.Name));
            else
                message = “Sorry, No wines found matching your criteria.”;

            await context.PostAsync(message);
        }
    }
}

FormFlow reads enum values to build menus. It adds spaces between words of individual enum members automatically. For example, RedWine becomes “Red Wine” in two words. FormFlow breaks words by capitalization. FormFlow also breaks words on underscores, so Red_Wine becomes “Red Wine” too. However, standard C# naming conventions prefer PascalCase, as you see in this book.


Images Note

C# has a couple common naming conventions where identifiers use either PascalCase or camelCase. PascalCase is used for identifiers like classes, properties, methods, and other class members. Just as in the name, PascalCase, the each word of the identifier is upper case, including the first. Camel case is for private fields, parameters, and variables. The camelCase format lower-cases the first word and capitalizes subsequent workds in the identifier. These are the only two types of identifier conventions you’ll see throughout the book.


WineForm, in Listing 6-4, is Serializable. Just like IDialog<T>, FormFlow types must be serializable so the Bot Framework can persist state on the Bot State Service between user activities. The properties; WineType, Rating, and InStock; hold the user’s choices. Notice that their types are the enum types from Listing 6-1 through Listing 6-3.


Images Note

Form state can also be fields, instead of properties. It’s your choice.


Essentially, FormFlow reads these properties, via reflection, and dynamically generates a question and menu based on the property name and type. The order of properties in the class determines the order FormFlow uses to present a question to the user. The logic to make this happen occurs in the BuildForm method, repeated below for your convenience:

        public IForm<WineForm> BuildForm()
        {
            return new FormBuilder<WineForm>()
                .Message(
                    “I have a few questions on your wine search. “ +
                    “You can type ”help” at any time for more info.”)
                .OnCompletion(DoSearch)
                .Build();
        }

BuildForm uses the FormBuilder class, which offers a fluent interface for building a form. The only requirement here is to instantiate FormBuilder and call Build, which performs all of the internal work to prepare the form as a dialog and returns an IForm<WineForm>. Later, you’ll see how WineBot wraps this IForm<WineForm> in a dialog to handle user input.

FormBuilder has several methods to customize how the form asks questions and you’ll see how to use each of these methods during a deep dive in Chapter 7, Customizing FormFlow. WineBot uses the Message and OnCompletion methods to produce a minimal, but working chatbot. Message defines the text to show the user at the beginning of interaction with WineForm, before asking the first question. This text displays in addition to the Welcome message WineBot displays on the ConversationUpdate Activity.

After the text, which is specified in Message displays, WineBot asks all of the questions corresponding to WineForm properties. After WineBot asks all questions and the user confirms that their choices are correct, FormBuilder executes the method referenced by OnCompletion, whose type is the OnCompletionAsyncDelegate delegate below:

public delegate Task OnCompletionAsyncDelegate<T>(IDialogContext context, T state);

According to OnCompletionAsyncDelegate, the referenced method receives an IDialogContext reference. As discussed in Chapter 5, Building Dialogs, IDialogContext gives access to several convenience methods that chatbots commonly need when responding to user input. In the current implementation the T in state will be WineForm, because that’s the instantiated type of FormBuilder. Here’s the DoSearch method, specified as the OnCompletionAsyncDelegate type argument that OnCompletion refers to:

        async Task DoSearch(IDialogContext context, WineForm wineInfo)
        {
            List[] wines =
                await new WineApi().SearchAsync(
                    (int)WineType, 
                    (int)Rating, 
                    InStock == StockingType.InStock, 
                    “”);

            string message;

            if (wines.Any())
                message = “Here are the top matching wines: “ +
                          string.Join(“, “, wines.Select(w => w.Name));
            else
                message = “Sorry, No wines found matching your criteria.”;

            await context.PostAsync(message);
        }

WineBot uses the same WineApi class as the one used in Chapter 5, but look at the SearchAsync arguments. Both WineType and Rating use a cast operator to convert their enum type values to an int, passing values that the Wine API recognizes. The inStock argument is bool, so a comparison operator works there. To keep this version of WineBot simple, the searchTerms is empty, but you’ll see an example of how to obtain text input later in this chapter.

DoSearch reads parameters directly from it’s containing type, because they’re available in the same instance. However, if DoSearch were static or resided in another class, the wineInfo parameter would have supplied access to properties. This might be a good argument for properties over fields if you prefer the encapsulation benefits.


Images Tip

You can also allow a user to select multiple items from a single option by specifying the type as a List. For example, List<WineType> WineTypes { get; set; } would allow a user to choose multiple wine types. In this case, you could read each value of WineTypes in the DoSearch method, Listing 6-4, to read each value and create a WineApi SearchAsync overload that accepts multiple values for wineType.


Finally, IDialogContext comes in handly by letting DoSearch communicate results with the user via PostAsync.

After DoSearch returns, WineForm is ready to go again from the beginning. Until then, FormFlow maintains the state of the conversation, as long as that state persists in the Bot State Service, as discussed in Chapter 4, Fine-Tuning Your Chatbot. This means that if the user stops communicating and then returns after a while, FormFlow will resume where it left off. Then the user has the option to request status, resume by answering remaining questions, or simply resetting.

That’s the basics of how a FormFlow form works. Next, you’ll learn how to use WineForm as the main dialog to WineBot.

Using WineForm as a Dialog

If you recall, from Listing 6-4, BuildForm returns an IForm<WineForm>. The next task for WineForm is to wrap that IForm<WineForm> in a dialog to handle user input activities. This section introduces the MessagesController for WineBot and shows how to use WineForm as a dialog, which you can see in Listing 6-5.

LISTING 6-5 WineBot – MessagesController Class

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using Microsoft.Bot.Connector;

namespace WineBot2
{
    [BotAuthentication]
    public class MessagesController : ApiController
    {
        public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
        {
            if (activity.Type == ActivityTypes.Message)
            {
                try
                {
                    await Conversation.SendAsync(activity, BuildWineDialog);
                }
                catch (FormCanceledException ex)
                {
                    HandleCanceledForm(activity, ex);
                }
            }
            else
            {
                await HandleSystemMessageAsync(activity);
            }

            return Request.CreateResponse(HttpStatusCode.OK);
        }

        IDialog<WineForm> BuildWineDialog()
        {
            return FormDialog.FromForm(
                new WineForm().BuildForm));
        }

        async Task HandleSystemMessageAsync(Activity message)
        {
            if (message.Type == ActivityTypes.ConversationUpdate)
            {
                const string WelcomeMessage =
                    “Welcome to WineBot! “ +
                    “Through a series of questions, WineBot can do a “ +
                    “search and return wines that match your answers. “ +
                    “You can type ”start” to get started.”;

                Func<ChannelAccount, bool> isChatbot =
                    channelAcct => channelAcct.Id == message.Recipient.Id;

                if (message.MembersAdded?.Any(isChatbot) ?? false)
                {
                    Activity reply = message.CreateReply(WelcomeMessage);

                    var connector = new ConnectorClient(new Uri(message.ServiceUrl));
                    await connector.Conversations.ReplyToActivityAsync(reply);
                }
            }
        }

        void HandleCanceledForm(Activity activity, FormCanceledException ex)
        {
            string responseMessage =
                $”Your conversation ended on {ex.Last}. “ +
                “The following properties have values: “ +
                string.Join(“, “, ex.Completed);

            var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
            var response = activity.CreateReply(responseMessage);
            connector.Conversations.ReplyToActivity(response);
        }
    }
}

The MessagesController.Post method in Listing 6-5 calls SendAsync if the activity type is ActivityTypes.Message. As in the examples of previous chapters, the first parameter of SendAsync is the activity passed to the Post method and the second is an IDialog<T>. The difference this time is that the previous examples created IDialog<object>, but now we’re working with an IDialog<WineForm>—the same WineForm shown in Listing 6-4.


Images Note

Whether derived directly from IDialog<T> or wrapped in an IDialog<T> derived type, like FormFlow, all dialogs are ultimately IDialog<T>. Because the term “dialog” is so generic, we still use the IDialog<T> in this book to differentiate the discussion of the difference of FormFlow and a method-based dialog of Chapter 6, Building Dialogs.


The BuildWineDialog method returns an IDialog<WineForm> that SendAsync uses. Inside of BuildWineDialog are a couple nested method calls that result in the IDialog<WineForm>, repeated here:

            return FormDialog.FromForm(
                new WineForm().BuildForm));

FormDialog implements IFormDialog<T>, which derives from IDialog<T>. As you learned in Chapter 5, IDialog<T> has a StartAsync member, which is the dialog entry point. So, FormDialog is managing all of the logic to manage dialog conversation. FromForm takes a BuildFormDelegate argument type, shown bhere:

public delegate IForm<T> BuildFormDelegate<T>() where T : class;

This is why BuildForm returns an IForm<T>, just like the BuildFormDelegate–so you can pass a BuildForm reference to FromForm. Internally, FromForm instantiates a new FormDialog, passing the BuildForm reference and invokes BuildForm along with other tasks to configure and manage conversation state. Coming back full-circle, this is how BuildWineDialog returns the IDialog<WineForm> that SendAsync needs.


Images Note

Listing 6-5 handles the ActivityTypes.ConversationUpdate activity, which was described in detail in Chapter 4, Fine-Tuning Your Chatbot. In fact, you’ll see this as a common practice throughout this book. The message recommends that you type start to begin the conversation. In reality, the user can type anything and this particular code will launch the FormFlow form as a dialog. In previous chapters, you saw how to intercept and handle commands, which is another way to handle this situation. In this case, WineBot gives the user specific guidance in order to be helpful.


Finally, one of the commands that a user can give to FormFlow is Quit, for when they want to exit before completing the form. When the user types quit, FormFlow throws FormCanceledException, and Table 6-1 shows available FormCanceledException members.

TABLE 6-1 FormCanceledException Members

Member

Description

Completed

Names of steps that the user has already provided answers for

Last

Step that the user was on

LastForm

Reference to the FormFlow form that the user was using

Table 6-1 uses the term Steps to refer to the state of the form when the user has sen the Quit command. In WineForm, those steps correspond to the properties that hold the user’s answers. The following HandleCanceledForm method, from Listing 6-5, shows one way to handle the FormCanceledException:

        void HandleCanceledForm(Activity activity, FormCanceledException ex)
        {
            string responseMessage =
                $”Your conversation ended on {ex.Last}. “ +
                “The following properties have values: “ +
                string.Join(“, “, ex.Completed);

            var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
            var response = activity.CreateReply(responseMessage);
            connector.Conversations.ReplyToActivity(response);
        }

When building the responseMessage string, HandleCanceledForm accesses ex.Last and ex.Completed properties. The ex.Last property tells where the user was at in the form. The ex.Completed contains a list of property names that the user already provided answers for, at the point in time that the user Quit. When handling FormCanceledException, you can also reference the form, like WineForm, instance to access the property values via the ex.LastForm property.

Now that the basics of FormFlow have been covered, let’s move on to more about attributes and other features that you can use with forms to enhance the user experience.

Enhancing FormFlow Conversations

In FormFlow, you can decorate the form class and its members with attributes. These attributes, outlined in Table 6-2, manage the appearance and layout of text for improving the chatbot user experience. The examples use WebBot3 in the accompanying source code, which is largely the same code as WebBot2, except for enhancements discussed in this section.

TABLE 6-2 FormFlow Attributes

Attribute

Description

Applies To

Describe

Overrides FormFlow’s default description

Enum, field, or property

Numeric

Specifies a min and max number

Field or property

Optional

Indicates that a value is not required

Field or property

Pattern

Uses a regular expression to validate value

Field or property

Prompt

Overrides default question

Field or property

Template

Defines text and patterns for how to prompt the user

Class, struct, field, or property

Terms

Allows alternate terms that can match choices

Field or property

The following sections explain each of the attributes from Table 6-2.


Images Note

The code in this section uses WineBot3 in the accompanying source code. It uses port 3978 to avoid confusion with WineBot2 that might have cached on port 3979.


The Describe Attribute

Each field, property, or enum value has a default description. FormFlow creates default descriptions by using capitalization, or underscores, to separate words of an identifier to use in help text, menu items, and questions. An example of a property is WineType, which becomes “Wine Type” in the Bot Emulator question. An example of an enum member is WineTypes.DessertSherryAndPort, which becomes “Dessert Sherry And Port” as a menu item.

Often, the default description is inadequate or you might want to show the user the description in a different way. This is where the Description attribute helps. The following example changes the default WineType property in WineForm:

        [Describe(Description=”Type of Wine”)]
        public WineType WineType { get; set; }

Now, the user sees “Type of Wine” instead of “Wine Type.” You can also change enum members, as shown in the following WineType enum example:

    public enum WineType
    {
        None = 0,
        RedWine = 124,
        WhiteWine = 125,
        ChampagneAndSparkling = 123,
        RoseWine = 126,
        [Describe(“Dessert, Sherry, and Port”)]
        DessertSherryAndPort = 128,
        Sake = 134
    }

The Describe attribute decorates the DessertSherryAndPort member, adding punctuation and lower casing and. When the user communicates with the chatbot, they’ll see the text from Describe attributes, as shown in Figure 6-6.

Images

FIGURE 6-6 Viewing Results of the Describe Attribute.

In Figure 6-6, I typed help at the menu for the WineForm.WineType property. You can see “type of wine” in both the help text and the menu title. You can also see the differences in the WineType members, decorated with the Describe attribute.


Images Note

Describe has other properties for Title, Subtitle, Image, and Message that are used for a more graphical presentation of the menu, which are covered in Chapter 10, Attaching Cards.


The Numeric Attribute

WineBot2 defined Rating as an enum with three choices, making it easier to integrate with default FormFlow behavior. This, however, doesn’t give the user sufficient flexibility because the Wine API takes a value between 1 and 100. This is where the Numeric attribute helps, as shown in the refactored Rating property, shown here:

        [Numeric(1, 100)]
        public int Rating { get; set; }

The Numeric attribute has two parameters: Min and Max. Here, you can see that Min is 1 and Max is 100. What this does is change from a button menu in the emulator to a text string, shown below, that let’s the user enter a number:

Please enter a number between 1 and 100 for rating (current choice: 0).

Now the user can enter a real number and FormFlow validates that the number is greater than or equal to 1 or less than or equal to 100. Also, notice how the current choice, above, is 0. That’s because the Rating property hasn’t been set and 0 is the default value for an int. You can initialize the property to a default value to avoid any problems with this.

The Optional Attribute

FormFlow normally performs validation, making all fields/properties required. There might be times, however, when you won’t require the user to enter a value. That’s the case with InStock, shown here:

        [Optional]
        public StockingType InStock { get; set; }

Here the Optional attribute allows the user to bypass entering a value. When you do this, FormFlow shows an additional No Preference option. Selecting No Preference returns the enum option with a value of 0 (None in WineBot) or null if the property type is nullable.

The Pattern Attribute

You can allow the user to enter text by using the Pattern attribute. The Pattern attribute accepts a single required parameter, which is a regular expression string. The following property demonstrates how to use the Pattern attribute:

        [Pattern(@”.+@.+..+”)]
        public string EmailAddress { get; set; }

The EmailAddress property should receive a properly formatted email address. While the regular expression used in this example isn’t very sophisticated, it demonstrates how you add a string parameter to the Pattern attribute. Sometimes, you might want a purely free-form string field, which you can do with the following example:

        [Pattern(“.*”)]
        public string SearchTerms { get; set; }

In the SearchTerms example, the input must not have any constraints, and the regular expression reflects that a user can provide any input they desire.

The Prompt Attribute

Sometimes the default question doesn’t necessarily make sense for the current entry, so you want to add additional info, or use something that reflects the personality of the chatbot. The Prompt attribute lets you do this by changing the question text, as the following example demonstrates:

        [Numeric(1, 100)]
        [Prompt(
            “Please enter a minimum rating (from 1 to 100).”,
            “What rating, 1 to 100, would you like to search for?”)]
        public int Rating { get; set; }

The Prompt attribute for Rating has two arguments, representing different ways to ask the same question. This lets a chatbot vary the conversation, which might be more interesting for the user. You have a choice to add only one string or multiple strings, beyond the two in the Rating example.

The Terms Attribute

FormFlow out-of-the-box is very flexible and has a lot of options. When the user types, however, they must spell everything correctly or receive an error message. Sometimes, you might want to select an item if the user types part of the answer, an abbreviation, or a common misspelling. The Terms attribute demonstrated here, shows how to give the user flexibility in the answers they provide:

    public enum WineType
    {
        None = 0,
        RedWine = 124,
        WhiteWine = 125,

        [Terms(“[S|s|C|c]hamp.*”, MaxPhrase=2)]
        ChampagneAndSparkling = 123,

        RoseWine = 126,

        [Terms(
            “desert”, “shery”, “prt”,
            “dessert”, “sherry”, “port”,
            “Dessert, Sherry, and Port”)]
        [Describe(“Dessert, Sherry, and Port”)]
        DessertSherryAndPort = 128,

        Sake = 134
    }

In the example, there are two WineType enum members decorated with the Terms attribute: ChampagneAndSparkling and DessertSherryAndPort. The Terms attribute on DesertSherryAndPort has a list of seven different terms, and the user’s input will pass if it matches any of those terms. As you can see, there are a few misspellings along with the full spelling that matches the Describe attribute, just in case the user has trouble and wants to make sure they type the exact words.

The Terms attribute for ChampagneAndSparkling has two parameters. The first is a regular expression that assumes it might be easy for the user to misspell champagne. The second parameter, MaxPhrase, supports auto-generation capability where FormFlow creates multiple phrases that would match the value. In this example, MaxPhrase is 2, indicating that the user has two additional variations that could potentially match their input.

Both regular expressions and MaxPhrase increase the chances that a given option will be selected. However, you might want to adjust this based on what other options are available. When a user’s input matches multiple items, FormFlow shows potential matches and asks the user which item they really wanted. e.g. notice that I did’t have Terms attributes, with MaxPhrase on RedWine, WhiteWine, and RoseWine properties because typing wine would result in FormFlow querying the user for which wine, which is half the list. Imagine if there were even more matches in a long list. You could probably see where too many matches of this type might introduce friction for the user. So, testing and limiting options appropriately might improve the user experience.

Combined, alternate terms, regular expressions, and auto-generation give a chatbot a lot of flexibility in being able to recognize what the user wants.


Images Tip

This might seem like a lot of fuss about allowing the user to type in text when buttons are more efficient. However, remember that one of the great features of the Bot Framework is in giving you the opportunity to write a chatbot one time and deploy to multiple platforms. There are some channels, like SMS and email that don’t offer buttons, which means that text is the only option. Another consideration is that an entire generation of users has grown up communicating with messaging applications and text is second nature to them, as well as other power users who have embraced the conversational nature of messaging applications. While buttons might be the default interface when it’s available, it’s best practice to also offer the most pleasant user experience via text. In the longer term, the advent of voice will make this obvious.


The remaining attribute that I have yet to cover is Template. Both Prompt and Template have sophisticated options, including the ability to use a pattern language, which you’ll learn about next.

Advanced Templates and Patterns

FormFlow has an advanced templating system that supports overriding its default messaging by targeting where a message can be used, and employing a pattern language to customize a message. In this section, you’ll learn about the FormFlow pattern language and how both Prompt and Template attributes use it. You’ll also learn about the advanced features of Template attributes and various options that enhance the chatbot user experience.


Images Note

When discussing patterns, I’ll use the term field to refer to either C# fields or properties, both of which work the same for FormFlow attributes and associated patterns.


Pattern Language

The FormFlow pattern language is a set of formatting items for a string that specifies items like substitution options or question layout. The patterns fall into a couple different categories: field question display and template usage placeholders. This section describes the field question display patterns, leaving template usage placeholders to a later section.

The following Prompt attribute for the WineType property shows how pattern language can be used, and starts off with the description and value of the current field for WineType:

        [Describe(Description=”Type of Wine”)]
        [Prompt(“Which {&} would you like? (current value: {})”)]
        public WineType WineType { get; set; }

In the pattern string for the Prompt attribute above, the {&} is a placeholder for the property description, which is “type of wine” as specified in the Describe attribute. Even though the Description property for the Describe attribute is upper case, FormFlow lower cases the description when it’s in the middle of a sentence. Without the Describe attribute, FormFlow would have substituted the default “wine type” into the placeholder. The {} is a placeholder for the value of the current field. Now, the question “Which type of wine would you like? (current value: Unspecified)” displays instead the default “Please select a type of wine.”. The first time through, the current value is unspecified, but if the user picks Red Wine and then re-displays (by typing WineType), the new message is “Which type of wine would you like? (current value: Red Wine).” Here again, the Describe attribute of an enum member will override the default FormFlow generated description.

In addition to the current field, there are patterns to display the description and value of a separate field. The patterns for the Rating field below combine all of the description/value patterns:

        [Numeric(1, 100)]
        [Prompt(
            “Please enter a minimum rating (from 1 to 100).”,
            “What rating, 1 to 100, would you like to search for?”,
            “Minimum {&} (selected {&WineType}: {WineType}, current rating: {:000})”)]
        public int Rating { get; set; }

In this example, the third prompt string is “Minimum {&} (selected {&WineType}: {WineType}, current rating: {:000})”. As mentioned earlier, {&} is the description of the current field, but {&WineType} is the description of the WineType field, demonstrating how to display the description of a field other than the current field. For displaying values, {WineType} is the value of the WineType field and {:000} is the value of the current, Rating, field. In the previous example, for WineType, the current field value is empty braces, {}. However, contents of the value pattern for the rating field has a format specifier :000, indicating that the format is 3 digits, left-padded with 0. Also, notice the colon prefix to the pattern, which is required. To summarize, the ampersand is a placeholder for field description and no ampersand is a placeholder for field value.

In the previous description, field description/value patterns replaced the default FormFlow questions. However, they also replaced the menus with nothing. Essentially, FormFlow asked the question without showing any options. The user could type help and see the options, but that might not be the best user experience. The next example shows how to add field members back to the question and how to change the appearance of those options:

        [Describe(Description=”Type of Wine”)]
        [Prompt(“Which {&} would you like? (current value: {}) {||}”)]
        public WineType WineType { get; set; }

This example is nearly identical to the WineType Prompt above, except for the {||} appended to the end of the string, indicating that the UI should display menu options.


Images Note

The menu pattern {||} shows buttons in the Bot Emulator and most messaging channels, but that isn’t guaranteed. Remember that the Bot Framework is multi-platform and it’s up to the channel platform on whether buttons appear. Additionally, some channels, like SMS, can’t display buttons and the user sees a text menu instead.


The remaining patterns are designed to work with Templates, which you’ll learn about next.

Basic Templates

FormFlow has an extensive set of standard messaging that covers questions, help, status, and more. As you learned in earlier sectons, the Prompt attribute is designed to help customize messaging, but this was focused on fields. There’s another attribute, Template, that allows even further customization of FormFlow messaging. Template attributes, like Prompt, can decorate fields, but also classes and structs. Here’s an example of how a Template attribute can decorate a class:

    [Serializable]
    [Template(TemplateUsage.String, “What {&} would you like to enter?”)]
    public class WineForm
    {
    }

In this example, the Template attribute has two parameters, the TemplateUsage enum and pattern string. The TemplateUsage.String means that this attribute applies to all fields in this class whose type is string. The pattern string replaces the default FormFlow question for string fields. Because both SearchTerms and EmailAddress are type string, this customizes their question text. To be complete, any other fields of different type are not affected by a Template attribute with TemplateUsage.String. The next sections explain more of the template usage and other available options.

Template Usage

The TemplateUsage enum, Listing 6-6, shows that the Bot Framework has an extensive set of messages available for customization. At first glance, this list might appear quite daunting. However, there are some common patterns that can help break down and categorize members to help digest what is available.

First, notice that there are several members associated with .NET types, such as Bool, DateTime, Double, and more. These members help customize questions associated with a type – just like TemplateUsage.String customized all fields of type string in the previous example. Additionally, each of the members for a type has a corresponding member with a Help suffix, <type>Help, such as in BoolHelp, DateTimeHelp, DoubleHelp, and more. Each of the <type>Help members help customize the FormFlow Help message (when the user types help) for questions whose type is <type>.

Another set of members customize FormFlow navigation and commands, such as Help and StatusFormat.

LISTING 6-6 The Bot Framework TemplateUsage Enum

    /// <summary>
    /// All of the built-in templates.
    /// </summary>
    /// <remarks>
    /// A good way to understand these is to look at the default templates defined in <see cref=”FormConfiguration.Templates”/>
    /// </remarks>
    public enum TemplateUsage
    {
        /// <summary>   An enum constant representing the none option. </summary>
        None,

        /// <summary>
        /// How to ask for a boolean.
        /// </summary>
        Bool,

        /// <summary>
        /// What you can enter when entering a bool.
        /// </summary>
        /// <remarks>
        /// Within this template {0} is the current choice if any and {1} is no preference if optional. 
        /// </remarks>
        BoolHelp,

        /// <summary>
        /// Clarify an ambiguous choice.
        /// </summary>
        /// <remarks>This template can use {0} to capture the term that was ambiguous.</remarks>
        Clarify,

        /// <summary>
        /// Default confirmation.
        /// </summary>
        Confirmation,

        /// <summary>
        /// Show the current choice.
        /// </summary>
        /// <remarks>
        /// This is how the current choice is represented as an option.  
        /// If you change this, you should also change <see cref=”FormConfiguration.CurrentChoice”/>
        /// so that what people can type matches what you show.
        /// </remarks>
        CurrentChoice,

        /// <summary>
        /// How to ask for a <see cref=”DateTime”/>.
        /// </summary>
        DateTime,

        /// <summary>
        /// What you can enter when entering a <see cref=”DateTime”/>.
        /// </summary>
        /// <remarks>
        /// Within this template {0} is the current choice if any and {1} is no preference if optional. 
        /// </remarks>
        /// <remarks>
        /// This template can use {0} to get the current choice or {1} for no preference if field is optional.
        /// </remarks>
        DateTimeHelp,

        /// <summary>
        /// How to ask for a double.
        /// </summary>
        /// <remarks>
        /// Within this template if numerical limits are specified using <see cref=”NumericAttribute”/>, 
        /// {0} is the minimum possible value and {1} is the maximum possible value.
        /// </remarks>
        Double,

        /// <summary>
        /// What you can enter when entering a double.
        /// </summary>
        /// <remarks>
        /// Within this template {0} is the current choice if any and {1} is no preference if optional. 
        /// If limits are specified through <see cref=”NumericAttribute”/>, then {2} will be the minimum possible value 
        /// and {3} the maximum possible value.
        /// </remarks>
        /// <remarks>
        /// Within this template, {0} is current choice if any, {1} is no preference for optional  
        /// and {1} and {2} are min/max if specified.
        /// </remarks>
        DoubleHelp,

        /// <summary>
        /// What you can enter when selecting a single value from a numbered enumeration.
        /// </summary>
        /// <remarks>
        /// Within this template, {0} is the minimum choice. {1} is the maximum choice 
        /// and {2} is a description of all the possible words.
        /// </remarks>
        EnumOneNumberHelp,

        /// <summary>
        ///  What you can enter when selecting multiple values from a numbered enumeration.
        /// </summary>
        /// <remarks>
        /// Within this template, {0} is the minimum choice. {1} is the maximum choice 
        /// and {2} is a description of all the possible words.
        /// </remarks>
        EnumManyNumberHelp,

        /// <summary>
        /// What you can enter when selecting one value from an enumeration.
        /// </summary>
        /// <remarks>
        /// Within this template, {2} is a list of the possible values.
        /// </remarks>
        EnumOneWordHelp,

        /// <summary>
        /// What you can enter when selecting mutiple values from an enumeration.
        /// </summary>
        /// <remarks>
        /// Within this template, {2} is a list of the possible values.
        /// </remarks>
        EnumManyWordHelp,

        /// <summary>
        /// How to ask for one value from an enumeration.
        /// </summary>
        EnumSelectOne,

        /// <summary>
        /// How to ask for multiple values from an enumeration.
        /// </summary>
        EnumSelectMany,

        /// <summary>
        /// How to show feedback after user input.
        /// </summary>
        /// <remarks>
        /// Within this template, unmatched input is available through {0}, but it should be wrapped in 
        /// an optional {?} in 
ef patterns in case everything was matched. 
        /// </remarks>
        Feedback,

        /// <summary>
        /// What to display when asked for help.
        /// </summary>
        /// <remarks>
        /// This template controls the overall help experience.  {0} will be recognizer specific help and {1} will be command help.
        /// </remarks>
        Help,

        /// <summary>
        /// What to display when asked for help while clarifying. 
        /// </summary>
        /// <remarks>
        /// This template controls the overall help experience.  {0} will be recognizer specific help and {1} will be command help.
        /// </remarks>
        HelpClarify,

        /// <summary>
        /// What to display when asked for help while in a confirmation.
        /// </summary>
        /// <remarks>
        /// This template controls the overall help experience.  {0} will be recognizer specific help and {1} will be command help.
        /// </remarks>
        HelpConfirm,

        /// <summary>
        /// What to display when asked for help while navigating.
        /// </summary>
        /// <remarks>
        /// This template controls the overall help experience.  {0} will be recognizer specific help and {1} will be command help.
        /// </remarks>
        HelpNavigation,

        /// <summary>
        /// How to ask for an integer.
        /// </summary>
        /// <remarks>
        /// Within this template if numerical limits are specified using <see cref=”NumericAttribute”/>, 
        /// {0} is the minimum possible value and {1} is the maximum possible value.
        /// </remarks>
        Integer,

        /// <summary>
        /// What you can enter while entering an integer.
        /// </summary>
        /// <remarks>
        /// Within this template, {0} is current choice if any, {1} is no preference for optional  
        /// and {1} and {2} are min/max if specified.
        /// </remarks>
        IntegerHelp,

        /// <summary>
        /// How to ask for a navigation.
        /// </summary>
        Navigation,

        /// <summary>
        /// Help pattern for navigation commands. 
        /// </summary>
        /// <remarks>
        /// Within this template, {0} has the list of possible field names.
        /// </remarks>
        NavigationCommandHelp,

        /// <summary>
        /// Navigation format for one line in navigation choices.
        /// </summary>
        NavigationFormat,

        /// <summary>
        /// What you can enter when navigating.
        /// </summary>
        /// <remarks>
        /// Within this template, if numeric choies are allowed {0} is the minimum possible choice 
        /// and {1} the maximum possible choice. 
        /// </remarks>
        NavigationHelp,

        /// <summary>
        /// How to show no preference in an optional field.
        /// </summary>
        NoPreference,

        /// <summary>
        /// Response when an input is not understood.
        /// </summary>
        /// <remarks>
        /// When no input is matched this template is used and gets {0} for what the user entered.
        /// </remarks>
        NotUnderstood,

        /// <summary>
        /// Format for one entry in status.
        /// </summary>
        StatusFormat,

        /// <summary>
        /// How to ask for a string.
        /// </summary>
        String,

        /// <summary>
        /// What to display when asked for help when entering a string. 
        /// </summary>
        /// <remarks>
        /// Within this template {0} is the current choice if any and {1} is no preference if optional. 
        /// </remarks>
        StringHelp,

        /// <summary>
        /// How to represent a value that has not yet been specified.
        /// </summary>
        Unspecified
    };

Another example of TemplateUsage is in handling situations where the chatbot doesn’t understand what the user wants. The following template shows how to use TemplateUsage.NotUnderstood:

    [Template(TemplateUsage.NotUnderstood,
        “Sorry, I didn’t get that.”,
        “Please try again.”,
        “My apologies, I didn’t understand ‘{0}’.”,
        “Excuse me, I didn’t quite get that.”,
        “Sorry, but I’m a chatbot and don’t know what ‘{0}’ means.”)]

Notice that this Template attribute has several pattern strings. Imagine a user interacting with a chatbot and continuously receiving the same message. After a few times, the user might become frustrated and stop using the chatbot, but by varying the message, they might not feel like they’re stuck and pay attention to the message.

Notice the {0} specifier in a couple of the strings. This holds the value that the user typed in. For example, if the user was entering a response for the WineType field and typed rse, they might receive the My apologies, “I didn’t understand ‘rse’.” message, highlighting that they misspelled rose. To know what values are available, examine the TemplateUsage enum in Listing 6-6 for the NotUnderstood member:

        /// <summary>
        /// Response when an input is not understood.
        /// </summary>
        /// <remarks>
        /// When no input is matched this template is used and gets {0} for what the user entered.
        /// </remarks>
        NotUnderstood,

The comments explain that {0} is the placeholder for what the user typed in. Additionally, this message will appear in the Visual Studio IDE Intellisense when typing the dot after TemplateUsage.


Images Tip

To learn what default message matches a TemplateUsage member, visit the open source BotBuilder project code at https://github.com/Microsoft/BotBuilder/. Then find FormConfiguration.Templates and look for the mapping of the TemplateUsage to resource name. Then map the resource name to the *.resx file in the BotBuilder source code Resource folder.


Now that you know about TemplateUsage and placeholders, here’s a useful pattern, demonstrating how to customize a help message:

        [Pattern(“.*”)]
        [Template(
            TemplateUsage.StringHelp,
            “Additional words to filter search {?{0}}”)]
        public string SearchTerms { get; set; }

Notice the {?{0}} appended to the Template attribute string. This is a conditional placeholder, symbolized by the leading question mark. The {0} placeholder in TemplateUsage for StringHelp is for the current choice. If this is the first time the user encounters this question, there is not a current choice and the conditional placeholder hides the current choice. However, if the user has specified a value for SearchTerms, they will see the current choice. Since this is a <type>Help TemplateUsage member, the user sees this message when they are on the SearchTerms question and they type help.

Also, the previous example demonstrated how to apply a Template attribute to a single field, rather than a whole class.

Template Options

In addition to patterns and TemplateUsage, FormFlow offers a set of options to customize messages even further. Listing 6-7 shows available options via the TemplateBaseAttribute class. Both Prompt and Template attributes derive from TemplateBaseAttribute and inherit these options.

LISTING 6-7 The Bot Framework TemplateBaseAttribute Class

    /// <summary>
    /// Abstract base class used by all attributes that use 
ef patterns.
    /// </summary>
    public abstract class TemplateBaseAttribute : FormFlowAttribute
    {
        /// <summary>
        /// When processing choices {||} in a 
ef patterns string, provide a choice for the default value if present.
        /// </summary>
        public BoolDefault AllowDefault { get; set; }

        /// <summary>  
        /// Control case when showing choices in {||} references in a 
ef patterns string. 
        /// </summary>
        public CaseNormalization ChoiceCase { get; set; }

        /// <summary>
        /// Format string used for presenting each choice when showing {||} choices in a 
ef patterns string.
        /// </summary>
        /// <remarks>The choice format is passed two arguments, {0} is the number of the choice 
        /// and {1} is the field name.</remarks>
        public string ChoiceFormat { get; set; }

        /// <summary>   
        /// When constructing inline lists of choices using {||} in a 
ef patterns string, the string used before the last choice. 
        /// </summary>
        public string ChoiceLastSeparator { get; set; }

        /// <summary>  
        /// When constructing inline choice lists for {||} in a 
ef patterns string 
        /// controls whether to include parentheses around choices. 
        /// </summary>
        public BoolDefault ChoiceParens { get; set; }

        /// <summary>
        /// When constructing inline lists using {||} in a 
ef patterns string, 
        /// the string used between all choices except the last. 
        /// </summary>
        public string ChoiceSeparator { get; set; }

        /// <summary>
        /// How to display choices {||} when processed in a 
ef patterns string.
        /// </summary>
        public ChoiceStyleOptions ChoiceStyle { get; set; }

        /// <summary>
        /// Control what kind of feedback the user gets after each input.
        /// </summary>
        public FeedbackOptions Feedback { get; set; }

        /// <summary>
        /// Control case when showing {&amp;} field name references in a 
ef patterns string.
        /// </summary>
        public CaseNormalization FieldCase { get; set; }

        /// <summary>
        /// When constructing lists using {[]} in a 
ef patterns string, the string used before the last value in the list.
        /// </summary>
        public string LastSeparator { get; set; }

        /// <summary>
        /// When constructing lists using {[]} in a 
ef patterns string, the string used between all values except the last.
        /// </summary>
        public string Separator { get; set; }

        /// <summary>
        /// Control case when showing {} value references in a 
ef patterns string.
        /// </summary>
        public CaseNormalization ValueCase { get; set; }
    }

Each of the options are named parameters for the Prompt or Template attributes. Here’s an example that customizes the WineType options format:

        [Describe(Description=”Type of Wine”)]
        [Prompt(
            “Which {&} would you like? (current value: {}) {||}”, 
            ChoiceStyle = ChoiceStyleOptions.PerLine)]
        public WineType WineType { get; set; }

The ChoiceStyleOptions enum has several members for formatting the menu options. In this example, ChoiceStyleOptions.PerLine causes the output to be text, where each entry is numbered and is on a new line, as shown in Figure 6-7.

Images

FIGURE 6-7 ChoiceStyleOptions.PerLine Prompt Attribute Option Showing Text Options on Separate Lines.

Listing 6-8 has the entire update of WineForm that includes all of the attributes and patterns demonstrated in this section.

LISTING 6-8 The WineForm Class with Attributes and Patterns

using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace WineBot3
{
    [Serializable]
    [Template(TemplateUsage.String, “What {&} would you like to enter?”)]
    [Template(TemplateUsage.NotUnderstood,
        “Sorry, I didn’t get that.”,
        “Please try again.”,
        “My apologies, I didn’t understand ‘{0}’.”,
        “Excuse me, I didn’t quite get that.”,
        “Sorry, but I’m a chatbot and don’t know what ‘{0}’ means.”)]
    public class WineForm
    {
        [Describe(Description=”Type of Wine”)]
        [Prompt(
            “Which {&} would you like? (current value: {}) {||}”, 
            ChoiceStyle = ChoiceStyleOptions.PerLine)]
        public WineType WineType { get; set; }

        [Numeric(1, 100)]
        [Prompt(
            “Please enter a minimum rating (from 1 to 100).”,
            “What rating, 1 to 100, would you like to search for?”,
            “Minimum {&} (selected {&WineType}: {WineType}, current rating: {:000})”)]
        public int Rating { get; set; }

        [Optional]
        public StockingType InStock { get; set; }

        [Pattern(“.*”)]
        [Template(
            TemplateUsage.StringHelp,
            “Additional words to filter search {?{0}}”)]
        public string SearchTerms { get; set; }

        [Pattern(@”.+@.+..+”)]
        public string EmailAddress { get; set; }

        public IForm<WineForm> BuildForm()
        {
            return new FormBuilder<WineForm>()
                .Message(
                    “I have a few questions on your wine search. “ +
                    “You can type ”help” at any time for more info.”)
                .OnCompletion(DoSearch)
                .Build();
        }

        async Task DoSearch(IDialogContext context, WineForm wineInfo)
        {
            List[] wines =
                await new WineApi().SearchAsync(
                    (int)wineInfo.WineType,
                    wineInfo.Rating,
                    wineInfo.InStock == StockingType.InStock,
                    wineInfo.SearchTerms);

            string message;

            if (wines.Any())
                message = “Here are the top matching wines: “ +
                          string.Join(“, “, wines.Select(w => w.Name));
            else
                message = “Sorry, No wines found matching your criteria.”;

            await context.PostAsync(message);
        }
    }
}

While this is a lot of information on FormFlow customization, Listing 6-8 shows how straight forward it is to customize the user experience for a chatbot with attributes and patterns.


Choosing Between FormFlow and IDialog<T>

At this point in time, you might be wondering how to choose between FormFlow and IDialog<T>. This section examines the characteristic of each to help you decide which is the best tool for the job.

As you’ve just seen, FormFlow is very easy for building Q&A chatbots. It’s very simple to get started and relatively easy to customize with attributes. This is a very powerfull capability compared to the small amount of associated coding. FormFlow excels in situations where the chatbot needs to collect information from a user. This chapter showed how to collect filters for a wine search. Other examples are when you need a user to fill out a form to order food or another service, take a survey, or complete an application for a job.

An IDialog<T> is good for more free-form interaction. There isn’t a specified sequence for how the user interacts with the chatbot. One minute they could be asking about one thing and the next minute move to another subject. You can put logic inside of callbacks to take the logical path through a conversation that the user wants to go. Examples might be browsing a store catalog, playing a game, or asking a vendor what type of services they provide and their hours of operation.

There isn’t always a definitive rule on when to use one over the other and you’ll no doubt find gray areas in between and wonder which way to go. e.g. In the case of WineBot, you could go either way without too much trouble. However, lets also examine some potentially extreme cases of when not to use a specific dialog type. One example of when a IDialog<T> might not work is for a survey. Imagine writing a dialog where there’s a separate method for each question. The chatbot asks a question, the user responds, the chatbot obtains the result and saves in a property, and then moves on to ask the next question. This would result in a lot of repetitive code doing the same thing, taking more time than required, and potentially introducing bugs. In this example, FormFlow would have been the better choice.

In Chapter 7, Customizing FormFlow, you’ll learn how to specify options where you don’t want to ask a user a specific question, how to validate input, and how to dynamically present input. While those will be useful in scenarios where FormFlow does well, imagine writing a toy store chatbot. The dialog would ask what type of toy the user is interested in, show a list, put the selected toy in a cart, and take the user to checkout. If the user wants to modify their cart or select another toy, they could use the built-in FormFlow navigation. You might be able to write some adaptive logic for all of these conversation paths, but it would soon become unwieldy. Unless the process of buying something is so simple, such as when there’s a small number of options and the user is expected to only buy one and move forward in sequence, the amount of work to get FormFlow to do this would not be worth it. In this example, an IDialog<T> would have been a better choice.

You’ll want to look at the task your chatbot is designed to accomplish and use the right tool for the right job.


Summary

FormFlow offers an easy way to quickly build a chatbot for question and answer scenarios. This chapter started by showing the FormFlow user experience and how it provides a lot of default functionality out-of-the-box.

A FormFlow form is relatively simple to construct as a class with fields and properties. You need to add FormBuilder code, that the Post method configures to handle user activities. That’s what tells the chatbot to hand over the conversation to your FormFlow class.

You’ll probably need to customize the user experience, and FormFlow offers several attributes to do so in a declarative manner. You learned how the Describe attribute changes a field name, the Numeric attribute specifies bounds for integers and floating point numbers, the Optional attribute allows users to skip a field, and the Pattern attribute constrains a field to match a regular expression. There’s even more customization available through the Prompt and Template attributes. In this chapter you also learned how to apply patterns and additional options to enhance the user experience.

This chapter provided the essentials of how to create a powerful user experience with FormFlow, and the next chapter builds on this with more advanced FormFlow features.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.223.123