DEV Community

Albert Bennett
Albert Bennett

Posted on • Updated on

How to: Adaptive Cards and the MS Bot Framework

Here we go again!
This time, I'd like to show you how to create a simple bot that can return an adaptive card as a response to the user instead of plain text.

This is a link to the github code, incase you wanted to go straight to the good stuff :)
If you like this post, feel free to like and share I'd really appreciate it.

Step 1:
Well this shouldn't be coming out of left field... we need to create a bot using the MS bot Framework. I'm creating the project in VS 2019, so for me I started the project from the echo bot template using .net core 3.1 and the v4 Bot Framework.

image

Step 2:
Next up we need to install the correct NuGet package to get adaptive cards into our bot. See below for snapshot. The version that I will be installing is v2.7.2, as it is the latest version of the package at the time of writing.
image

Step 3:
Next up with the package installed we will start by creating a factory to create the different adaptive cards that we want for the demo. For the demo I wanted to demonstrate only three different configurations of adaptive cards. One with a set of buttons, one with plain text, and the other with a combination of both. This class will be injected into our ActivityHandler class which I'll explain later on. See code below of how I created the factory.

 public sealed class AdaptiveCardFactory : IAdaptiveCardFactory
    {
        //The default schema for the adaptive cards, it tells the client what version of adaptive cards to render
        static AdaptiveSchemaVersion defaultSchema = new(1, 0);

        /// <summary>
        /// Creates a simple adaptive card with two submit actions.
        /// </summary>
        public Attachment CreateOptionsCard()
        {
            var options = new Dictionary<string, string>
            {
                { ConstantStrings.get_joke, ConstantStrings.joke_request },
                { ConstantStrings.return_to_start, ConstantStrings.return_message }
            };

            var card_actions = new List<AdaptiveAction>();

            foreach (var option in options) 
            {
                card_actions.Add(new AdaptiveSubmitAction
                {
                    Title = option.Key,
                    Data = option.Value
                });
            }

            AdaptiveCard card = new(defaultSchema)
            {
                Body = new List<AdaptiveElement>()
                {
                    new AdaptiveActionSet
                    {
                        Actions = card_actions
                    }
                }
            };

            return CreateAdaptiveCardAttachment(card.ToJson());
        }

        /// <summary>
        /// Creates an adaptive card that contains a textbox only
        /// </summary>
        public Attachment CreateTextCard()
        {
            var message = ConstantStrings.welcome_message;

            AdaptiveCard card = new(defaultSchema)
            {
                Body = new List<AdaptiveElement>()
                {
                    new AdaptiveTextBlock()
                    {
                        Text = message,
                        Size = AdaptiveTextSize.Default,
                        Wrap = true
                    }
                }
            };

            return CreateAdaptiveCardAttachment(card.ToJson());
        }

        /// <summary>
        /// Creates an adaptive card that has both text and a submit action
        /// </summary>
        public Attachment CreateCombinationCard(string message)
        {
            AdaptiveCard card = new(defaultSchema)
            {
                Body = new List<AdaptiveElement>()
                {
                    new AdaptiveTextBlock()
                    {
                        Text = message,
                        Size = AdaptiveTextSize.Default
                    },
                    new AdaptiveActionSet
                    {
                        Actions = new List<AdaptiveAction>
                        {
                            new AdaptiveSubmitAction
                            {
                                Title = ConstantStrings.return_to_start,
                                Data = ConstantStrings.return_message
                            }
                        }
                    }
                }
            };

            return CreateAdaptiveCardAttachment(card.ToJson());
        }

        /// <summary>
        /// Using a JSON representation of the adaptive card return an attachment that can be sent to the client
        /// </summary>
        Attachment CreateAdaptiveCardAttachment(string jsonData)
        {
            var adaptiveCardAttachment = new Attachment()
            {
                ContentType = "application/vnd.microsoft.card.adaptive",
                Content = JsonConvert.DeserializeObject(jsonData),
            };

            return adaptiveCardAttachment;
        }
    }
Enter fullscreen mode Exit fullscreen mode

The one major thing to note with the factory code is that each of the create methods returns an attachment. The attachment will be sent to the client and represents the adaptive card. They are sent in the body of a post request back to the client and rendered by the bot framework. You'll see this in action in the next step. For this demo I'm only really making use of text blocks and submit actions. However there are a myriad of other features and components that you can use with adaptive cards. If you are interested in a more complete list and even in a tool to help design adaptive cards see this link. It is also worth noting the data and title fields of the submit actions. The title field is the display text for the control and what the user will see first, the data field is what will be sent to the bot when the user clicks on the control the user will see this value when they click on the submit action.

Step 4:
Now it is time to start returning the adaptive card to the user. It is a simple process. That can be configured in two places in the test bot. Both places is when the bot would return a response to the client. Those are in the OnMessageActivityAsync and OnMembersAddedAsync methods. OnMembersAddedAsync is triggered whenever a new user enters the chat, and OnMessageActivityAsync is trigged when the users sends a response to the bot.
With the factory created, the code changes are straightforward:

        protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
        {
            var welcomeCard = _adaptiveCardFactory.CreateTextCard();

            foreach (var member in membersAdded)
            {
                if (member.Id != turnContext.Activity.Recipient.Id)
                {
                    await turnContext.SendActivityAsync(MessageFactory.Attachment(welcomeCard), cancellationToken);
                }
            }
        }
        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            var optionsCard = _adaptiveCardFactory.CreateOptionsCard();
            await turnContext.SendActivityAsync(MessageFactory.Attachment(optionsCard), cancellationToken);
        }
Enter fullscreen mode Exit fullscreen mode

In these two methods we are essentially doing the same thing. We are creating an adaptive card and sending it as an attachment to the client to be rendered.

Step 5:
In this step we will be working in the OnMessageActivityAsync method. It's just to check for user input and respond to it correctly. In the GitHub repo I've added some more logic and functionality, most of which is irrelevant for the demo. See finalized code below for checking for user input.

        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            Attachment optionsCard;

            if (turnContext.Activity.Text.Equals(ConstantStrings.return_message))
            {
                optionsCard = _adaptiveCardFactory.CreateTextCard();
            } 
            else if (turnContext.Activity.Text.Equals(ConstantStrings.joke_request)) 
            {
                var joke = await _jokeService.GetJoke();

                optionsCard = _adaptiveCardFactory.CreateCombinationCard(joke);
            }
            else
            {
                optionsCard = _adaptiveCardFactory.CreateOptionsCard();
            }

            await turnContext.SendActivityAsync(MessageFactory.Attachment(optionsCard), cancellationToken);
        }
Enter fullscreen mode Exit fullscreen mode

The main thing to note in these changes is that the users input is not always in turnContext.Activity.Text this is mostly for just text based user input other times you can find it in turnContext.Activity.Value you'll normally find user input here for more complex types such as dates chosen from a datepicker.

Testing the bot for yourself:
To test the bot out yourself you'll need to download the code and also install the latest version of the Bot Framework Emulator (you can find it here). This will allow you to test the bot over web chat. It's a straight forward process just run the code and connect the bot. When connecting the bot, the url should look something like this: http://localhost:3978/api/messages this is the endpoint for requests to be sent to the bot.
image

On the side panel of the emulator you can see the details of the responses to and from the bot as well which is helpful for debugging the code.



Thanks for reading my blog post.
You can find the code here at this link.
Feel free to connect with me on linkedin you can find me here.
See you in the next post >>>

Discussion (0)