In this exercise you will learn how to add conversation abilities to the bot to guide the user to create a help desk ticket.
Inside this folder you will find a solution with the code that results from completing the steps in this exercise. You can use this solution as guidance if you need additional help as you work through this exercise.
The following software is required for completing this exercise:
- Visual Studio 2017 Community or higher
- The Bot Framework Emulator (make sure it's configured with the
en-USLocale)
In this task you will modify the bot code to ask the user a sequence of questions before performing some action.
-
Open the solution you've obtained from the previous exercise. Alternatively, you can open the solution from the exercise1-EchoBot folder.
-
Open the Dialogs\RootDialog.cs file.
-
Add the following variables at the beginning of the
RootDialogclass. We will use them later to store the user answers.private string category; private string severity; private string description;
-
Replace the method
MessageReceivedAsyncwith the following code.public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument) { var message = await argument; await context.PostAsync("Hi! I’m the help desk bot and I can help you create a ticket."); PromptDialog.Text(context, this.DescriptionMessageReceivedAsync, "First, please briefly describe your problem to me."); } public async Task DescriptionMessageReceivedAsync(IDialogContext context, IAwaitable<string> argument) { this.description = await argument; await context.PostAsync($"Got it. Your problem is \"{this.description}\""); context.Done<object>(null); }
You will notice the Dialog implementation consist of a set of methods that are connected together using either the conversation flow control methods (provided by the
IDialogContextinterface) or some of thePromptDialoghelper methods which also use theIDialogContextmethods behind the scene to manage the conversation flow.When the conversation first starts, the dialog does not contain state, so the
Conversation.SendAsyncconstructsRootDialogand calls itsStartAsyncmethod. TheStartAsyncmethod callsIDialogContext.Waitwith the continuation delegate to specify the method that should be called when a new message is received (in this case is theMessageReceivedAsyncmethod).The Bot Framework SDK provides a set of built-in prompts to simplify collecting input from a user. The
MessageReceivedAsyncmethod waits for a message, which once received, posts a response greeting the user and callsPromptDialog.Text()to prompt him to describe the problem.Also, the response is persisted in the dialog instance by the framework. Notice it was marked as
[Serializable]. This is essential for storing temporary information in between the steps of the dialog. -
Run the solution in Visual Studio (click the Run button) and open the emulator. Type the bot URL as usual (
http://localhost:3979/api/messages) and test the bot as show below.
In this task you are going to add more message handlers to the bot code to prompt for all the ticket details.
-
Stop the app and open the Dialogs\RootDialog.cs file.
-
Update the
DescriptionMessageReceivedAsyncto store the description the user entered and prompt the ticket's severity. The following code uses thePromptDialog.Choicemethod which will give the user a set of choices to pick.public async Task DescriptionMessageReceivedAsync(IDialogContext context, IAwaitable<string> argument) { this.description = await argument; var severities = new string[] { "high", "normal", "low" }; PromptDialog.Choice(context, this.SeverityMessageReceivedAsync, severities, "Which is the severity of this problem?"); }
-
Next, add the
SeverityMessageReceivedAsyncmethod that receives the severity and prompts the user to enter the category using thePromptDialog.Textmethod.public async Task SeverityMessageReceivedAsync(IDialogContext context, IAwaitable<string> argument) { this.severity = await argument; PromptDialog.Text(context, this.CategoryMessageReceivedAsync, "Which would be the category for this ticket (software, hardware, networking, security or other)?"); }
-
Now add the
CategoryMessageReceivedAsyncmethod which stores the category and prompt the user to confirm the ticket creation using thePromptDialog.Confirmmethod.public async Task CategoryMessageReceivedAsync(IDialogContext context, IAwaitable<string> argument) { this.category = await argument; var text = $"Great! I'm going to create a \"{this.severity}\" severity ticket in the \"{this.category}\" category. " + $"The description I will use is \"{this.description}\". Can you please confirm that this information is correct?"; PromptDialog.Confirm(context, this.IssueConfirmedMessageReceivedAsync, text); }
NOTE: Notice that you can use Markdown syntax to create richer text messages. However it's important to note that not all channels support Markdown.
-
Add a method to handle the response from the confirmation message.
public async Task IssueConfirmedMessageReceivedAsync(IDialogContext context, IAwaitable<bool> argument) { var confirmed = await argument; if (confirmed) { await context.PostAsync("Awesome! Your ticked has been created."); } else { await context.PostAsync("Ok. The ticket was not created. You can start again if you want."); } context.Done<object>(null); }
-
Re-run the app and use the 'Start new conversation' button of the emulator
. Test the new conversation.NOTE: At this point if you talk to the bot again, the dialog will start over.
Now you have all the information for the ticket, however that information is discarded when the dialog ends. You will now add the code to create the ticket using an external API. For simplicity purposes, you will use a simple endpoint that saves the ticket into an in-memory array. In the real world, you would use any external API that is accessible from your bot's code.
NOTE: One important fact about bots to keep in mind is most bots you will build will be a front end to an existing API. Bots are simply apps, and they do not require artificial intelligence (AI), machine learning (ML), or natural language processing (NLP), to be considered a bot.
-
Stop the app. In the Controllers folder copy the TicketsController.cs from the assets folder of this hands-on lab. This will handle the POST request to the
/api/ticketsendpoint, add the ticket to an array and respond with the ticket id created. -
Add a new
Utilfolder to your project. In the new folder, copy the TicketAPIClient.cs file which will call the ticket API from the bot. -
Update your
Web.Configfile by adding the key TicketsAPIBaseUrl under the appSettings section. This key will contain the Base URL where the Ticket API will run. In this exercise, it will be the same URL where the bot is running, but in a real world scenario it may be different URL.<add key="TicketsAPIBaseUrl" value="http://localhost:3979/" />
-
Open the Dialogs\RootDialog.cs file.
-
Add the
HelpDeskBot.Utilusing statements.using HelpDeskBot.Util;
-
Replace the content of the
IssueConfirmedMessageReceivedAsyncmethod to make the call using the TicketAPIClient.public async Task IssueConfirmedMessageReceivedAsync(IDialogContext context, IAwaitable<bool> argument) { var confirmed = await argument; if (confirmed) { var api = new TicketAPIClient(); var ticketId = await api.PostTicketAsync(this.category, this.severity, this.description); if (ticketId != -1) { await context.PostAsync($"Awesome! Your ticked has been created with the number {ticketId}."); } else { await context.PostAsync("Ooops! Something went wrong while I was saving your ticket. Please try again later."); } } else { await context.PostAsync("Ok. The ticket was not created. You can start again if you want."); } context.Done<object>(null); }
-
Re-run the app and use the Start new conversation button of the emulator
. Test the full conversation again to check that the ticket id is returned from the API.
In this task you will enhance the confirmation message that is shown to the user after the ticket using Adaptive Cards. Adaptive Cards are an open card exchange format enabling developers to exchange UI content in a common and consistent way. Their content can be specified as a JSON object. Content can then be rendered natively inside a host application (Bot Framework channels), automatically adapting to the look and feel of the host.
-
You will need to add the
Microsoft.AdaptiveCardsNuGet package. Right click on your project's References folder in the Solution Explorer and click Manage NuGet packages. Search for theMicrosoft.AdaptiveCardsand then click on the Install button. Or you can type in the Packager Manager ConsoleInstall-Package Microsoft.AdaptiveCards. -
Open the Dialogs\RootDialog.cs file.
-
Add the
System.Collections.GenericandAdaptiveCardsusing statements.using System.Collections.Generic; using AdaptiveCards;
-
At the end of the file (inside the
RootDialogclass) add the following code that creates the Adaptive card:Anatomy of this example card,
- A header section containing the title with the ticketID
- A middle section containing a
ColumnSetwith two columns: one for aFactSetwith the Severity and Category and another with an icon - A last section that includes a description block with the ticket description
private AdaptiveCard CreateCard(int ticketId, string category, string severity, string description) { AdaptiveCard card = new AdaptiveCard(); var headerBlock = new TextBlock() { Text = $"Ticket #{ticketId}", Weight = TextWeight.Bolder, Size = TextSize.Large, Speak = $"<s>You've created a new Ticket #{ticketId}</s><s>We will contact you soon.</s>" }; var columnsBlock = new ColumnSet() { Separation = SeparationStyle.Strong, Columns = new List<Column> { new Column { Size = "1", Items = new List<CardElement> { new FactSet { Facts = new List<AdaptiveCards.Fact> { new AdaptiveCards.Fact("Severity:", severity), new AdaptiveCards.Fact("Category:", category), } } } }, new Column { Size = "auto", Items = new List<CardElement> { new Image { Url = "https://raw.githubusercontent.com/GeekTrainer/help-desk-bot-lab/master/assets/botimages/head-smiling-medium.png", Size = ImageSize.Small, HorizontalAlignment = HorizontalAlignment.Right } } } } }; var descriptionBlock = new TextBlock { Text = description, Wrap = true }; card.Body.Add(headerBlock); card.Body.Add(columnsBlock); card.Body.Add(descriptionBlock); return card; }
-
Update the
IssueConfirmedMessageReceivedAsyncmethod to call this method when the ticket was successfully created.public async Task IssueConfirmedMessageReceivedAsync(IDialogContext context, IAwaitable<bool> argument) { var confirmed = await argument; if (confirmed) { var api = new TicketAPIClient(); var ticketId = await api.PostTicketAsync(this.category, this.severity, this.description); if (ticketId != -1) { var message = context.MakeMessage(); message.Attachments = new List<Attachment> { new Attachment { ContentType = "application/vnd.microsoft.card.adaptive", Content = CreateCard(ticketId, this.category, this.severity, this.description) } }; await context.PostAsync(message); } else { await context.PostAsync("Ooops! Something went wrong while I was saving your ticket. Please try again later."); } } else { await context.PostAsync("Ok. The ticket was not created. You can start again if you want."); } context.Done<object>(null); }
-
Re-run the app and use the Start new conversation button of the emulator
. Test the new conversation. You should see the Adaptive Card as follows.
If you want to continue working on your own you can try with these tasks:



