Ask the user questions

Note

This topic is for the latest release of the SDK (v4). You can find content for the older version of the SDK (v3) here.

At its core, a bot is built around the conversation with a user. Conversation can take many forms; they may be short or may be more complex, may be asking questions or may be answering questions. What shape the conversation takes depends on several factors, but they all involve a conversation.

This tutorial guides you through building up a conversation, from asking a simple question through a multi-turn bot. Our example will be around reserving a table, but you can imagine a bot that does a variety of things through a multi-turn conversation, such as placing an order, answering FAQs, making reservations, and so on.

An interactive chat bot can respond to user input or ask user for specific input. This tutorial will show you how to ask a user a question using the Prompts library, which is part of Dialogs. Dialogs can be thought of as the container that defines a conversation structure, and prompts within dialogs is covered more in depth in its own how-to article.

Prerequisite

Code in this tutorial will build on the basic bot you created through the Get Started experience.

Get the package

Install the Microsoft.Bot.Builder.Dialogs package from Nuget packet manager.

Import package to bot

Add reference to both dialogs and prompts in your bot code.

// ...
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Prompts;
// ...

This will give you access to the DialogSet and Prompts library that you will use to ask the user questions. DialogSet is just a collection of dialogs, which we structure in a waterfall pattern. This simply means that one dialog follows another.

Instantiate a dialogs object

Instantiate a dialogs object. You will use this dialog object to manage the question and answer process.

Declare a member variable in your bot class and initialize it in the constructor for your bot.

public class MyBot : IBot
{
    private readonly DialogSet dialogs;
    public MyBot()
    {
        dialogs = new DialogSet();
    }
    // The rest of the class definition is omitted here
}

Define a waterfall dialog

To ask a question, you will need at least a two step waterfall dialog. For this example, you will construct a two step waterfall dialog where the first step asks the user for their name and the second step greets the user by name.

Modify your bot's constructor to add the dialog:

public MyBot()
{
    dialogs = new DialogSet();
    dialogs.Add("greetings", new WaterfallStep[]
    {
        async (dc, args, next) =>
        {
            // Prompt for the guest's name.
            await dc.Prompt("textPrompt","What is your name?");
        },
        async(dc, args, next) =>
        {
            await dc.Context.SendActivity($"Hi {args["Text"]}!");
            await dc.End();
        }
    });
}

The question is asked using a textPrompt method that came with the Prompts library. The Prompts library offers a set of prompts that allows you to ask users for various types of information. For more information about other prompt types, see Prompt user for input.

For the prompting to work, you will need to add a prompt to the dialogs object with the dialogId textPrompt and create it with the TextPrompt() constructor.

public MyBot()
{
    dialogs = new DialogSet();
    dialogs.Add("greetings", new WaterfallStep[]
    {
        async (dc, args, next) =>
        {
            // Prompt for the guest's name.
            await dc.Prompt("textPrompt","What is your name?");
        },
        async(dc, args, next) =>
        {
            await dc.Context.SendActivity($"Hi {args["Text"]}!");
            await dc.End();
        }
    });
    // add the prompt, of type TextPrompt
    dialogs.Add("textPrompt", new Builder.Dialogs.TextPrompt());
}

Once the user answers the question, the response can be found in the args parameter of step 2.

Now that you have defined your dialogs to ask a question, you need to call on the dialog to start the prompting process.

Start the dialog

Modify your bot logic to something like this:


public async Task OnTurn(ITurnContext context)
{
    // We'll cover state later, in the next tutorial
    var state = ConversationState<Dictionary<string, object>>.Get(context);
    var dc = dialogs.CreateContext(context, state);
    if (context.Activity.Type == ActivityTypes.Message)
    {
        await dc.Continue();
        
        if(!context.Responded)
        {
            await dc.Begin("greetings");
        }
    }
}

Bot logic goes in the OnTurn() method. Once the user says "Hi" then the bot will start the greetings dialog. The first step of the greetings dialog prompts the user for their name. The user will send a reply with their name as a message activity, and the result is send to step two of the waterfall through the dc.Continue() method. The second step of the waterfall, as you have defined it, will greet the user by their name and ends the dialog.

Define a more complex waterfall dialog

Now that we've covered how a waterfall dialog works and how to build one, let's try a more complex dialog aimed at reserving a table.

