Before reading this article, I highly recommend you read the
Dialogs in Bot and
Custom validation in Bot. First, why do we want to overwrite the Dialog class? See the below code. To read the username for this property, we have to write two functions - one function asks the question and the other one reads the value.
- private async Task<DialogTurnResult> UserNameAsync(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)
- {
- return await stepcontext.PromptAsync(DlgNameId, new PromptOptions
- {
- Prompt = MessageFactory.Text("Hello !!!, Please enter the Name")
-
- }, cancellationtoken);
- }
-
- private async Task<DialogTurnResult> GetUserNameAsync(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)
- {
- var name = (string)stepcontext.Result;
- }
Let’s imagine we need 20 different properties from the user. Then, how many functions do we have to write? This is not good, right? The code maintainability is out of control.
The answer to this problem is - Overwrite the dialog class.
Dialog class has BeginDialogAsync, ResumeDialogAsync, & EndDialogAsync functions. We are going to use those functions to implement Reusable dialog.
Let's take one example and see how to solve this problem.
Example: Our requirement is to get the username, mobile number, city name, and the pizza name. Based on the properties, define the prompt dialog types.
User class : Username(TextPrompt), Mobile Number(TextPrompt), City Name(TextPrompt) Pizza class : Pizza Name(choicePrompt)
Add the Prompt Dialog in DialogSet with Unique Key
Note
All dialogs must be added into the DialogSet.
Add all the prompt dialog types into the DialogSet. That’s also one of the maintainability problems. Here, what we are going to do is to add the one unique PromptDialogtype into the DialogSet and reuse it.
In our example - if two prompt texts are required, only one TextPrompt adds into the DialogSet. Use this TextPrompt into two properties with the help of the DialogId.
Add the Prompt type in the DialogSet as in this example.
- _dialogSet = new DialogSet(dlgstate);
- _dialogSet.Add(new TextPrompt("text"));
- _dialogSet.Add(new NumberPrompt<int>("number"));
- _dialogSet.Add(new ChoicePrompt("choice"));
Create a Model
For adding the property into the dialog set, we create a class model and create the property of the DialogSet.
- using Microsoft.Bot.Builder.Dialogs;
-
- namespace MeetpupDialog.Model
- {
- public class Property
- {
- public string PromptDlgId;
-
- public string Name;
-
- public PromptOptions Pmoptions;
-
- public Property(string promptDlgId, string name, PromptOptions pmoptions)
- {
- this.PromptDlgId = promptDlgId;
- Name = name;
- this.Pmoptions = pmoptions;
- }
-
- public Property(string promptDlgId,string name,string pmtext=null) :
- this(promptDlgId,name, new PromptOptions { Prompt = MessageFactory.Text(pmtext) })
- {
- }
- }
- }
- Public string PromptDlgId;
- public string
- Public PromptOptions Pmoptions;
Create a UserForm & Pizza class
Based on the case, we divided two different classes 1. User form 2. Pizza order
Create a UserForm class
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using MeetpupDialog.Model;
-
- namespace MeetpupDialog.ChildDialog
- {
- public static class UserForm
- {
- public static List<Property> PromptProperties()
- {
- var lstproperty = new List<Property>
- {
- new Property("text", "FirstName", "Please enter the user Name"),
- new Property("number", "moblieNo", "Please enter the mobile Number")
- };
-
- return lstproperty;
- }
- }
- }
Creating a property list, Property “text” is used to ref the DialogSet prompt type.
“First Name” is used for Key in the ExecuteChildDialog (later of this dialog, see more on this ) to store the value and final string used for PromptOptions, and what dialog type is shown to the user.
Pizza Order class
- using System.Collections.Generic;
- using MeetpupDialog.Model;
- using Microsoft.Bot.Builder;
- using Microsoft.Bot.Builder.Dialogs;
- using Microsoft.Bot.Builder.Dialogs.Choices;
-
- namespace MeetpupDialog.ChildDialog
- {
- public static class PizzaOrder
- {
- public static List<Property> PromptProperties()
- {
- var pizzalist = new PromptOptions
- {
- Choices = new List<Choice> {new Choice("Plain Pizza"), new Choice("Pizza with Mushrooms"), new Choice("Pizza3")},
- Prompt = MessageFactory.Text("Please select the Pizza Type")
- };
-
- var lstpropery = new List<Property>
- {
- new Property("choice","PizzaName",pizzalist)
- };
-
- return lstpropery;
- }
- }
- }
The same as UserForm class the difference is Pizza order class using the choice option.
Overwrite the Dialog Class
Once our class builds, we have to execute all the properties one by one and get value from the user. This step by step execution is taken care of by CustomDialog.
CustomDialog class inherits from the Dialog class & overwrite the BeginDialogAsync, and ResumeDialogAsync function.
This function takes care of the execution of the steps, all process is done call the EndDialog function to stop the execution.
Below flow diagram explain about the custom dialog execution
Overview of the above image steps,
- BeginDialogAsync trigger the first question
- Waiting for user response, once user responded to that
- ResumeDialogAsync get invoked and get the value stored into the dictionary
- Check and trigger the next property goto step 1, once all the execution did
- Call the EndDialog.
Above steps are high-level execution of the dialog process, in code level see more on step 3, step 4, step 5
Store data into the Dictionary
As a general, we have to write our collections to store (temporary memory) the data this also a slight problem but not always? How to avoid our own collections? Ans: Internal Dictionary
Dictionary: Dictionary is playing a significant role in a user dialog. Internally Dialog class has the dictionary; we can use this dictionary & store our values, one more advantage of this dictionary is used for maintaining state management also.
Use of internal Dictionary
Dictionary object is available in the DialogInstance & this class is used for tracking information for a dialog in the stack. Note: Create our key to store the data into the Dictionary.
First, check if the key is present or not, If not, create a new Dictionary object and store the values in DialogInstance Dictionary.
Sample code for Store the values in DialogInstance Dictionary.
- private static IDictionary<string, object> GetStoredValue(DialogInstance dialogInstance)
- {
- if (!dialogInstance.State.TryGetValue(MyDir, out object value))
- {
- value = new Dictionary<string,object>();
- dialogInstance.State.Add(MyDir,value);
- }
-
- return (IDictionary<string, object>) value;
- }
ResumeDialogAsync
Once the user is responded to Bot query, this function gets invoked. This function is used to store the property value to get the help of GetStoredValue function.
Refer
In the above model class, the name property is used in this class as Key to store the value in the dictionary.
- public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext dc, DialogReason reason, object result = null,
- CancellationToken cancellationToken = new CancellationToken())
- {
-
- var value = GetStoredValue(dc.ActiveDialog);
- var exeProp = (string)dc.ActiveDialog.State[Property];
-
- if (result is FoundChoice resultChoice)
- {
- value[exeProp] = resultChoice.Value;
- }
- else
- {
- value[exeProp] = result;
- }
-
- return await ExecuteProperty(dc, cancellation: cancellationToken);
- }
GetStoredValue return the dictionary object, in this dictionary add the value which is entered by the user.
If condition checks the whether result value is choice prompt or not, If choice prompt read the value from the FoundChoice class else directly read from the result set & store into the dictionary.
ExecuteProperty
This function is a user-defined function; the Main purpose executes the property one by one. As I said early of this article bot framework is stateless we have to maintain the state management this function handling state management.
Ex: If the one question asked to the user the same question should not ask next time(state management); this function takes care of the execution with the help of the dictionary
Check the Dictionary if the property key is present or not. If not present, assign the property as a current state and call the BeginDialogAsyn function to get the value from the user. Once the user is responded resume dialog function gets called
- private async Task<DialogTurnResult> ExecuteProperty(DialogContext dlgContext, CancellationToken cancellation)
- {
- var executeprop = GetStoredValue(dlgContext.ActiveDialog);
- var unexecutedprop = _executeProperty.FirstOrDefault((item) => !executeprop.ContainsKey(item.Name));
-
- if (unexecutedprop != null)
- {
- dlgContext.ActiveDialog.State[Property] = unexecutedprop.Name;
- return await dlgContext.BeginDialogAsync(unexecutedprop.PromptDlgId,unexecutedprop.Pmoptions,cancellationToken:cancellation);
- }
- else
- {
- return await dlgContext.EndDialogAsync(executeprop,cancellation);
- }
- }
Add user define properties into the DialogSet
Once the supported class is created, add all the properties into the DialogSet.
Note
All the dialogs must be added into the dialog set otherwise it won't invoke.
- _dialogSet.Add(new ExecuteChildDialog(nameof(UserForm), UserForm.PromptProperties()));
- _dialogSet.Add(new ExecuteChildDialog(nameof(PizzaOrder), PizzaOrder.PromptProperties()));
Root Dialog
This is a user-defined dialog; the purpose of the dialog class is to execute all the child dialogs. In this example, User-defined & PizzaOrder is a child dialog. Why we called as a child class? Let see in details.
First should understand how the dialogs are executed? Ans: Dialog execution takes care by the Waterfall Dialog. Waterfall take care execution step by step. In our example, waterfall step dialog first executes the user-defined a dialog and second waterfall step executes the Pizaa order dialog if any dialog is there waterfall execute the next dialog. Look like sound like repetition.
Yes, we are doing a repetition, to avoiding this add all the child dialog into the root dialog, lets water fall dialog execute the root dialog and root dialog automatically runs all the child class, so only one-time waterfall start the execution, and final result receives from next waterfall function.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using MeetpupDialog.ChildDialog;
- using MeetpupDialog.Model;
-
-
- namespace MeetpupDialog.MainDialog
- {
- public static class RootDialog
- {
- public static List<Property> PromptProperties()
- {
- var lstProperty = new List<Property>()
- {
- new Property(nameof(UserForm),nameof(UserForm)),
- new Property(nameof(PizzaOrder),nameof(PizzaOrder))
- };
-
- return lstProperty;
- }
- }
- }
Overview of the execution
WaterfallDialogStep call the RunMainDialog (user defined) function , this function calls the RootDialog , RootDialog executes the all the child dialogs once all done , call the EndDialog function & pass the Dictionary object . Waterfall step EndMainDlg gets called received the Dictionary object.
- private async Task<DialogTurnResult> RunMainDialog(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)
- {
- return await stepcontext.BeginDialogAsync(nameof(RootDialog), null, cancellationtoken);
- }
-
- private async Task<DialogTurnResult> EndMainDlg(WaterfallStepContext stepcontext, CancellationToken cancellationtoken)
- {
- if(stepcontext.Result is IDictionary<string,object> result)
- {
- var userform = (IDictionary<string, object>)result[nameof(UserForm)];
- var display = DisplayResult(userform, stepcontext, cancellationtoken);
-
- var pizzaform = (IDictionary<string, object>)result[nameof(PizzaOrder)];
- display += DisplayResult(pizzaform, stepcontext, cancellationtoken);
-
- await stepcontext.Context.SendActivityAsync(display, cancellationToken: cancellationtoken);
- }
- return await stepcontext.EndDialogAsync(cancellationToken: cancellationtoken);
- }
-
- private static string DisplayResult(IDictionary<string, object> result, WaterfallStepContext stepcontext, CancellationToken cancellationtoken)
- {
- string res = string.Empty;
- foreach (var prop in result)
- {
- res += "\n" + prop.Key + " : " + prop.Value;
- }
- return res;
- }
WaterfallDialogStep call the RunMainDialog (user defined) function, this function calls the Root Dialog, Root Dialog executes all the subclass once all done, call the EndDialog function , refer to the ExecuteProperty function if. else condition once all the properties have executed else block we call the EndDialog function & pass the dictionary object to that function, Waterfall step EndMainDlg gets called, received the Dictionary object via WaterfallStepContext object, based on the key read the value from the dictionary.
DialogSet code
- private void ExecuteMainDialog(IStatePropertyAccessor<DialogState> dlgstate)
- {
-
- var waterFallSteps = new WaterfallStep[]
- {
- RunMainDialog,
- EndMainDlg
- };
-
- _dialogSet = new DialogSet(dlgstate);
- _dialogSet.Add(new TextPrompt("text"));
- _dialogSet.Add(new NumberPrompt<int>("number"));
- _dialogSet.Add(new ChoicePrompt("choice"));
- _dialogSet.Add(new ExecuteChildDialog(nameof(UserForm), UserForm.PromptProperties()));
- _dialogSet.Add(new ExecuteChildDialog(nameof(PizzaOrder), PizzaOrder.PromptProperties()));
- _dialogSet.Add(new ExecuteChildDialog(nameof(RootDialog),RootDialog.PromptProperties()));
- _dialogSet.Add(new WaterfallDialog("main", waterFallSteps));
-
- }
Output
Adding new property
Another advantage of a user-defined class is later if you want to add any new properties or a class based on that just insert into the proper place no need to do any change it will work.
Ex: Adding new property.
If you want to read the last name from the user, add LastName property in the User-defined class that’s it no need to do any changes,
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
- using MeetpupDialog.Model;
-
- namespace MeetpupDialog.ChildDialog
- {
- public static class UserForm
- {
- public static List<Property> PromptProperties()
- {
- var lstproperty = new List<Property>
- {
- new Property("text", "FirstName", "Please enter the First Name"),
- new Property("text", "LastName", "Please enter the Last Name"),
- new Property("number", "moblieNo", "Please enter the mobile Number")
- };
-
- return lstproperty;
- }
- }
- }
Output
You can find the complete sample
here
Conclusion
I hope you understand how to implement the user-defined dialogs in Bot Framework V4.
Happy Reading. Happy coding.