Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Tip
If you're not familiar with how to build Adaptive Cards, check out the cards guide. Understanding their basics is a prerequisite for this guide.
Entry Point
To open a dialog, you need to supply a special type of action to the Adaptive Card. The TaskFetchAction is specifically designed for this purpose - it automatically sets up the proper Teams data structure to trigger a dialog. Once this button is clicked, the dialog will open and ask the application what to show.
To open a dialog, add a button to your Adaptive Card using OpenDialogData. This sets up the task/fetch protocol and includes a dialog_id that the SDK uses to route to the correct handler.
using Microsoft.Teams.Api.Activities;
using Microsoft.Teams.Apps;
using Microsoft.Teams.Apps.Annotations;
using Microsoft.Teams.Cards;
using Microsoft.Teams.Common.Logging;
//...
[Message]
public async Task OnMessage([Context] MessageActivity activity, [Context] IContext.Client client, [Context] ILogger log)
{
// Create the launcher adaptive card
var card = CreateDialogLauncherCard();
await client.Send(card);
}
private static AdaptiveCard CreateDialogLauncherCard()
{
var card = new AdaptiveCard
{
Body = new List<CardElement>
{
new TextBlock("Select the examples you want to see!")
{
Size = TextSize.Large,
Weight = TextWeight.Bolder
}
},
Actions = new List<Action>
{
new TaskFetchAction(new { opendialogtype = "simple_form" })
{
Title = "Simple form test"
},
new TaskFetchAction(new { opendialogtype = "webpage_dialog" })
{
Title = "Webpage Dialog"
},
new TaskFetchAction(new { opendialogtype = "multi_step_form" })
{
Title = "Multi-step Form"
}
}
};
return card;
}
from microsoft_teams.api import MessageActivity, MessageActivityInput, TypingActivityInput
from microsoft_teams.apps import ActivityContext
from microsoft_teams.cards import AdaptiveCard, TextBlock, SubmitAction, OpenDialogData
# ...
@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
await ctx.reply(TypingActivityInput())
card = AdaptiveCard(
schema="http://adaptivecards.io/schemas/adaptive-card.json",
body=[
TextBlock(
text="Select the examples you want to see!",
size="Large",
weight="Bolder",
)
]
).with_actions([
# OpenDialogData sets msteams.type = "task/fetch" and adds dialog_id for routing
SubmitAction(title="Simple form test").with_data(OpenDialogData("simple_form")),
SubmitAction(title="Webpage Dialog").with_data(OpenDialogData("webpage_dialog")),
SubmitAction(title="Multi-step Form").with_data(OpenDialogData("multi_step_form")),
])
message = MessageActivityInput(text="Enter this form").add_card(card)
await ctx.send(message)
import { cardAttachment, MessageActivity } from '@microsoft/teams.api';
import { App } from '@microsoft/teams.apps';
import {
AdaptiveCard,
IAdaptiveCard,
OpenDialogData,
SubmitAction,
} from '@microsoft/teams.cards';
// ...
app.on('message', async ({ send }) => {
await send({ type: 'typing' });
const card: IAdaptiveCard = new AdaptiveCard({
type: 'TextBlock',
text: 'Select the examples you want to see!',
size: 'Large',
weight: 'Bolder',
}).withActions(
// OpenDialogData sets msteams.type = "task/fetch" and adds dialog_id for routing
new SubmitAction()
.withTitle('Simple form test')
.withData(new OpenDialogData('simple_form')),
new SubmitAction()
.withTitle('Webpage Dialog')
.withData(new OpenDialogData('webpage_dialog')),
new SubmitAction()
.withTitle('Multi-step Form')
.withData(new OpenDialogData('multi_step_form'))
);
await send(new MessageActivity('Enter this form').addCard('adaptive', card));
});
Handling Dialog Open Events
Once an action is executed to open a dialog, the Teams client will send an event to the agent to request what the content of the dialog should be. When using TaskFetchAction, the data is nested inside an MsTeams property structure.
When a user clicks the button, Teams sends a task/fetch invoke to your app. Register a handler with @app.on_dialog_open("dialog_id") to handle a specific dialog, or @app.on_dialog_open() for a catch-all.
Tip
Use @app.on_dialog_open("simple_form") to handle specific dialogs directly, instead of a single catch-all handler with if-else logic. This keeps each handler focused and avoids routing boilerplate.
When a user clicks the button, Teams sends a task/fetch invoke to your app. Register a handler using dialog.open.<dialog_id> to handle a specific dialog, or dialog.open for a catch-all.
Tip
Use sub-routes like dialog.open.simple_form to handle specific dialogs directly, instead of a single catch-all handler with if-else logic. This keeps each handler focused and avoids routing boilerplate.
using System.Text.Json;
using Microsoft.Teams.Api.TaskModules;
using Microsoft.Teams.Apps;
using Microsoft.Teams.Apps.Activities.Invokes;
using Microsoft.Teams.Apps.Annotations;
using Microsoft.Teams.Common.Logging;
//...
[TaskFetch]
public Microsoft.Teams.Api.TaskModules.Response OnTaskFetch([Context] Tasks.FetchActivity activity, [Context] IContext.Client client, [Context] ILogger log)
{
var data = activity.Value?.Data as JsonElement?;
if (data == null)
{
log.Info("[TASK_FETCH] No data found in the activity value");
return new Microsoft.Teams.Api.TaskModules.Response(
new Microsoft.Teams.Api.TaskModules.MessageTask("No data found in the activity value"));
}
var dialogType = data.Value.TryGetProperty("opendialogtype", out var dialogTypeElement) && dialogTypeElement.ValueKind == JsonValueKind.String
? dialogTypeElement.GetString()
: null;
log.Info($"[TASK_FETCH] Dialog type: {dialogType}");
return dialogType switch
{
"simple_form" => CreateSimpleFormDialog(),
"webpage_dialog" => CreateWebpageDialog(_configuration, log),
"multi_step_form" => CreateMultiStepFormDialog(),
"mixed_example" => CreateMixedExampleDialog(),
_ => new Microsoft.Teams.Api.TaskModules.Response(
new Microsoft.Teams.Api.TaskModules.MessageTask("Unknown dialog type"))
};
}
from microsoft_teams.api import (
TaskFetchInvokeActivity, TaskModuleResponse,
TaskModuleContinueResponse, CardTaskModuleTaskInfo,
AdaptiveCardAttachment, card_attachment,
)
from microsoft_teams.apps import ActivityContext
from microsoft_teams.cards import AdaptiveCard
# ...
# Handle a specific dialog by ID — no if-else needed
@app.on_dialog_open("simple_form")
async def handle_simple_form_open(ctx: ActivityContext[TaskFetchInvokeActivity]):
card = AdaptiveCard(...)
return TaskModuleResponse(
task=TaskModuleContinueResponse(
value=CardTaskModuleTaskInfo(
title="Title of Dialog",
card=card_attachment(AdaptiveCardAttachment(content=card)),
)
)
)
import { cardAttachment } from '@microsoft/teams.api';
import { App } from '@microsoft/teams.apps';
import { AdaptiveCard, IAdaptiveCard } from '@microsoft/teams.cards';
// ...
// Handle a specific dialog by ID — no if-else needed
app.on('dialog.open.simple_form', async ({ activity }) => {
const card: IAdaptiveCard = new AdaptiveCard()...
return {
task: {
type: 'continue',
value: {
title: 'Title of Dialog',
card: cardAttachment('adaptive', card),
},
},
};
});
Rendering A Card
You can render an Adaptive Card in a dialog by returning a card response.
using System.Text.Json;
using Microsoft.Teams.Api;
using Microsoft.Teams.Api.TaskModules;
using Microsoft.Teams.Cards;
//...
private static Microsoft.Teams.Api.TaskModules.Response CreateSimpleFormDialog()
{
var choices = new List<Choice>
{
new Choice { Title = "Option 1", Value = "opt1" },
new Choice { Title = "Option 2", Value = "opt2" },
new Choice { Title = "Option 3", Value = "opt3" }
};
var dialogCard = new AdaptiveCard
{
Body = new List<CardElement>
{
new TextBlock("This is a simple form")
{
Size = TextSize.Large,
Weight = TextWeight.Bolder
},
new TextInput
{
Id = "name",
Label = "Name",
Placeholder = "Enter your name",
IsRequired = true
},
new ChoiceSetInput
{
Id = "preference",
Label = "Select your preference",
Choices = choices,
Style = StyleEnum.Compact
}
},
Actions = new List<Action>
{
new SubmitAction
{
Title = "Submit",
Data = new { submissiondialogtype = "simple_form" }
}
}
};
var taskInfo = new TaskInfo
{
Title = "Simple Form Dialog",
Card = new Attachment
{
ContentType = new ContentType("application/vnd.microsoft.card.adaptive"),
Content = dialogCard
}
};
return new Microsoft.Teams.Api.TaskModules.Response(
new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo));
}
from microsoft_teams.api import (
TaskFetchInvokeActivity, TaskModuleResponse,
TaskModuleContinueResponse, CardTaskModuleTaskInfo,
AdaptiveCardAttachment, card_attachment,
)
from microsoft_teams.apps import ActivityContext
from microsoft_teams.cards import AdaptiveCard, TextBlock, TextInput, SubmitAction, SubmitData
# ...
@app.on_dialog_open("simple_form")
async def handle_simple_form_open(ctx: ActivityContext[TaskFetchInvokeActivity]):
dialog_card = AdaptiveCard(
schema="http://adaptivecards.io/schemas/adaptive-card.json",
body=[
TextBlock(text="This is a simple form", size="Large", weight="Bolder"),
TextInput().with_label("Name").with_is_required(True).with_id("name").with_placeholder("Enter your name"),
],
# Use SubmitData to set the "action" field, which routes to @app.on_dialog_submit("action")
actions=[
SubmitAction().with_title("Submit").with_data(SubmitData("simple_form"))
]
)
return TaskModuleResponse(
task=TaskModuleContinueResponse(
value=CardTaskModuleTaskInfo(
title="Simple Form Dialog",
card=card_attachment(AdaptiveCardAttachment(content=dialog_card)),
)
)
)
import { cardAttachment } from '@microsoft/teams.api';
import { AdaptiveCard, TextInput, SubmitAction, SubmitData } from '@microsoft/teams.cards';
// ...
app.on('dialog.open.simple_form', async () => {
const dialogCard = new AdaptiveCard(
{
type: 'TextBlock',
text: 'This is a simple form',
size: 'Large',
weight: 'Bolder',
},
new TextInput()
.withLabel('Name')
.withIsRequired()
.withId('name')
.withPlaceholder('Enter your name')
)
// Use SubmitData to set the "action" field, which routes to dialog.submit.<action>
.withActions(
new SubmitAction().withTitle('Submit').withData(new SubmitData('simple_form'))
);
return {
task: {
type: 'continue',
value: {
title: 'Simple Form Dialog',
card: cardAttachment('adaptive', dialogCard),
},
},
};
});
Note
The action type for submitting a dialog must be Action.Submit. This is a requirement of the Teams client. If you use a different action type, the dialog will not be submitted and the agent will not receive the submission event.
Rendering A Webpage
You can render a webpage in a dialog as well. There are some security requirements to be aware of:
- The webpage must be hosted on a domain that is allow-listed as
validDomainsin the Teams app manifest for the agent - The webpage must also host the teams-js client library. The reason for this is that for security purposes, the Teams client will not render arbitrary webpages. As such, the webpage must explicitly opt-in to being rendered in the Teams client. Setting up the teams-js client library handles this for you.
using Microsoft.Teams.Api.TaskModules;
using Microsoft.Teams.Common;
//...
private static Microsoft.Teams.Api.TaskModules.Response CreateWebpageDialog(IConfiguration configuration, ILogger log)
{
var botEndpoint = configuration["BotEndpoint"];
if (string.IsNullOrEmpty(botEndpoint))
{
log.Warn("No remote endpoint detected. Using webpages for dialog will not work as expected");
botEndpoint = "http://localhost:3978"; // Fallback for local development
}
else
{
log.Info($"Using BotEndpoint: {botEndpoint}/tabs/dialog-form");
}
var taskInfo = new TaskInfo
{
Title = "Webpage Dialog",
Width = new Union<int, Size>(1000),
Height = new Union<int, Size>(800),
// Here we are using a webpage that is hosted in the same
// server as the agent. This server needs to be publicly accessible,
// needs to set up teams.js client library (https://www.npmjs.com/package/@microsoft/teams-js)
// and needs to be registered in the manifest.
Url = $"{botEndpoint}/tabs/dialog-form"
};
return new Microsoft.Teams.Api.TaskModules.Response(
new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo));
}
import os
from microsoft_teams.api import TaskModuleContinueResponse, TaskModuleResponse, UrlTaskModuleTaskInfo
# ...
@app.on_dialog_open("webpage_dialog")
async def handle_webpage_dialog_open(ctx):
return TaskModuleResponse(
task=TaskModuleContinueResponse(
value=UrlTaskModuleTaskInfo(
title="Webpage Dialog",
# The webpage must be publicly accessible, use the teams-js client library,
# and be registered in validDomains in the manifest.
url=f"{os.getenv('BOT_ENDPOINT')}/tabs/dialog-form",
width=1000,
height=800,
)
)
)
import { App } from '@microsoft/teams.apps';
// ...
app.on('dialog.open.webpage_dialog', async () => {
return {
task: {
type: 'continue',
value: {
title: 'Webpage Dialog',
// The webpage must be publicly accessible, use the teams-js client library,
// and be registered in validDomains in the manifest.
url: `${process.env['BOT_ENDPOINT']}/tabs/dialog-form`,
width: 1000,
height: 800,
},
},
};
});
Setting up Embedded Web Content
To serve web content for dialogs, you can use the AddTab functionality to embed HTML files as resources:
// In Program.cs when building your app
app.UseTeams();
app.AddTab("dialog-form", "Web/dialog-form");
// Configure project file to embed web resources
// In .csproj:
// <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
// <EmbeddedResource Include="Web/**" />
// <Content Remove="Web/**" />
Setting up Embedded Web Content
To serve web content for dialogs, you can use the page method to host static webpages:
import os
# In your app setup (e.g., main.py)
# Hosts a static webpage at /tabs/dialog-form
app.page("customform", os.path.join(os.path.dirname(__file__), "views", "customform"), "/tabs/dialog-form")
Setting up Embedded Web Content
To serve web content for dialogs, you can use the tab method to host static webpages:
import path from 'path';
// In your app setup (e.g., index.ts)
// Hosts a static webpage at /tabs/dialog-form
app.tab('dialog-form', path.join(__dirname, 'views', 'customform'));