Reuse dialogs

APPLIES TO: yesSDK v4 no SDK v3

With component dialogs, you can create independent dialogs to handle specific scenarios, breaking a large dialog set into more manageable pieces. Each of these pieces has its own dialog set, and avoids any name collisions with the dialog sets outside of it.

Prerequisites

About the sample

In the multi-turn prompt sample, we use a waterfall dialog, a few prompts, and a component dialog to create a simple interaction that asks the user a series of questions. The code uses a dialog to cycle through these steps:

Steps Prompt type
Ask the user for their mode of transportation Choice prompt
Ask the user for their name Text prompt
Ask the user if they want to provide their age Confirm prompt
If they answered yes, asks for their age Number prompt with validation to only accept ages greater than 0 and less than 150.
Asks if the collected information is "ok" Reuse Confirm prompt

Finally, if they answered yes, display the collected information; otherwise, tell the user that their information will not be kept.

Implement the component dialog

In the multi-turn prompt sample, we use a waterfall dialog, a few prompts, and a component dialog to create a simple interaction that asks the user a series of questions.

A component dialog encapsulates one or more dialogs. The component dialog has an inner dialog set, and the dialogs and prompts that you add to this inner dialog set have their own IDs, visible only from within the component dialog.

To use dialogs, install the Microsoft.Bot.Builder.Dialogs NuGet package.

Dialogs\UserProfileDialog.cs

Here the UserProfileDialog class derives from the ComponentDialog class.

public class UserProfileDialog : ComponentDialog

Within the constructor, the AddDialog method adds dialogs and prompts to the component dialog. The first item you add with this method is set as the initial dialog, but you can change this by explicitly setting the InitialDialogId property. When you start a component dialog, it will start its initial dialog.

public UserProfileDialog(UserState userState)
    : base(nameof(UserProfileDialog))
{
    _userProfileAccessor = userState.CreateProperty<UserProfile>("UserProfile");

    // This array defines how the Waterfall will execute.
    var waterfallSteps = new WaterfallStep[]
    {
        TransportStepAsync,
        NameStepAsync,
        NameConfirmStepAsync,
        AgeStepAsync,
        ConfirmStepAsync,
        SummaryStepAsync,
    };

    // Add named dialogs to the DialogSet. These names are saved in the dialog state.
    AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
    AddDialog(new TextPrompt(nameof(TextPrompt)));
    AddDialog(new NumberPrompt<int>(nameof(NumberPrompt<int>), AgePromptValidatorAsync));
    AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
    AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));

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

This is the implementation of the first step of the waterfall dialog.

private static async Task<DialogTurnResult> TransportStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
    // Running a prompt here means the next WaterfallStep will be run when the users response is received.
    return await stepContext.PromptAsync(nameof(ChoicePrompt),
        new PromptOptions
        {
            Prompt = MessageFactory.Text("Please enter your mode of transport."),
            Choices = ChoiceFactory.ToChoices(new List<string> { "Car", "Bus", "Bicycle" }),
        }, cancellationToken);
}

For more information on implementing waterfall dialogs, see how to implement sequential conversation flow.

At run-time, the component dialog maintains its own dialog stack. When the component dialog is started:

  • An instance is created and added to the outer dialog stack
  • It creates an inner dialog stack that it adds to its state
  • It starts its initial dialog and adds that to the inner dialog stack.

From the parent context, it will look like the component is the active dialog. From inside the component, it will look like the initial dialog is the active dialog.

Implement the rest of the dialog and add it to the bot

In the outer dialog set, the one to which you add the component dialog, the component dialog has the ID that you created it with. In the outer set, the component looks like a single dialog, much like prompts do.

To use a component dialog, add an instance of it to the bot's dialog set - this is the outer dialog set.

DialogExtensions.cs

In the sample, this is done using the Run extension method as shown below.

public static async Task Run(this Dialog dialog, ITurnContext turnContext, IStatePropertyAccessor<DialogState> accessor, CancellationToken cancellationToken = default(CancellationToken))
{
    var dialogSet = new DialogSet(accessor);
    dialogSet.Add(dialog);

    var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
    var results = await dialogContext.ContinueDialogAsync(cancellationToken);
    if (results.Status == DialogTurnStatus.Empty)
    {
        await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken);
    }
}

Bots\DialogBot.cs

The Run method is called from the bot's OnMessageActivityAsync method.

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    Logger.LogInformation("Running dialog with Message Activity.");

    // Run the Dialog with the new message Activity.
    await Dialog.Run(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}

To test the bot

  1. If you have not done so already, install the Bot Framework Emulator.
  2. Run the sample locally on your machine.
  3. Start the emulator, connect to your bot, and send messages as shown below.

Sample run of the multi-turn prompt dialog

Additional information

How cancellation works for component dialogs

If you call cancel all dialogs from the component dialog's context, the component dialog will cancel all of the dialogs on its inner stack and then end, returning control to the next dialog on the outer stack.

If you call cancel all dialogs from the outer context, the component is cancelled, along with the rest of the dialogs on the outer context.

Keep this in mind when managing nested component dialogs in your bot.

Next steps

You can enhance bots to react to additional input, like "help" or "cancel", that can interrupt the normal flow of the conversation.