Source Code

Highlight

Driving user conversations is key for any successful bot. By leveraging bot framework’s rich cards in conversations developers can enrich user experience by allowing them to interact with bots without need of a typing.

Why rich cards

A healthy debate can be found on the web around topics how chatbots should act, how should they interact and if it is better to let users speak freely or drive them with buttons and options. When user is typing (or using speech) it is often required that bot must be very well trained. It also must be very detailed in its purpose and the broader range of functionalities the harder it is to learn such a bot. Sometimes a simplicity is all there’s to it.

Building a bot which automates simple business process like shopping, checking request status, ordering new hardware from your IT department, doesn’t need to involve natural language processing. Instead in most cases it’s better to present users with predefined options in form of a buttons. This form is simple and straightforward and most importantly, doesn’t require user to type in anything at all!

Let’s see how is bot framework prepared for such scenarios but creating a bot which presents users with options and displays detailed information using rich cards.

Create a chatbot with rich cards

This post is more technical in nature as I’m not going into business justification of creating chatbots which use rich cards. Purpose of this post is to show how using bot service in Azure to deliver this functionality.

Create simple form flow bot from the default Azure template. Follow below steps to do so it read about it in more detail on Creation of a chat bot section in my previous post.

Instead of reading you can watch!

  1. Log into Azure Portal
  2. Find Azure Bot Service in Marketplace
  3. Create Bot Service
  4. Fill in the data.
    • Consumption Plan will Functions pricing model (pay per execution). Preferred for most bots.
    • App Service Plan will use App Service (pay for reserved capacity). Preferred if using Free Tier for learning purposes. Portal should automatically open bot editor after deployment is successful. If not, open bot can be opened via either Portal > App Services, Notifications or Dashboard if ‘Pin to Dashboard’ option was selected.
  5. Click create button redirects to bot wizard where C# should be set language and Form as a bot template.
  6. Microsoft App Registration page where application must be first registered. Note: Forgetting to save app password might be troublesome in some scenarios but this isn’t the case. App password will be available in bot app settings
  7. Once bot is created, developer is transferred back to Azure Development portal. Hitting Create button begins the resource provisioning process. Provisioning takes between 2 and 5 minutes and during that time template sets up Bot Framework application, storage account for the code and logic, bot service application and application insights.
  8. Test the bot with IDE chat control

Now that the bot is provisioned and it works simply replace MainDialog class with following code.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web;
using System.Linq;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;

[Serializable]
public class MainDialog : IDialog<object>
{
    private List<string> options;

    public Task StartAsync(IDialogContext context)
    {
        context.Wait(this.MessageReceivedAsync);
        return Task.CompletedTask; 
    }

    public async virtual Task MessageReceivedAsync(
        IDialogContext context, IAwaitable<IMessageActivity> result)
    {
        var message = await result;
        options = new List<string> { };
        ShowPrompt(context);
    }
    
    public async Task DisplaySelectedCard(IDialogContext context, IAwaitable<string> result)
    {
        var selectedCard = await result;

        var message = context.MakeMessage();
        message.Attachments = GetSelectedCard(selectedCard);
        
        await context.PostAsync(message);
        ShowPrompt(context);
    }
    
    private void ShowPrompt(IDialogContext context) 
    {
        PromptDialog.Choice<string>(
            context,
            this.DisplaySelectedCard,
            this.options,
            "What card would like to test?",
            "Ooops, what you wrote is not a valid option, please try again",
            3,
            PromptStyle.PerLine);
    }

    private static List<Attachment> GetSelectedCard(string selectedCard)
    {
        switch (selectedCard)
        {
            default:
                return new List<Attachment>();
        }
    }
}

Small rundown of the methods in the class.

  • DisplaySelectedCard method is a handler which will be executed after user selects any choice
  • ShowPrompt display prompts with options of rich cards which will be shown; above code does not show anything yet
    • context - current conversation context
    • this.DisplaySelectedCard - function to execute after choice is made
    • this.options - the possible options all of which must be convertible to a string
    • “What card would like to test?” - the prompt to show to the user
    • “Ooops, what you wrote is not a valid option, please try again” - what to show on retry
    • 3 - the number of times to retry
    • PromptStyle.PerLine - Style of the prompt, PerLine returns text message with listed options
  • GetSelectedCard returns card based on user selection

Hero Cards

Hero cards work best when presenting content where image is one of the, if the most important information. Best examples of hero card usage are online shops where item picture is often deciding factor for the purchase.

  1. Add new method in MainDialog class
    private static Attachment GetHeroCard()
    {
        var heroCard = new HeroCard
        {
            Title = "Marczak.IO Azure Bot Series Hero Card",
            Subtitle = "Smarter bots with natural language processing",
            Text = "The closer bot interaction gets to the one of a human the better the end user experience will be. See how to leverage Microsoft Cognitive Services LUIS for natural language processing so that users can type naturally while allowing bots to understand and act.",
            Images = new List<CardImage> 
            { 
                new CardImage("https://marczak.io/images/botseries-luis/splash.svg") 
            },
            Buttons = new List<CardAction> 
            { 
                new CardAction(ActionTypes.OpenUrl, 
                    "Read Article", 
                    value: "https://marczak.io/posts/botseries-smarter-bots-with-nlp/") 
            }
        };
    
        return heroCard.ToAttachment();
    }
    which outputs
  2. Modify function GetSelectedCard by adding new case for Hero Card
    private static List<Attachment> GetSelectedCard(string selectedCard)
    {
        switch (selectedCard)
        {
            case HeroCard:
                return new List<Attachment>() { GetHeroCard() }; 
    
            default:
                return new List<Attachment>();
        }
    }
  3. Add new private property to the CardsDialog class
    private const string HeroCard = "Hero card";
  4. Modify initialization in contructor
    public async virtual Task MessageReceivedAsync(
        IDialogContext context, IAwaitable<IMessageActivity> result)
    {
        var message = await result;
    
        options = new List<string> { HeroCard };
    
        ShowPrompt(context);
    }
  5. Test the bot

Suggestions

Users interaction is important and by asking them to type in their option selection can be in fact very tedious task. By default, showing options in prompt return a list of available options and user must type in their selection.

With a one option this behavior can be changed to show options as clickable list called suggestions. To do that go to ShowPrompt method and change PromptStyle from PerLine to Keyboard which will activate suggestions.

private void ShowPrompt(IDialogContext context) 
{
    PromptDialog.Choice<string>(
        context,
        this.DisplaySelectedCard,
        this.options,
        "What card would like to test?",
        "Ooops, what you wrote is not a valid option, please try again",
        3,
        PromptStyle.Keyboard);
}

Which results in following change

PromptStyle.PerLine
PromptStyle.Keyboard

Now instead of typing in their selection user can simply click on the option and it will automatically be retyped back to the chat window.

Thumbnail Cards

Thumbnail cards are very similar to hero cards. They present the same content, only the layout is different. Thumbnail cards are best used when content of the card is of the most importance and image is just addition. For instance, a company logo. Their code initialization looks exactly the same as it was for hero cards.

  1. Add new method in MainDialog class
    private static Attachment GetThumbnailCard()
    {
        var heroCard = new ThumbnailCard
        {
            Title = "Marczak.IO Azure Bot Series Thumbnail Card",
            Subtitle = "Precompiled Bots with VS 2017 tooling",
            Text = "With the release of Visual Studio 2017 15.3 developers can take advantage of new Tools for Azure Functions. Learn how to take advantage of those tools to deliver pre-compiled bots for Azure Bot Service.",
            Images = new List<CardImage> 
            { 
                new CardImage("https://marczak.io/images/compilable-bots/splash.svg") 
            },
            Buttons = new List<CardAction> 
            { 
                new CardAction(ActionTypes.OpenUrl, 
                    "Read Article", 
                    value: "https://marczak.io/posts/precompiled-bots-for-bot-service/") 
            }
        };
    
        return heroCard.ToAttachment();
    }
    which outputs
  2. Modify function GetSelectedCard by adding new case for Thumbnail Card
    private static List<Attachment> GetSelectedCard(string selectedCard)
    {
        switch (selectedCard)
        {
            case ThumbnailCard:
                return new List<Attachment>() { GetThumbnailCard() }; 
            case HeroCard:
                return new List<Attachment>() { GetHeroCard() }; 
    
            default:
                return new List<Attachment>();
        }
    }
  3. Add new private property to the CardsDialog class
    private const string ThumbnailCard = "Thumbnail card";
  4. Modify initialization in contructor
    public async virtual Task MessageReceivedAsync(
        IDialogContext context, IAwaitable<IMessageActivity> result)
    {
        var message = await result;
    
        options = new List<string> { HeroCard, ThumbnailCard };
    
        ShowPrompt(context);
    }
  5. Test the bot

Receipt Cards

  1. Add new method in MainDialog class
    private static Attachment GetReceiptCard()
    {
        var receiptCard = new ReceiptCard
        {
            Title = "Adam Marczak",
            Facts = new List<Fact> 
            { 
                new Fact("Order Number", "1234"), 
                new Fact("Payment Method", "VISA 1234-****") 
            },
            Items = new List<ReceiptItem>
            {
                new ReceiptItem("Func App", 
                    price: "$ 11.21", 
                    quantity: "16", 
                    image: new CardImage(url: "https://marczak.io/images/tags/functions.svg")),
                new ReceiptItem("VS License", 
                    price: "$ 450.00", 
                    quantity: "1", 
                    image: new CardImage(url: "https://marczak.io/images/tags/vs17.svg")),
            },
            Tax = "$ 7.50",
            Total = "$ 468.71",
            Buttons = new List<CardAction>
            {
                new CardAction(
                    ActionTypes.OpenUrl,
                    "More information",
                    "https://marczak.io/images/tags/azure.svg",
                    "https://azure.microsoft.com/en-us/pricing/")
            }
        };
    
        return receiptCard.ToAttachment();
    }
    which outputs
  2. Modify function GetSelectedCard by adding new case for Thumbnail Card
    private static List<Attachment> GetSelectedCard(string selectedCard)
    {
        switch (selectedCard)
        {
            case ReceiptCard:
                return new List<Attachment>() { GetReceiptCard() }; 
            case ThumbnailCard:
                return new List<Attachment>() { GetThumbnailCard() }; 
            case HeroCard:
                return new List<Attachment>() { GetHeroCard() }; 
    
            default:
                return new List<Attachment>();
        }
    }
  3. Add new private property to the CardsDialog class
    private const string ReceiptCard = "Receipt card";
  4. Modify initialization in contructor
    public async virtual Task MessageReceivedAsync(
        IDialogContext context, IAwaitable<IMessageActivity> result)
    {
        var message = await result;
    
        options = new List<string> { HeroCard, ThumbnailCard, ReceiptCard };
    
        ShowPrompt(context);
    }
  5. Test the bot

Video Cards

  1. Add new method in MainDialog class
    private static Attachment GetVideoCard()
    {
        var videoCard = new VideoCard
        {
            Title = "MarczakIO Azure Bot Series Video Card",
            Subtitle = "Introduction to chatbots",
            Text = "In recent years increased availability of internet across the world and evolution of mobile devices popularized messaging applications which became most common way to communicate in private life as well as everyday business. It is no surprise that leveraging those communication channels has huge potential in any business..",
            Image = new ThumbnailUrl
            {
                Url = "https://marczak.io/images/botseries-intro/splash.svg"
            },
            Media = new List<MediaUrl>
            {
                new MediaUrl()
                {
                    Url = "https://marczak.io/images/botseries-rich-cards/CreatingBot.mp4"  
                }
            },
            Buttons = new List<CardAction>
            {
                new CardAction()
                {
                    Title = "Read Article",
                    Type = ActionTypes.OpenUrl,
                    Value = "https://marczak.io/posts/botseries-introductiontobots/"
                }
            }
        };
    
        return videoCard.ToAttachment();
    }
    which outputs
  2. Modify function GetSelectedCard by adding new case for Thumbnail Card
    private static List<Attachment> GetSelectedCard(string selectedCard)
    {
        switch (selectedCard)
        {
            case VideoCard:
                return new List<Attachment>() { GetVideoCard() }; 
            case ReceiptCard:
                return new List<Attachment>() { GetReceiptCard() }; 
            case ThumbnailCard:
                return new List<Attachment>() { GetThumbnailCard() }; 
            case HeroCard:
                return new List<Attachment>() { GetHeroCard() }; 
    
            default:
                return new List<Attachment>();
        }
    }
  3. Add new private property to the CardsDialog class
    private const string VideoCard = "Video card";
  4. Modify initialization in contructor
    public async virtual Task MessageReceivedAsync(
        IDialogContext context, IAwaitable<IMessageActivity> result)
    {
        var message = await result;
    
        options = new List<string> { HeroCard, ThumbnailCard, ReceiptCard, VideoCard };
    
        ShowPrompt(context);
    }
  5. Test the bot

Audio Cards

  1. Add new method in MainDialog class
    private static Attachment GetAudioCard()
    {
        var audioCard = new AudioCard
        {
            Title = "MarczakIO Azure Bot Series Audio Card",
            Subtitle = "Smarter bots with natural language processing",
            Text = "LUIS is shorthand name for Language Understanding Intelligent Service. It is one of Microsoft services available in Cognitive Services package in Azure. LUIS is very well described by a quote from Microsoft Azure website.",
            Image = new ThumbnailUrl
            {
                Url = "https://marczak.io/images/botseries-luis/splash.svg"
            },
            Media = new List<MediaUrl>
            {
                new MediaUrl()
                {
                    Url = "https://marczak.io/images/botseries-rich-cards/SmarterBotsVoice.mp3"
                }
            },
            Buttons = new List<CardAction>
            {
                new CardAction()
                {
                    Title = "Read More",
                    Type = ActionTypes.OpenUrl,
                    Value = "https://marczak.io/posts/botseries-smarter-bots-with-nlp/"
                }
            }
        };
    
        return audioCard.ToAttachment();
    }
    which outputs
  2. Modify function GetSelectedCard by adding new case for Thumbnail Card
    private static List<Attachment> GetSelectedCard(string selectedCard)
    {
        switch (selectedCard)
        {
            case AudioCard:
                return new List<Attachment>() { GetAudioCard() }; 
            case VideoCard:
                return new List<Attachment>() { GetVideoCard() }; 
            case ReceiptCard:
                return new List<Attachment>() { GetReceiptCard() }; 
            case ThumbnailCard:
                return new List<Attachment>() { GetThumbnailCard() }; 
            case HeroCard:
                return new List<Attachment>() { GetHeroCard() }; 
    
            default:
                return new List<Attachment>();
        }
    }
  3. Add new private property to the CardsDialog class
    private const string AudioCard = "Audio card";
  4. Modify initialization in contructor
    public async virtual Task MessageReceivedAsync(
        IDialogContext context, IAwaitable<IMessageActivity> result)
    {
        var message = await result;
    
        options = new List<string> { HeroCard, ThumbnailCard, ReceiptCard, VideoCard, AudioCard };
    
        ShowPrompt(context);
    }
  5. Test the bot

