FormFlow In Bot Framework

What is FormFlow?

FormFlow is an automatic conversion with less effort. It has more options with less effort. It allows you to quickly manage a guided conversation based upon guidelines, which you specify. It is used to gather information from a user with a small amount of code. It can be combined with dialogs to increase its functionality. It is less flexible compared to dialogs.

Features of FormFlow

  • Understand both number and textual entries.
  • Provides clear guidance and help.
  • Asks clarifying question when required.
  • Gives feedback on what is understood and what is not understood.
  • Message during the process of filling in a form.
  • Allows navigating between the steps.
  • Custom prompts per field.
  • Templates to be used when automatically generating prompts or help.
  • Fields that are optional.
  • Conditional fields.
  • Binds of model.
  • Value validation.
Customizing our forms
  1. Attributes
  2. Custom business login blocks
  3. Form builder class

Attributes

There are many attributes of FormFlow

Describe 

Change how a field or a value is shown in the text.
For example: [Describe("My name is {&}")]

Numeric

Provide limits on the values accepted in a number field.
For example: [Numeric(10, 50)]

Optional

Mark a field is as optional, which means that one choice is not to supply a value
For example: [Optional]

Pattern

Define a regular expression to validate a string field.
For example: [Pattern(@"^[\w\s]+$")]

Prompt

Define a prompt to use when asking for a field.
For example: [Prompt("What is your current location?")]

Template

Define a template, which is used to generate the prompts or the value in the prompts.
For example: [Template( TemplateUsage.EnumSelectOne, "Which temperature {&} would you like?{||}", ChoiceStyle = ChoiceStyleOptions.PerLine)]

Terms 

Define the input terms, which matches a field or value.

For example,

  1. Public enum SizeOption {  
  2.     [Terms("s""m""l")]  
  3.     small, medium, large  
  4. };  

Custom business logic blocks

  • Allows you to inject in before the getter or setter for a property.
  • More options.

Form Builder

  • Brings it all together.
  • Fluent API Interface.
  • Can specify attributes and custom logic.

Property of forms and fields

These are the properties to represent the data that Bot will collect from the user.

  • Integral (sbyte, byte, short, ushort, int, uint, long, ulong)
  • Floating point (float, double)
  • String
  • DateTime
  • Enumeration
  • List of enumerations

Note

Any datatype can be nullable.

How to create FormFlow?

We need to specify some information that Bot needs to collect from the user.

Now, take one example to demonstrate how can we create FormFlow? You can easily understand FormFlow, using the example.

For example

If I want to create a Bot for flight inquiry, I need to define a form, which contains fields for the data that Bot needs to fulfill the inquiry. We can define the form by creating C# class, which contains one or more public properties to represent the data, which Bot will collect from the user.

Step 1

As FormFlow are bound to a model, we will create a model class first, as shown below.

Add new folder name as Model and add new class. In this example, I have created a class as EnquiryForm.cs

EnquiryForm.cs

  1. using Microsoft.Bot.Builder.Dialogs;  
  2. using Microsoft.Bot.Builder.FormFlow;  
  3. using System;  
  4.   
  5. namespace BotFlightEnquiry.Models  
  6. {  
  7.     [Serializable]  
  8.     public class EnquiryForm  
  9.     {  
  10.         public FlightTypes flightTypes;  
  11.         public ClassTypes classTypes;  
  12.         [Optional]  
  13.         public IsMeal isMeal;  
  14.         public FoodMenu foodMenu;  
  15.         public DateTime? DateOfJurney;  
  16.         [Numeric(1, 5)]  
  17.         public int? NumberOfAdult;  
  18.          
  19.         public int? NumberOfChild;  
  20.   
  21.         public static IForm<EnquiryForm> BuildForm()  
  22.         {     
  23.             return new FormBuilder<EnquiryForm>()  
  24.                 .Message("Welcome to the flight reservation BOT.")  
  25.                  .OnCompletion(async (context, profileForm) =>  
  26.                  {  
  27.                      // Tell the user that the form is complete  
  28.                      await context.PostAsync("Thanks for Enquiry.");  
  29.                  })  
  30.                 .Build();  
  31.         }  
  32.     }  
  33.   
  34.   
  35.     [Serializable]  
  36.     public enum FlightTypes  
  37.     {  
  38.         International = 1, Domestic = 2  
  39.     }  
  40.     [Serializable]  
  41.     public enum ClassTypes  
  42.     {  
  43.         FirstClass = 1,  
  44.         Business = 2,  
  45.         Economy = 3  
  46.     }  
  47.     [Serializable]  
  48.     public enum IsMeal  
  49.     {  
  50.         Yes = 1,  
  51.         No = 2  
  52.     }  
  53.     [Serializable]  
  54.     public enum FoodMenu  
  55.     {  
  56.         Sandwich = 1,  
  57.         Noodles = 2,  
  58.         Samosa = 3,  
  59.         Cookies = 4,  
  60.         Juice = 5,  
  61.         Tea = 6,  
  62.         Coffee = 7  
  63.     }  
  64. }   
Step 2

Now, create a new dialog under dialogs folder. Here, I have created a dialog name as FlightBotDialog.cs.

FlightBotDialog.cs 

  1. using BotFlightEnquiry.Models;  
  2. using Microsoft.Bot.Builder.Dialogs;  
  3. using Microsoft.Bot.Builder.FormFlow;  
  4. using System.Linq;  
  5. using System.Text.RegularExpressions;  
  6. using System.Threading.Tasks;  
  7.   
  8. namespace BotFlightEnquiry.Dialogs  
  9. {  
  10.     public class FlightBotDialog  
  11.     {  
  12.         public static readonly IDialog<string> dialog = Chain.PostToChain()  
  13.             .Select(msg => msg.Text)  
  14.             .Switch(  
  15.             new RegexCase<IDialog<string>>(new Regex("^hi", RegexOptions.IgnoreCase), (context, text) =>  
  16.             {  
  17.                 return Chain.ContinueWith(new MyDialog(), AfterMyDialogContinue);  
  18.             }),  
  19.             new DefaultCase<string, IDialog<string>>((context, text) =>  
  20.             {  
  21.                 return Chain.ContinueWith(FormDialog.FromForm(EnquiryForm.BuildForm, FormOptions.PromptInStart), AfterMyDialogContinue);  
  22.             }))  
  23.             .Unwrap()  
  24.             .PostToUser();  
  25.   
  26.         private async static Task<IDialog<string>> AfterMyDialogContinue(IBotContext context, IAwaitable<object> item)  
  27.         {  
  28.             var token = await item;  
  29.             var name = "User";  
  30.             context.UserData.TryGetValue<string>("Name"out name);  
  31.             return Chain.Return($"Thanks. Please type something-");  
  32.         }  
  33.     }  
  34. }   

Step 3

Add another dialog named MyDialog.cs. Now, you can ask a question, why do we create a new dialog? It is because we can divide the code into small pieces for our convenience.

MyDialog.cs

  1. using Microsoft.Bot.Builder.Dialogs;  
  2. using Microsoft.Bot.Connector;  
  3. using System;  
  4. using System.Threading.Tasks;  
  5.   
  6. namespace BotFlightEnquiry.Dialogs  
  7. {  
  8.     [Serializable]  
  9.     public class MyDialog : IDialog  
  10.     {  
  11.         public async Task StartAsync(IDialogContext context)  
  12.         {  
  13.             // My dialog initiates and waits for the next message from the user.              
  14.             await context.PostAsync("Hi I am flight Enquiry Bot.");  
  15.             await Respond(context);  
  16.             // When a message arrives, call MessageReceivedAsync.  
  17.             context.Wait(MessageReceivedAsync);  
  18.         }  
  19.   
  20.         private static async Task Respond(IDialogContext context)  
  21.         {  
  22.             var userName = string.Empty;  
  23.             context.UserData.TryGetValue<string>("Name"out userName);  
  24.             if (string.IsNullOrEmpty(userName))  
  25.             {  
  26.                 await context.PostAsync("What is your Name?");  
  27.                 context.UserData.SetValue<bool>("GetName"true);  
  28.             }  
  29.             else  
  30.             {  
  31.                 await context.PostAsync(string.Format("Hi {0}. How can I help you today?", userName));  
  32.             }  
  33.         }  
  34.         public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)  
  35.         {  
  36.             var message = await result;// We've got a message!  
  37.             var userName = String.Empty;  
  38.             var getName = false;  
  39.             context.UserData.TryGetValue<string>("Name"out userName);  
  40.             context.UserData.TryGetValue<bool>("GetName"out getName);  
  41.             if (getName)  
  42.             {  
  43.                 userName = message.Text;  
  44.                 context.UserData.SetValue<string>("Name", userName);  
  45.                 context.UserData.SetValue<bool>("GetName"false);  
  46.             }  
  47.             await Respond(context);  
  48.             context.Done(message);  
  49.         }  
  50.     }  
  51. }   

Step 4

When we create Bot, MessageController has been created.

MessagesController.cs

  1. using BotFlightEnquiry.Dialogs;  
  2. using BotFlightEnquiry.Models;  
  3. using Microsoft.Bot.Builder.Dialogs;  
  4. using Microsoft.Bot.Builder.FormFlow;  
  5. using Microsoft.Bot.Connector;  
  6. using System.Net;  
  7. using System.Net.Http;  
  8. using System.Threading.Tasks;  
  9. using System.Web.Http;  
  10.   
  11. namespace BotFlightEnquiry  
  12. {  
  13.     [BotAuthentication]  
  14.     public class MessagesController : ApiController  
  15.     {  
  16.         /// <summary>  
  17.         /// POST: api/Messages  
  18.         /// Receive a message from a user and reply to it  
  19.         /// </summary>  
  20.         public async Task<HttpResponseMessage> Post([FromBody]Activity activity)  
  21.         {  
  22.             if (activity.Type == ActivityTypes.Message)  
  23.             {  
  24.                 await Conversation.SendAsync(activity, () => FlightBotDialog.dialog);  
  25.             }  
  26.             else  
  27.             {  
  28.                 HandleSystemMessage(activity);  
  29.             }  
  30.             var response = Request.CreateResponse(HttpStatusCode.OK);  
  31.             return response;  
  32.         }  
  33.         internal static IDialog<EnquiryForm> MakeRootDialog()  
  34.         {  
  35.             return Chain.From(() => FormDialog.FromForm(EnquiryForm.BuildForm));  
  36.         }  
  37.         private Activity HandleSystemMessage(Activity message)  
  38.         {  
  39.             if (message.Type == ActivityTypes.DeleteUserData)  
  40.             {  
  41.                 // Implement user deletion here  
  42.                 // If we handle user deletion, return a real message  
  43.             }  
  44.             else if (message.Type == ActivityTypes.ConversationUpdate)  
  45.             {  
  46.                 // Handle conversation state changes, like members being added and removed  
  47.                 // Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info  
  48.                 // Not available in all channels  
  49.             }  
  50.             else if (message.Type == ActivityTypes.ContactRelationUpdate)  
  51.             {  
  52.                 // Handle add/remove from contact lists  
  53.                 // Activity.From + Activity.Action represent what happened  
  54.             }  
  55.             else if (message.Type == ActivityTypes.Typing)  
  56.             {  
  57.                 // Handle knowing tha the user is typing  
  58.             }  
  59.             else if (message.Type == ActivityTypes.Ping)  
  60.             {  
  61.             }  
  62.   
  63.             return null;  
  64.         }  
  65.     }  
  66. }  

Work flow

When we debug this Bot, Post method will invoke into MessagesController and proceed, as shown below.

  1. public async Task<HttpResponseMessage> Post([FromBody]Activity activity)    
  2. { 
  3.    if (activity.Type == ActivityTypes.Message)  
  4.    {   
  5.       await Conversation.SendAsync(activity, () => FlightBotDialog.dialog);  
  6.    }  
  7.    else  
  8.    {  
  9.       //HandleSystemMessage(activity);  
  10.    }  
  11.    var response = Request.CreateResponse(HttpStatusCode.OK);  
  12.   
  13.    return response;  
  14.   
  15. }  

Now, we reached on FlightBotDialog and check if the user has typed hi or any word, which is started by hi, followed by invoking MyDialog, else BuildForm invokes.

Output