Blog Post

IIS Support Blog
4 MIN READ

How to Add an Adaptive Card in Microsoft 365 Agent SDK

meenakshiBalekar's avatar
Mar 31, 2026

Microsoft’s new Agent SDK is replacing the old Azure Bot Service SDK. With the Agent SDK, building conversational apps across Microsoft 365 Teams, M365 Chat, and Copilot becomes far simpler and more powerful.

One of the most important UI capabilities is Adaptive Cards, which let your agent send structured, interactive content such as forms, inputs, buttons, and layouts.

In this guide, you’ll learn exactly how to:

  • Create an Agent SDK bot
  • Send an Adaptive Card when a user joins
  • Handle Action.Execute submit events
  • Parse user input from the card
  • Respond with text

I will walk through the full working code from my project.

You can download complete sample from : M365AgentSDKAdaptiveCard

Step 1: Understanding How Adaptive Cards Work in Agent SDK

Adaptive Cards are sent in the Agent SDK using:

var attachment = new Attachment {
    ContentType = "application/vnd.microsoft.card.adaptive",
    Content = <JSON>
};

You then send them like this:

await turnContext.SendActivityAsync(MessageFactory.Attachment(attachment));

And to handle submit actions (Action.Execute), the Agent SDK triggers:

ActivityTypes.Invoke  
Name = "adaptiveCard/action"

Step 2: Use the Adaptive Card Designer

Create or test your card on our new designer here: https://adaptivecards.microsoft.com/designer

Your sample card:

  • Collects name & age
  • Uses Action.Execute with verb "personalInfo"

Step 3: The Full Working Agent SDK Code

Below is the complete working implementation showing:

✔ Welcome card using Adaptive Card
✔ Parsing Action.Execute values
✔ Responding back to the user

This is based entirely on your code, cleaned up and rewritten for clarity & correctness.

Complete Agent SDK Bot with Adaptive Card

using Microsoft.Agents.Builder;
using Microsoft.Agents.Builder.App;
using Microsoft.Agents.Builder.State;
using Microsoft.Agents.Core.Models;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;

namespace MyFirstAgentSDK.Bot;

public class EchoBot : AgentApplication
{
    public EchoBot(AgentApplicationOptions options, IHostEnvironment env, ILoggerFactory loggerFactory) : base(options)
    {
        OnConversationUpdate(ConversationUpdateEvents.MembersAdded, WelcomeMessageAsync);
        OnActivity(ActivityTypes.Message, OnMessageAsync, rank: RouteRank.Last);
        OnActivity(ActivityTypes.Invoke, OnInvokeAsync);
    }

    private async Task WelcomeMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
    {
        foreach (ChannelAccount member in turnContext.Activity.MembersAdded)
        {
            if (member.Id != turnContext.Activity.Recipient.Id)
            {
                var attachment = new Attachment
                {
                    ContentType = "application/vnd.microsoft.card.adaptive",
                    Content = """
{
  "type": "AdaptiveCard",
  "version": "1.4",
  "schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "body": [
    {
      "type": "Container",
      "items": [
        {
          "type": "TextBlock",
          "text": "Please enter your personal information",
          "weight": "Bolder",
          "size": "Medium",
          "color": "Accent"
        },
        {
          "type": "Input.Text",
          "id": "Name",
          "label": "What's your name?",
          "placeholder": "Enter your full name",
          "maxLength": 50,
          "isRequired": true,
          "errorMessage": "Name is required"
        },
        {
          "type": "Input.Number",
          "id": "Age",
          "label": "How old are you?",
          "placeholder": "Enter your age",
          "min": 1,
          "max": 150,
          "isRequired": true,
          "errorMessage": "Please enter a valid age between 1 and 150"
        }
      ],
      "style": "emphasis",
      "spacing": "Medium"
    }
  ],
  "actions": [
    {
      "type": "Action.Execute",
      "title": "Submit",
      "verb": "personalInfo",
      "style": "positive"
    }
  ]
}
"""
                };
                await turnContext.SendActivityAsync(MessageFactory.Attachment(attachment), cancellationToken);
            }
            else
            {
                await turnContext.SendActivityAsync(MessageFactory.Text("Hello and Welcome!"), cancellationToken);
            }
        }
    }

    private async Task OnMessageAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
    {
        await turnContext.SendActivityAsync($"You said: {turnContext.Activity.Text}", cancellationToken: cancellationToken);
    }

    private async Task OnInvokeAsync(ITurnContext turnContext, ITurnState turnState, CancellationToken cancellationToken)
    {
        if (turnContext.Activity.Name == "adaptiveCard/action")
        {
            JsonElement root;
            if (turnContext.Activity.Value is JsonElement element)
            {
                root = element;
            }
            else
            {
                var json = JsonSerializer.Serialize(turnContext.Activity.Value);
                root = JsonDocument.Parse(json).RootElement;
            }

            if (root.TryGetProperty("action", out var action))
            {
                if (action.TryGetProperty("verb", out var verbElement) && verbElement.GetString() == "personalInfo")
                {
                    if (action.TryGetProperty("data", out var data))
                    {
                        var name = data.GetProperty("Name").GetString();
                        var age = data.GetProperty("Age").ToString();

                        await turnContext.SendActivityAsync(MessageFactory.Text($"Hello {name}, you are {age} years old!"), cancellationToken);

                        var invokeResponse = new Activity
                        {
                            Type = ActivityTypes.InvokeResponse,
                            Value = new InvokeResponse { Status = 200 }
                        };
                        await turnContext.SendActivityAsync(invokeResponse, cancellationToken);
                    }
                }
            }
        }
    }
}

 

Step 4: What This Code Does

1. Sends an Adaptive Card when a new user joins or as per your criteria 

The card that I have used includes:

  • Text
  • Name input (required)
  • Age input (required)
  • A submit button with verb "personalInfo"

2. When the user clicks Submit

Teams / Message Extension sends:

invoke name = adaptiveCard/action

OnInvokeAsync() receives:

{ "action": { "verb": "personalInfo", "data": { "Name": "...", "Age": "..." } } }

 

3. Bot parses and sends a text response

Example output:

Hello Meenakshi, you are 30 years old! ( P.S I am older than this )

Locally when you run the project on playground it looks like :

 

This is how it looks on test in webchat 

And this how it looks on teams :

4. Responds with 200 status

This is required for Teams & M365:

 var invokeResponse = new Activity
 {
     Type = ActivityTypes.InvokeResponse,
     Value = new InvokeResponse { Status = 200 }
 };
 await turnContext.SendActivityAsync(invokeResponse, cancellationToken);

Conclusion

With the Microsoft 365 Agent SDK:

  • Action.Execute events are handled inside OnInvokeAsync
  • Inputs are parsed through the Activity.Value JSON payload
  • The SDK is lightweight and much simpler than the old Azure Bot SDK

Your bot is now fully capable of collecting structured user input using Adaptive Cards.

Drop in any queries or samples that you would like me to explain.

Happy Learning!

Updated Jan 30, 2026
Version 1.0
No CommentsBe the first to comment