Get the User Details using Microsoft Bot Framework in C# with Waterfall Dialog Model

In this post, we will get the User details and validate them using the Waterfall Dialog Model in Microsoft Bot Framework v4 using C#. Very useful for beginners to learn Waterfall Dialog Model.

Prerequisites

  1. Visual Studio
  2. Bot Framework Emulator
  3. Bot Framework v4 Templates

The links for the above requirements are available on the Downloads page.

Creating a Core Bot project in Visual Studio

Follow my other blog to create the project and edit the project structure. I will name this project as UserBot. You can also refer to the reusable bot template for this requirement.

Creating the UserBot

Create a new class file with the name UserProfile.cs. We will use this file to store the user details. Also, add the following properties to it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace UserBot
{
    public class UserProfile
    {
        public string UserName { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public string SocialMediaProfile { get; set; }
        public string SocialMediaProfileLink { get; set; }
        public string Travel { get; set; }
        public string TravelPreference { get; set; }
        public string BookReading { get; set; }
        public string BookReadingPreference { get; set; }
        public string BookReadingGenre { get; set; }
        public string MoreFocussed { get; set; }
        public string MoreInformation { get; set; }
        public string OtherInformation { get; set; }
    }
}

To give you a background, what details we are asking for and what options we will show to the user.

  1. UserName: Name of the user
  2. Email: User Email
  3. Phone: User Phone Number
  4. SocialMediaProfile: Show the options to choose from {Instagram, Snapchat, Facebook, Twitter, LinkedIn}
  5. SocialMediaProfileLink: Link of the profile based on the Social Media Selection.
  6. Travel: If the user loves to travel {Yes, No}
  7. TravelPreference: Show the options to choose from {Solo, Group}
  8. BookReading: If the user loves to read books {Yes, No}
  9. BookReadingPreference: Show the options to choose from {Paperbook, Audio Book}
  10. BookReadingGenre: Show the options to choose from {Fiction, Non Fiction}
  11. MoreFocussed: Show the options to choose from {Day Time, Night Time}
  12. MoreInformation: If the user wants to give any other information {Yes, No}
  13. OtherInformation: To provide any other information.

Create Waterfall Dialog steps inside the MainDialog to go in a sequential flow.

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Generated with Bot Builder V4 SDK Template for Visual Studio CoreBot v4.12.2

using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using Microsoft.Recognizers.Text.DataTypes.TimexExpression;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace UserBot.Dialogs
{
    public class MainDialog : ComponentDialog
    {
        
        protected readonly ILogger Logger;

        // Dependency injection uses this constructor to instantiate MainDialog
        public MainDialog(ILogger<MainDialog> logger)
            : base(nameof(MainDialog))
        {
            
            Logger = logger;

            AddDialog(new TextPrompt(nameof(TextPrompt)));           
            AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
            {
                UserNameStepAsync,
                UserEmailStepAsync,
                UserPhoneStepAsync,
                SocialMediaProfileStepAsync,
                SocialMediaProfileLinkStepAsync,
                TravelStepAsync,
                TravelPreferenceStepAsync,
                BookReadingStepAsync,
                BookReadingPreferenceStepAsync,
                BookReadingGenreStepAsync,
                MoreFocussedStepAsync,
                MoreInformationStepAsync,
                OtherInformationStepAsync,
                ConfirmStepAsync,
                FinalStepAsync,
            }));

            // The initial child Dialog to run.
            InitialDialogId = nameof(WaterfallDialog);
        }

        private Task<DialogTurnResult> UserNameStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> UserEmailStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> UserPhoneStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> SocialMediaProfileStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> SocialMediaProfileLinkStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> TravelStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> TravelPreferenceStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> BookReadingStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> BookReadingPreferenceStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> BookReadingGenreStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> MoreFocussedStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> MoreInformationStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> OtherInformationStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private Task<DialogTurnResult> ConfirmStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            
            // Restart the main dialog with a different message the second time around
            var promptMessage = "What else can I do for you?";
            return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage, cancellationToken);
        }
    }
}

Update the MainDialog.cs with the below code. In the below code, we are doing validation for Email, Phone, and SocialMediaProfile.

We have also created a reusable Adaptive Card method that can be called multiple times to generate an adaptive that shows the options to the user.

Install the necessary Nuget Packages and import them in the MainDialog.cs.

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Generated with Bot Builder V4 SDK Template for Visual Studio CoreBot v4.12.2