Sign-in Cards

  1. Add new method in MainDialog class
    private static Attachment GetSigninCard()
    {
        var signinCard = new SigninCard
        {
            Text = "MarczakIO - Azure Sign-In Card",
            Buttons = new List<CardAction> 
            { 
                new CardAction(ActionTypes.Signin, 
                    "Sign-in", 
                    value: "https://login.microsoftonline.com/") 
            }
        };
    
        return signinCard.ToAttachment();
    }
    which outputs
  2. Modify function GetSelectedCard by adding new case for Thumbnail Card
    private static List<Attachment> GetSelectedCard(string selectedCard)
    {
        switch (selectedCard)
        {
            case SigninCard:
                return new List<Attachment>() { GetSigninCard() }; 
            case AudioCard:
                return new List<Attachment>() { GetAudioCard() }; 
            case VideoCard:
                return new List<Attachment>() { GetVideoCard() }; 
            case ReceiptCard:
                return new List<Attachment>() { GetReceiptCard() }; 
            case ThumbnailCard:
                return new List<Attachment>() { GetThumbnailCard() }; 
            case HeroCard:
                return new List<Attachment>() { GetHeroCard() }; 
    
            default:
                return new List<Attachment>();
        }
    }
  3. Add new private property to the CardsDialog class
    private const string SigninCard = "Sign-in card";
  4. Modify initialization in contructor
    public async virtual Task MessageReceivedAsync(
        IDialogContext context, IAwaitable<IMessageActivity> result)
    {
        var message = await result;
    
        options = new List<string> { 
            HeroCard, ThumbnailCard, ReceiptCard, VideoCard, AudioCard, SigninCard 
        };
    
        ShowPrompt(context);
    }
  5. Test the bot
  1. Add new method in MainDialog class
    private static List<Attachment> GetCarouselCard()
    {
        // Return list of cards, any type of card works
        return new List<Attachment>() {
            GetHeroCard(), 
            GetThumbnailCard(), 
            GetAudioCard(), 
            GetVideoCard()
        };
    }
    which outputs
  2. Modify function GetSelectedCard by adding new case for Thumbnail Card
    private static List<Attachment> GetSelectedCard(string selectedCard)
    {
        switch (selectedCard)
        {
            case CarouselCard:
                return new List<Attachment>() { GetCarouselCard() }; 
            case SigninCard:
                return new List<Attachment>() { GetSigninCard() }; 
            case AudioCard:
                return new List<Attachment>() { GetAudioCard() }; 
            case VideoCard:
                return new List<Attachment>() { GetVideoCard() }; 
            case ReceiptCard:
                return new List<Attachment>() { GetReceiptCard() }; 
            case ThumbnailCard:
                return new List<Attachment>() { GetThumbnailCard() }; 
            case HeroCard:
                return new List<Attachment>() { GetHeroCard() }; 
    
            default:
                return new List<Attachment>();
        }
    }
  3. Add new private property to the CardsDialog class
    private const string CarouselCard = "Carousel card";
  4. Modify initialization in contructor
    public async virtual Task MessageReceivedAsync(
        IDialogContext context, IAwaitable<IMessageActivity> result)
    {
        var message = await result;
    
        options = new List<string> { 
            HeroCard, ThumbnailCard, ReceiptCard, VideoCard, AudioCard, SigninCard, CarouselCard
        };
    
        ShowPrompt(context);
    }
  5. Test the bot

What’s next?

That of ‘course isn’t all to it

  • There are more cards in bot framework library than those show in this post. Head to their MSDN page to see more in action.
  • See which cards are supported by which channel using channel inspector

Play around with it!

Chatbot is now ready to use. Clicking on below button will open live chat window.

Source Code

Did you enjoy the article?

Share it!

More tagged posts