APPLIES TO: SDK v4
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. Component dialogs are reusable in that they can be:
- Added to another
ComponentDialog
or DialogSet
in your bot.
- Exported as a part of a package.
- Used within other bots.
Note
The Bot Framework JavaScript, C#, and Python SDKs will continue to be supported, however, the Java SDK is being retired with final long-term support ending in November 2023.
Existing bots built with the Java SDK will continue to function.
For new bot building, read about choosing the right copilot solution.
For more information, see The future of bot building.
Prerequisites
About the sample
In the multi-turn prompt sample, we use a waterfall dialog, a few prompts, and a component dialog to create an 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, ask for their age |
Number prompt with validation to only accept ages greater than 0 and less than 150. |
Ask 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 won't be kept.
Implement your component dialog
In the multi-turn prompt sample, we use a waterfall dialog, a few prompts, and a component dialog to create an 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. You can change the initial dialog 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,
PictureStepAsync,
SummaryStepAsync,
ConfirmStepAsync,
};
// 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)));
AddDialog(new AttachmentPrompt(nameof(AttachmentPrompt), PicturePromptValidatorAsync));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
The following code represents the first step of the waterfall dialog.
private static async Task<DialogTurnResult> NameStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["transport"] = ((FoundChoice)stepContext.Result).Value;
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("Please enter your name.") }, cancellationToken);
}
For more information on implementing waterfall dialogs, see how to implement sequential conversation flow.
To use dialogs, your project needs to install the botbuilder-dialogs npm package.
dialogs/userProfileDialog.js
Here the UserProfileDialog
class extends ComponentDialog
.
class UserProfileDialog extends 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. You can change the initial dialog by explicitly setting the InitialDialogId
property. When you start a component dialog, it will start its initial dialog.
constructor(userState) {
super('userProfileDialog');
this.userProfile = userState.createProperty(USER_PROFILE);
this.addDialog(new TextPrompt(NAME_PROMPT));
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT));
this.addDialog(new NumberPrompt(NUMBER_PROMPT, this.agePromptValidator));
this.addDialog(new AttachmentPrompt(ATTACHMENT_PROMPT, this.picturePromptValidator));
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.transportStep.bind(this),
this.nameStep.bind(this),
this.nameConfirmStep.bind(this),
this.ageStep.bind(this),
this.pictureStep.bind(this),
this.summaryStep.bind(this),
this.confirmStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
The following code represents the first step of the waterfall dialog.
async transportStep(step) {
// 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 user's response is received.
return await step.prompt(CHOICE_PROMPT, {
prompt: 'Please enter your mode of transport.',
choices: ChoiceFactory.toChoices(['Car', 'Bus', 'Bicycle'])
});
}
For more information on implementing waterfall dialogs, see how to implement sequential conversation flow.
UserProfileDialog.java
Here the UserProfileDialog
class derives from the ComponentDialog
class.
public class UserProfileDialog extends 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. You can change the initial dialog by calling the setInitialDialogId
method and provide the name of the initial dialog. When you start a component dialog, it will start its initial dialog.
public UserProfileDialog(UserState withUserState) {
super("UserProfileDialog");
userProfileAccessor = withUserState.createProperty("UserProfile");
WaterfallStep[] waterfallSteps = {
UserProfileDialog::transportStep,
UserProfileDialog::nameStep,
this::nameConfirmStep,
this::ageStep,
UserProfileDialog::pictureStep,
this::confirmStep,
this::summaryStep
};
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps)));
addDialog(new TextPrompt("TextPrompt"));
addDialog(new NumberPrompt<Integer>("NumberPrompt", UserProfileDialog::agePromptValidator, Integer.class));
addDialog(new ChoicePrompt("ChoicePrompt"));
addDialog(new ConfirmPrompt("ConfirmPrompt"));
addDialog(new AttachmentPrompt("AttachmentPrompt", UserProfileDialog::picturePromptValidator));
// The initial child Dialog to run.
setInitialDialogId("WaterfallDialog");
}
The following code represents the first step of the waterfall dialog.
private static CompletableFuture<DialogTurnResult> nameStep(WaterfallStepContext stepContext) {
stepContext.getValues().put("transport", ((FoundChoice) stepContext.getResult()).getValue());
PromptOptions promptOptions = new PromptOptions();
promptOptions.setPrompt(MessageFactory.text("Please enter your name."));
return stepContext.prompt("TextPrompt", promptOptions);
}
For more information on implementing waterfall dialogs, see how to implement sequential conversation flow.
To use dialogs, install the botbuilder-dialogs and botbuilder-ai PyPI packages by running pip install botbuilder-dialogs
and pip install botbuilder-ai
from a terminal.
dialogs/user_profile_dialog.py
Here the UserProfileDialog
class extends ComponentDialog
.
class UserProfileDialog(ComponentDialog):
Within the constructor, the add_dialog
method adds dialogs and prompts to the component dialog. The first item you add with this method is set as the initial dialog. You can change the initial dialog by explicitly setting the initial_dialog_id
property. When you start a component dialog, it will start its initial dialog.
class UserProfileDialog(ComponentDialog):
def __init__(self, user_state: UserState):
super(UserProfileDialog, self).__init__(UserProfileDialog.__name__)
self.user_profile_accessor = user_state.create_property("UserProfile")
self.add_dialog(
WaterfallDialog(
WaterfallDialog.__name__,
[
self.transport_step,
self.name_step,
self.name_confirm_step,
self.age_step,
self.picture_step,
self.summary_step,
self.confirm_step,
],
)
)
self.add_dialog(TextPrompt(TextPrompt.__name__))
self.add_dialog(
NumberPrompt(NumberPrompt.__name__, UserProfileDialog.age_prompt_validator)
)
self.add_dialog(ChoicePrompt(ChoicePrompt.__name__))
self.add_dialog(ConfirmPrompt(ConfirmPrompt.__name__))
self.add_dialog(
AttachmentPrompt(
AttachmentPrompt.__name__, UserProfileDialog.picture_prompt_validator
)
)
self.initial_dialog_id = WaterfallDialog.__name__
The following code represents the first step of the waterfall dialog.
async def transport_step(
self, step_context: WaterfallStepContext
) -> DialogTurnResult:
# 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 step_context.prompt(
ChoicePrompt.__name__,
PromptOptions(
prompt=MessageFactory.text("Please enter your mode of transport."),
choices=[Choice("Car"), Choice("Bus"), Choice("Bicycle")],
),
)
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.
The parent context sees the component as the active dialog. However, to the context inside the component, it looks like the initial dialog is the active dialog.
Call the dialog from your bot
In the outer dialog set, the one to which you added 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.
Bots\DialogBot.cs
In the sample, this is done using the RunAsync
method that 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.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
dialogs/userProfileDialog.js
In the sample, we've added a run
method to the user profile dialog.
async run(turnContext, accessor) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(turnContext);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
bots/dialogBot.js
The run
method is called from the bot's onMessage
method.
this.onMessage(async (context, next) => {
console.log('Running dialog with Message Activity.');
// Run the Dialog with the new message Activity.
await this.dialog.run(context, this.dialogState);
await next();
});
DialogBot.java
In the sample, this is done using the run
method that is called from the bot's onMessageActivity
method.
@Override
protected CompletableFuture<Void> onMessageActivity(
TurnContext turnContext
) {
LoggerFactory.getLogger(DialogBot.class).info("Running dialog with Message Activity.");
// Run the Dialog with the new message Activity.
return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState"));
}
helpers/dialog_helper.py
In the sample, we've added a run_dialog
method to the user profile dialog.
class DialogHelper:
@staticmethod
async def run_dialog(
dialog: Dialog, turn_context: TurnContext, accessor: StatePropertyAccessor
):
dialog_set = DialogSet(accessor)
dialog_set.add(dialog)
dialog_context = await dialog_set.create_context(turn_context)
results = await dialog_context.continue_dialog()
if results.status == DialogTurnStatus.Empty:
await dialog_context.begin_dialog(dialog.id)
The run_dialog
method that is called from the bot's on_message_activity
method.
bots/dialog_bot.py
async def on_message_activity(self, turn_context: TurnContext):
await DialogHelper.run_dialog(
self.dialog,
turn_context,
self.conversation_state.create_property("DialogState"),
)
Test your bot
- If you haven't done so already, install the Bot Framework Emulator.
- Run the sample locally on your machine.
- Start the Emulator, connect to your bot, and send messages as shown below.
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 canceled, along with the rest of the dialogs on the outer context.
Next steps
Learn how to create complex conversations that branch and loop.