using AdaptiveCards;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace UserBot.Dialogs
{
    public class MainDialog : ComponentDialog
    {
        protected readonly ILogger Logger;
        private readonly string EmailDialogID = "EmailDlg";
        private readonly string PhoneDialogID = "PhoneDlg";
        private readonly string SocialMediaProfileDialogID = "SocialMediaProfileDlg";
        public string SocialMediaProfile { get; set; }

        // Dependency injection uses this constructor to instantiate MainDialog
        public MainDialog(ILogger<MainDialog> logger)
            : base(nameof(MainDialog))
        {
            
            Logger = logger;

            AddDialog(new TextPrompt(nameof(TextPrompt)));
            AddDialog(new TextPrompt(EmailDialogID, EmailValidation));
            AddDialog(new TextPrompt(PhoneDialogID, PhoneValidation));
            AddDialog(new TextPrompt(SocialMediaProfileDialogID, SocialMediaProfileValidation));
            AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
            AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
            AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
            {
                UserNameStepAsync,
                UserEmailStepAsync,
                UserPhoneStepAsync,
                SocialMediaProfileStepAsync,
                SocialMediaProfileLinkStepAsync,
                TravelStepAsync,
                TravelPreferenceStepAsync,
                BookReadingStepAsync,
                BookReadingPreferenceStepAsync,
                BookReadingGenreStepAsync,
                MoreFocussedStepAsync,
                MoreInformationStepAsync,
                OtherInformationStepAsync,
                ConfirmStepAsync,
                FinalStepAsync,
            }));

            // The initial child Dialog to run.
            InitialDialogId = nameof(WaterfallDialog);
        }
        
        private async Task<DialogTurnResult> UserNameStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
            {
                Prompt = MessageFactory.Text("Hello there, Please enter your name.")
            }, cancellationToken);
        }

        private async Task<DialogTurnResult> UserEmailStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            stepContext.Values["UserName"] = (string)stepContext.Result;
            await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Hi {(string)stepContext.Values["UserName"]}, welcome to User Details Bot."), cancellationToken);
            return await stepContext.PromptAsync(EmailDialogID, new PromptOptions
            {
                Prompt = MessageFactory.Text("Please enter your Email.")
            }, cancellationToken);
        }

        private async Task<DialogTurnResult> UserPhoneStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            stepContext.Values["UserEmail"] = (string)stepContext.Result;
            return await stepContext.PromptAsync(PhoneDialogID, new PromptOptions
            {
                Prompt = MessageFactory.Text("Please enter your Phone number.")
            }, cancellationToken);
        }

        private async Task<DialogTurnResult> SocialMediaProfileStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            stepContext.Values["UserPhone"] = (string)stepContext.Result;
            List<string> socialMediaList = new List<string> { "Instagram", "Snapchat", "Facebook", "Twitter", "LinkedIn" };
            await stepContext.Context.SendActivityAsync(MessageFactory.Text("Please select the Social Media Profile you use."), cancellationToken);
            return await CreateAdaptiveCardAsync(socialMediaList, stepContext, cancellationToken);
        }

        private async Task<DialogTurnResult> SocialMediaProfileLinkStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            stepContext.Values["SocialMediaProfile"] = ((FoundChoice)stepContext.Result).Value;
            SocialMediaProfile = (string)stepContext.Values["SocialMediaProfile"];
            return await stepContext.PromptAsync(SocialMediaProfileDialogID, new PromptOptions
            {
                Prompt = MessageFactory.Text($"Please enter the link of your {SocialMediaProfile} social media profile.")
            }, cancellationToken);
        }

        private async Task<DialogTurnResult> TravelStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            stepContext.Values["SocialMediaProfileLink"] = (string)stepContext.Result;
            return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions
            {
                Prompt = MessageFactory.Text("Do you like Travelling?")
            }, cancellationToken);
        }

        private async Task<DialogTurnResult> TravelPreferenceStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            if ((bool)stepContext.Result)
            {
                stepContext.Values["Travel"] = "Yes";
                List<string> travelPreferenceList = new List<string> { "Solo", "Group" };
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("How do you like to travel?"), cancellationToken);
                return await CreateAdaptiveCardAsync(travelPreferenceList, stepContext, cancellationToken);
            }
            else
            {
                stepContext.Values["Travel"] = "No";
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("Sad to hear that you don't like travel. You should definitely go on a trip atleast once in a month."), cancellationToken);
                return await stepContext.NextAsync(null, cancellationToken);
            }
        }

        private async Task<DialogTurnResult> BookReadingStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            if (stepContext.Values["Travel"].Equals("Yes"))
            {
                stepContext.Values["TravelPreference"] = ((FoundChoice)stepContext.Result).Value;              
            }
            else
            {
                stepContext.Values["TravelPreference"] = "No Travel Preference";
            }
            return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions
            {
                Prompt = MessageFactory.Text("Do you like Reading Books?")
            }, cancellationToken);

        }

        private async Task<DialogTurnResult> BookReadingPreferenceStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            if ((bool)stepContext.Result)
            {
                stepContext.Values["BookReading"] = "Yes";
                List<string> bookPreferenceList = new List<string> { "Paperbook", "Audio Book" };
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("How do you like to read books?"), cancellationToken);
                return await CreateAdaptiveCardAsync(bookPreferenceList, stepContext, cancellationToken);
            }
            else
            {
                stepContext.Values["BookReading"] = "No";
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("No problem, you will love reading books when you try reading the Shiva Trilogy by Amish Tripathi"), cancellationToken);
                return await stepContext.NextAsync(null, cancellationToken);
            }
        }

        private async Task<DialogTurnResult> BookReadingGenreStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            if (stepContext.Values["BookReading"].Equals("Yes"))
            {
                stepContext.Values["BookReadingPreference"] = ((FoundChoice)stepContext.Result).Value;

                if (stepContext.Values["BookReadingPreference"].Equals("Paperbook"))
                {
                    await stepContext.Context.SendActivityAsync(MessageFactory.Text("Even I don't like listening audio books."), cancellationToken);
                }
                List<string> bookGenreList = new List<string> { "Fiction", "Non Fiction" };
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("What type of books you like to read?"), cancellationToken);
                return await CreateAdaptiveCardAsync(bookGenreList, stepContext, cancellationToken);
            }
            else
            {
                stepContext.Values["BookReadingPreference"] = "No Reading Preference";
                return await stepContext.NextAsync(null, cancellationToken);
            }
        }

        private async Task<DialogTurnResult> MoreFocussedStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            if (stepContext.Values["BookReading"].Equals("Yes"))
            {
                stepContext.Values["BookReadingGenre"] = ((FoundChoice)stepContext.Result).Value;            
            }
            else
            {
                stepContext.Values["BookReadingGenre"] = "Not Reading Books";
            }
            List<string> moreFocussedList = new List<string> { "During Day Time", "During Night Time" };
            await stepContext.Context.SendActivityAsync(MessageFactory.Text("When are you more focussed?"), cancellationToken);
            return await CreateAdaptiveCardAsync(moreFocussedList, stepContext, cancellationToken);
        }

        private async Task<DialogTurnResult> MoreInformationStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            stepContext.Values["MoreFocussed"] = ((FoundChoice)stepContext.Result).Value;
            if (stepContext.Values["MoreFocussed"].Equals("During Night Time"))
            {
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("Even I like Night time. It will be very calm and silent"), cancellationToken);
            }
            return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions
            {
                Prompt = MessageFactory.Text("Would you like to give any other information?")
            }, cancellationToken);
        }

        private async Task<DialogTurnResult> OtherInformationStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            if ((bool)stepContext.Result)
            {
                stepContext.Values["MoreInformation"] = "Yes";
                return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
                {
                    Prompt = MessageFactory.Text("Please give any other information you want me to know about you.")
                }, cancellationToken);
            }
            else
            {
                stepContext.Values["MoreInformation"] = "No";
                return await stepContext.NextAsync(null, cancellationToken);
            }
        }

        private async Task<DialogTurnResult> ConfirmStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            if (stepContext.Values["MoreInformation"].Equals("Yes"))
            {
                stepContext.Values["OtherInformation"] = (string)stepContext.Result;
            }
            else
            {
                stepContext.Values["OtherInformation"] = "Not Given";
            }
            return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions
            {
                Prompt = MessageFactory.Text("Would you like to Confirm your details?")
            }, cancellationToken);
        }


        private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            if ((bool)stepContext.Result)
            {
                UserProfile userProfile = new UserProfile()
                {
                    UserName = (string)stepContext.Values["UserName"],
                    Email = (string)stepContext.Values["UserEmail"],
                    Phone = (string)stepContext.Values["UserPhone"],
                    SocialMediaProfile = (string)stepContext.Values["SocialMediaProfile"],
                    SocialMediaProfileLink = (string)stepContext.Values["SocialMediaProfileLink"],
                    Travel = (string)stepContext.Values["Travel"],
                    TravelPreference = (string)stepContext.Values["TravelPreference"],
                    BookReading = (string)stepContext.Values["BookReading"],
                    BookReadingPreference = (string)stepContext.Values["BookReadingPreference"],
                    BookReadingGenre = (string)stepContext.Values["BookReadingGenre"],
                    MoreFocussed = (string)stepContext.Values["MoreFocussed"],
                    MoreInformation = (string)stepContext.Values["MoreInformation"],
                    OtherInformation = (string)stepContext.Values["OtherInformation"]
                };

                //Use this JSON for your requirement.
                string stringjson = JsonConvert.SerializeObject(userProfile);

                await stepContext.Context.SendActivityAsync(MessageFactory.Text("Your details are stored, Thank you."), cancellationToken);
            }
            else
            {
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("Your details are not saved. Thank you."), cancellationToken);
            }
            // Restart the main dialog with a different message the second time around
            var promptMessage = "What else can I do for you?";
            return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage, cancellationToken);
        }

        private async Task<bool> EmailValidation(PromptValidatorContext<string> promptcontext, CancellationToken cancellationtoken)
        {
            string email = promptcontext.Recognized.Value;

            if (string.IsNullOrWhiteSpace(email))
            {
                await promptcontext.Context.SendActivityAsync("The email you entered is not valid, please enter a valid email.", cancellationToken: cancellationtoken);
                return false;
            }

            try
            {
                var addr = new System.Net.Mail.MailAddress(email);
                if (addr.Address == email)
                {
                    return true;
                }
                else
                {
                    await promptcontext.Context.SendActivityAsync("The email you entered is not valid, please enter a valid email.", cancellationToken: cancellationtoken);
                    return false;
                }
            }
            catch
            {
                await promptcontext.Context.SendActivityAsync("The email you entered is not valid, please enter a valid email.", cancellationToken: cancellationtoken);
                return false;
            }
        }

        private async Task<bool> PhoneValidation(PromptValidatorContext<string> promptcontext, CancellationToken cancellationtoken)
        {
            string number = promptcontext.Recognized.Value;
            if (Regex.IsMatch(number, @"^\d+$"))
            {
                int count = promptcontext.Recognized.Value.Length;
                if (count != 10)
                {
                    await promptcontext.Context.SendActivityAsync("Hello, you are missing some number !!!",
                        cancellationToken: cancellationtoken);
                    return false;
                }
                return true;
            }
            await promptcontext.Context.SendActivityAsync("The phone number is not valid. Please enter a valid number.",
                        cancellationToken: cancellationtoken);
            return false;
        }

        private async Task<bool> SocialMediaProfileValidation(PromptValidatorContext<string> promptcontext, CancellationToken cancellationtoken)
        {
            string profileLink = promptcontext.Recognized.Value;
            
            if (profileLink.ToLower().Contains(SocialMediaProfile.ToLower()))
            {
                return true;
            }
            else
            {
                await promptcontext.Context.SendActivityAsync($"The profile link you entered for {SocialMediaProfile} is not valid. Please enter correct profile link.", cancellationToken: cancellationtoken);
                return false;
            }

        }

        private async Task<DialogTurnResult> CreateAdaptiveCardAsync(List<string> listOfOptions, WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0))
            {
                // Use LINQ to turn the choices into submit actions
                Actions = listOfOptions.Select(choice => new AdaptiveSubmitAction
                {
                    Title = choice,
                    Data = choice,  // This will be a string
                }).ToList<AdaptiveAction>(),
            };
            // Prompt
            return await stepContext.PromptAsync(nameof(ChoicePrompt), new PromptOptions
            {
                Prompt = (Activity)MessageFactory.Attachment(new Attachment
                {
                    ContentType = AdaptiveCard.ContentType,
                    // Convert the AdaptiveCard to a JObject
                    Content = JObject.FromObject(card),
                }),
                Choices = ChoiceFactory.ToChoices(listOfOptions),
                // Don't render the choices outside the card
                Style = ListStyle.None,
            },
                cancellationToken);
        }
    }
}

Run the project and see the output in the Bot Framework Emulator. Remember, we have not changed the welcome card. It is default from the template. If you want to learn about creating the Welcome Cards, refer to my blog.

Get the complete code from GitHub.

Thank you All!!! Hope you find this useful.


Up ↑

%d bloggers like this: