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

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

Form Builder

Property of forms and fields

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

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. namespace BotFlightEnquiry.Models
  5. {
  6. [Serializable]
  7. public class EnquiryForm
  8. {
  9. public FlightTypes flightTypes;
  10. public ClassTypes classTypes;
  11. [Optional]
  12. public IsMeal isMeal;
  13. public FoodMenu foodMenu;
  14. public DateTime? DateOfJurney;
  15. [Numeric(1, 5)]
  16. public int? NumberOfAdult;
  17. public int? NumberOfChild;
  18. public static IForm<EnquiryForm> BuildForm()
  19. {
  20. return new FormBuilder<EnquiryForm>()
  21. .Message("Welcome to the flight reservation BOT.")
  22. .OnCompletion(async (context, profileForm) =>
  23. {
  24. // Tell the user that the form is complete
  25. await context.PostAsync("Thanks for Enquiry.");
  26. })
  27. .Build();
  28. }
  29. }
  30. [Serializable]
  31. public enum FlightTypes
  32. {
  33. International = 1, Domestic = 2
  34. }
  35. [Serializable]
  36. public enum ClassTypes
  37. {
  38. FirstClass = 1,
  39. Business = 2,
  40. Economy = 3
  41. }
  42. [Serializable]
  43. public enum IsMeal
  44. {
  45. Yes = 1,
  46. No = 2
  47. }
  48. [Serializable]
  49. public enum FoodMenu
  50. {
  51. Sandwich = 1,
  52. Noodles = 2,
  53. Samosa = 3,
  54. Cookies = 4,
  55. Juice = 5,
  56. Tea = 6,
  57. Coffee = 7
  58. }
  59. }
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. namespace BotFlightEnquiry.Dialogs
  8. {
  9. public class FlightBotDialog
  10. {
  11. public static readonly IDialog<string> dialog = Chain.PostToChain()
  12. .Select(msg => msg.Text)
  13. .Switch(
  14. new RegexCase<IDialog<string>>(new Regex("^hi", RegexOptions.IgnoreCase), (context, text) =>
  15. {
  16. return Chain.ContinueWith(new MyDialog(), AfterMyDialogContinue);
  17. }),
  18. new DefaultCase<string, IDialog<string>>((context, text) =>
  19. {
  20. return Chain.ContinueWith(FormDialog.FromForm(EnquiryForm.BuildForm, FormOptions.PromptInStart), AfterMyDialogContinue);
  21. }))
  22. .Unwrap()
  23. .PostToUser();
  24. private async static Task<IDialog<string>> AfterMyDialogContinue(IBotContext context, IAwaitable<object> item)
  25. {
  26. var token = await item;
  27. var name = "User";
  28. context.UserData.TryGetValue<string>("Name", out name);
  29. return Chain.Return($"Thanks. Please type something-");
  30. }
  31. }
  32. }

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

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

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. return response;
  13. }

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