To manage the table reservation request, you will need to define a waterfall dialog with four steps. In this conversation, you will also be using a DatetimePrompt and NumberPrompt in additional to the TextPrompt.

Start with the Echo Bot template, and rename your bot to CafeBot. Add a DialogSet and some static member variables.


namespace CafeBot
{
    public class CafeBot : IBot
    {
        private readonly DialogSet dialogs;

        // Usually, we would save the dialog answers to our state object, which will be covered in a later tutorial.
        // For purpose of this example, let's use the three static variables to store our reservation information.
        static DateTime reservationDate;
        static int partySize;
        static string reservationName;

        // the rest of the class definition is omitted here
        // but is discussed in the rest of this article
    }
}

Then define your reserveTable dialog. You can add the dialog within the bot class constructor.

public CafeBot()
{
    dialogs = new DialogSet();

    // Define our dialog
    dialogs.Add("reserveTable", new WaterfallStep[]
    {
        async (dc, args, next) =>
        {
            // Prompt for the guest's name.
            await dc.Context.SendActivity("Welcome to the reservation service.");

            await dc.Prompt("dateTimePrompt", "Please provide a reservation date and time.");
        },
        async(dc, args, next) =>
        {
            var dateTimeResult = ((DateTimeResult)args).Resolution.First();

            reservationDate = Convert.ToDateTime(dateTimeResult.Value);
            
            // Ask for next info
            await dc.Prompt("partySizePrompt", "How many people are in your party?");

        },
        async(dc, args, next) =>
        {
            partySize = (int)args["Value"];

            // Ask for next info
            await dc.Prompt("textPrompt", "Whose name will this be under?");
        },
        async(dc, args, next) =>
        {
            reservationName = args["Text"];
            string msg = "Reservation confirmed. Reservation details - " +
            $"\nDate/Time: {reservationDate.ToString()} " +
            $"\nParty size: {partySize.ToString()} " +
            $"\nReservation name: {reservationName}";
            await dc.Context.SendActivity(msg);
            await dc.End();
        }
    });

     // Add a prompt for the reservation date
     dialogs.Add("dateTimePrompt", new Microsoft.Bot.Builder.Dialogs.DateTimePrompt(Culture.English));
     // Add a prompt for the party size
     dialogs.Add("partySizePrompt", new Microsoft.Bot.Builder.Dialogs.NumberPrompt<int>(Culture.English));
     // Add a prompt for the user's name
     dialogs.Add("textPrompt", new Microsoft.Bot.Builder.Dialogs.TextPrompt());
}

The conversation flow of the reserveTable dialog will ask the user 3 questions through the first three steps of the waterfall. Step four processes the answer to the last question and sends the user the reservation confirmation.

Each waterfall step of the reserveTable dialog uses a prompt to ask the user for information. The following code was used to add the prompts to the dialog set.

dialogs.Add("dateTimePrompt", new Microsoft.Bot.Builder.Dialogs.DateTimePrompt(Culture.English));
dialogs.Add("partySizePrompt", new Microsoft.Bot.Builder.Dialogs.NumberPrompt<int>(Culture.English));
dialogs.Add("textPrompt", new Microsoft.Bot.Builder.Dialogs.TextPrompt());

Now, you are ready to hook this into the bot logic.

Start the dialog

Modify your bot's OnTurn to contain the following code:

public async Task OnTurn(ITurnContext context)
{
    if (context.Activity.Type == ActivityTypes.Message)
    {
        // The type parameter PropertyBag inherits from 
        // Dictionary<string,object>
        var state = ConversationState<Dictionary<string, object>>.Get(context);
        var dc = dialogs.CreateContext(context, state);
        await dc.Continue();

        // Additional logic can be added to enter each dialog depending on the message received
        
        if(!context.Responded)
        {
            if (context.Activity.Text.ToLowerInvariant().Contains("reserve table"))
            {
                await dc.Begin("reserveTable");
            }
            else
            {
                await context.SendActivity($"You said '{context.Activity.Text}'");
            }
        }
    }
}

In Startup.cs, change the initialization of the ConversationState middleware to use a class deriving from Dictionary<string,object> instead of EchoState.

For example, in Configure():

options.Middleware.Add(new ConversationState<Dictionary<string, object>>(dataStore));

Next steps

In this tutorial, the bot is saving the user's input to variables within our bot. If you want to store or persist this information, you need to add state to the middleware layer. Let's take a closer look at how to persist user state data in the next tutorial.