Reusing FormBuilder - c#

My bot receives some user info like order no and few other details based on that it searches the orders. If more that 1 order is returned then it asks user to provide more info so asks for 2 additional details.
Now my problem is that as I have already build the form using FormBuilder and on completion I ask user to provide more info so that should be the approach to ask for additional info. Should I create another form and request info or is there any way to add the additional fields to the same form in the completion method.
I want to ask for more user inputs after initial search is complete and msg for more than 1 order found is displayed.
As my properties SubOrderNumber & SubOrderVersion are in OrderQuery class so Should i build new form for OrderQuery or is there anyother way to add these two to existing Order form.
using System;
using Microsoft.Bot.Builder.FormFlow;
namespace Bot
{
[Serializable]
public class OrderSearchQuery
{
[Prompt("Please enter {&}")]
public string OrderNumber { get; set; }
[Prompt("Please enter {&}?")]
public String Location { get; set; }
[Prompt("Please provide last status {&}?")]
public string Status{ get; set; }
[Prompt("Please enter {&}?")]
public string SubOrderNumber { get; set; }
[Prompt("Please enter {&}?")]
public string SubOrderVersion { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using Microsoft.Bot.Connector;
namespace Bot.Dialogs
{
[Serializable]
public class OrderDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to the Order helper!");
var orderFormDialog = FormDialog.FromForm(this.BuildOrderForm, FormOptions.PromptInStart);
context.Call(orderFormDialog, this.ResumeAfterOrdersFormDialog);
}
private IForm<OrderSearchQuery> BuildOrderForm()
{
OnCompletionAsyncDelegate<OrderSearchQuery> processOrderSearch = async (context, state) =>
{
await context.PostAsync($"Ok. Searching for Orders with Number: {state.OrderNumber}...");
};
return new FormBuilder<OrderSearchQuery>()
.Field(nameof(OrderSearchQuery.OrderNumber))
.AddRemainingFields(new string[] { nameof(RequiredDWPSearchQuery.SubOrderNumber), nameof(RequiredDWPSearchQuery.SubOrderVersion) })
.OnCompletion(processOrderSearch)
.Build();
}
private async Task ResumeAfterOrdersFormDialog(IDialogContext context, IAwaitable<OrderSearchQuery> result)
{
try
{
var searchQuery = await result;
await context.PostAsync($"I found total of 100 Ordes");
await context.PostAsync($"To get Order details, you will need to provide more info...");
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the Required DWP Search";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
finally
{
context.Done<object>(null);
}
}
}
}

While there are conditional parameters for fields and the possibility of doing logic in validation, I don't believe that would be the cleanest way to go. I would take a two dialog approach.
Here's the original form, with additional parameters removed:
using Microsoft.Bot.Builder.FormFlow;
using System;
namespace TestChatbot.Dialogs
{
[Serializable]
public class OrderSearchQuery
{
[Prompt("Please enter {&}")]
public string OrderNumber { get; set; }
[Prompt("Please enter {&}?")]
public String Location { get; set; }
[Prompt("Please provide last status {&}?")]
public string Status { get; set; }
}
}
Instead, I moved the additional properties to a new form:
using Microsoft.Bot.Builder.FormFlow;
using System;
namespace TestChatbot.Dialogs
{
[Serializable]
public class OrderFollowUp
{
[Prompt("Please enter {&}?")]
public string SubOrderNumber { get; set; }
[Prompt("Please enter {&}?")]
public string SubOrderVersion { get; set; }
}
}
Then I added logic to handle the second form, depending on the results of the search:
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
namespace TestChatbot.Dialogs
{
[Serializable]
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to the Order helper!");
var orderFormDialog = FormDialog.FromForm(this.BuildOrderForm, FormOptions.PromptInStart);
context.Call(orderFormDialog, this.ResumeAfterOrdersFormDialog);
}
private IForm<OrderSearchQuery> BuildOrderForm()
{
return new FormBuilder<OrderSearchQuery>()
.Build();
}
private IForm<OrderFollowUp> BuildFollowUpForm()
{
return new FormBuilder<OrderFollowUp>()
.Build();
}
private async Task ResumeAfterOrdersFormDialog(IDialogContext context, IAwaitable<OrderSearchQuery> result)
{
try
{
var searchQuery = await result;
await context.PostAsync($"Ok. Searching for Orders with Number: {searchQuery.OrderNumber}...");
await context.PostAsync($"I found total of 100 Ordes");
int searchResultCount = 2;
if (searchResultCount > 1)
{
await context.PostAsync($"To get Order details, you will need to provide more info...");
var followUpDialog = FormDialog.FromForm(this.BuildFollowUpForm, FormOptions.PromptInStart);
context.Call(followUpDialog, this.ResumeAfterFollowUpDialog);
}
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the Required DWP Search";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
}
private async Task ResumeAfterFollowUpDialog(IDialogContext context, IAwaitable<OrderFollowUp> result)
{
await context.PostAsync($"Order complete.");
context.Done<object>(null);
}
}
}
The difference here is that I removed the OnCompletion call and handled that logic on the resumption handler, after the first dialog completes. Based on the search result, you can call the second dialog, much like you did the first to extract additional information.

Related

Execute different pieces of code based on a value in C#

I have a piece of C# code that reacts to an HTTP trigger from Azure and gets executed. The piece of code sends an email containing a warning:
#r "Newtonsoft.Json"
#r "SendGrid"
using System;
using SendGrid.Helpers.Mail;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static SendGridMessage Run(HttpRequest req, ILogger log)
{
string requestBody = new StreamReader(req.Body).ReadToEnd();
log.LogInformation(requestBody);
var notifications = JsonConvert.DeserializeObject<IList<Notification>>(requestBody);
SendGridMessage message = new SendGridMessage();
message.Subject = "Sensor Anomaly Warning";
var content = "The following device has started registering anomalies:<br/><br/><table><tr><th>time</th><th>value</th><th>lower bound</th><th>upper bound</th><th>device</th></tr>";
foreach(var notification in notifications) {
log.LogInformation($" - time: {notification.time}, value: {notification.value}, lowerbound: {notification.lowerbound}, upperbound: {notification.upperbound}, device: {notification.device}");
content += $"<tr><td>{notification.time}</td><td>{notification.value}</td><td>{notification.lowerbound}</td><td>{notification.upperbound}</td><td>{notification.device}</td></tr>";
}
content += "</table>";
message.AddContent("text/html", content);
return message;
}
public class Notification
{
public string time { get; set; }
public string value { get; set; }
public string lowerbound { get; set; }
public string upperbound { get; set; }
public string device { get; set; }
}
Now, I want to execute this same piece of code that sends an email, but based on the value of notification.alert which stores a zero if an anomaly started and a 1 if the alert stopped. Coming from python, this would be as easy as setting an "if else" statement, where the function is called in each case.
However for this C# code, there is no function to call. The piece of code sends an email but it's just creating a class. In any case, I'm wondering if I can use a "if else" statement in C# based on the value of notification.alert that in one case sends an email saying something like "the device has started registering anomalies" and in the other "the device has stopped registering anomalies". I just can't get to do this, as I have to address the notification object, which is already inside the class.
I must say that I am not a C# developer but a Python developer, hence the doubts.
You can use if/else statement or switch to update the email body based on notification.alert. this function is not sending the email it's just setting up a message for the email body text.
using System;
using SendGrid.Helpers.Mail;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static SendGridMessage Run(HttpRequest req, ILogger log)
{
string requestBody = new StreamReader(req.Body).ReadToEnd();
log.LogInformation(requestBody);
var notifications = JsonConvert.DeserializeObject<IList<Notification>>(requestBody);
SendGridMessage message = new SendGridMessage();
message.Subject = "Sensor Anomaly Warning";
var content = "The following device has started registering anomalies: <br/><br/><table><tr><th>time</th><th>value</th><th>lower bound</th><th>upper bound</th><th>device</th></tr>";
foreach(var notification in notifications) {
log.LogInformation($" - time: {notification.time}, value: {notification.value}, lowerbound: {notification.lowerbound}, upperbound: {notification.upperbound}, device: {notification.device}");
content += notification.alert == 0 ? "the device has started registering anomalies" : "the device has stopped registering anomalies";
}
content += "</table>";
message.AddContent("text/html", content);
return message;
}
public class Notification
{
public string time { get; set; }
public string value { get; set; }
public string lowerbound { get; set; }
public string upperbound { get; set; }
public string device { get; set; }
public int alert { get; set;}
}

.net core console method call

I am trying to invoke a method from the Main() method of a .net core console application that should display two properties from an external api but the console is only displaying Hello World and no other result data, I also want the console to stay put and not disappear off the screen. All help pointers are most welcome and thank you in advance!
UserItem.cs -
public class UserItem
{
public UserItem(string name, string url)
{
Name = name;
Url = url;
}
public string Name { get; set; }
public string Url { get; set; }
}
Program.cs -
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
GetUser();
}
// retrieve all.
public static async void GetUser()
{
//baseUrl
string baseUrl = "http://pokeapi.co/api/v2/pokemon/";
try
{
// HttpClient implements a IDisposable interface.
using (HttpClient client = new HttpClient())
{
//initiate Get Request (await will execute the using statement in order).
using (HttpResponseMessage res = await client.GetAsync(baseUrl))
{
//get content from response, then convert it to a c# object.
using (HttpContent content = res.Content)
{
//assign content to data variable by converting into a string using await.
var data = await content.ReadAsStringAsync();
//If the data is not null log convert the data using newtonsoft JObject Parse class method.
if (content != null)
{
//log data object in the console
Console.WriteLine("data------------{0}", JObject.Parse(data)["results"]);
}
else
{
Console.WriteLine("NO Data----------");
}
}
}
}
}
catch (Exception exception)
{
Console.WriteLine("Exception Hit------------");
Console.WriteLine(exception);
}
}
}
using System;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
//Waits the API response
GetUser().Wait();
//Waits until a key is pressed.
Console.ReadKey();
}
// retrieve all.
public static async Task GetUser()
{
//...
}
}
}

MassTransit - Unit Testing a Large Payload Consumer

Using the InMemoryTestHarness, I am working on unit testing a Consumer that handles a command with a MessageData property. Using the InMemoryMessageDataRepository to populate the MessageData property works fine however, when the consumer attempts to load the payload (message.Body.Value) I am receiving the following exception: "The message data was not loaded: urn:msgdata:xxxxxxxxxxxxxxxxxxxxxxxxxx"}. What is the proper way to unit test a consumer that handles commands with a MessageData property?
I was going to use the In-memory transport, but it doesn't seem like the right approach since the unit test(s) will not receive immediate feedback from the consumer. Ultimately the end goal is for my unit tests to be able assert that specific exceptions are thrown or that the consumer completed successfully without errors.
using System;
using System.Linq;
using System.Threading.Tasks;
using MassTransit;
using MassTransit.MessageData;
using MassTransit.Testing;
using Newtonsoft.Json;
using Xunit;
namespace Test
{
public class LargePayloadConsumerTest
{
[Fact]
public async Task Consumer_Should_Deserialize_Message()
{
var harness = new InMemoryTestHarness();
var consumer = harness.Consumer<BigMessageConsumer>();
var command = new BigMessageCommand();
var dataRepo = new InMemoryMessageDataRepository();
var largePayload = new BigMessagePayload()
{
Id = 1,
FirstName = "TestFirstName",
MiddleName = "TestMiddleName",
LastName = "TestLastName"
};
var json = JsonConvert.SerializeObject(largePayload);
command.Body = await dataRepo.PutString(json);
var thisWorks = await command.Body.Value;
await harness.Start();
await harness.InputQueueSendEndpoint.Send(command);
Assert.True(harness.Consumed.Select<BigMessageCommand>().Any());
}
public class BigMessageConsumer : IConsumer<BigMessageCommand>
{
public async Task Consume(ConsumeContext<BigMessageCommand> context)
{
try
{
var json = await context.Message.Body.Value;
var deserialized = JsonConvert.DeserializeObject<BigMessagePayload>(json);
Console.WriteLine($"{deserialized.Id}: {deserialized.FirstName} {deserialized.MiddleName} {deserialized.LastName}");
}
catch (Exception e)
{
//throws {"The message data was not loaded: urn:msgdata:xxxxxxxxxxxxxxxxxxxxxxxxxx"}
Console.WriteLine(e);
throw;
}
}
}
public class BigMessageCommand
{
public MessageData<string> Body { get; set; }
}
public class BigMessagePayload
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
}
}
}
You need to configure the receive endpoint to handle the message data property, similar to this:
e.UseMessageData<BigMessageCommand>(dataRepo);
Since you're using the test harness, you can add it to the receive endpoint before you add the consumer test harness:
void ConfigureReceiveEndpoint(IReceiveEndpointConfigurator configurator)
{
configurator.UseMessageData<BigMessageCommand>(dataRepo);
}
harness.OnConfigureReceiveEndpoint += ConfigureReceiveEndpoint;

How to rebuild chat bot dialog using bot framework

I am working on a multi dialog form flow chat bot using bot framework.
Below is one of my dialog code where I want to get the confirmation from the user and if necessary, needs to alter customer selection/ parameters provided.
below is the code I'm working on
Dialog
namespace FormBot.Dialogs
{
[Serializable]
public class HardwareDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to the Hardware solution helpdesk!");
var HardwareFormDialog = FormDialog.FromForm(this.BuildHardwareForm, FormOptions.PromptInStart);
context.Call(HardwareFormDialog, this.ResumeAfterHardwareFormDialog);
}
private IForm<HardwareQuery> BuildHardwareForm()
{
OnCompletionAsyncDelegate<HardwareQuery> HardwareRequest = async (context, state) =>
{
string message = string.Empty;
await context.PostAsync($"Ok. {message}. Once we resolve it; we will get back to you....");
};
return new FormBuilder<HardwareQuery>()
.Field(nameof(HardwareQuery.Hardware))
.Message($"We are Creating Ticket your request ...")
.AddRemainingFields()
.OnCompletion(HardwareRequest)
.Build();
}
private async Task ResumeAfterHardwareFormDialog(IDialogContext context, IAwaitable<HardwareQuery> result)
{
try
{
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the HardwareDialog";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
finally
{
context.Done<object>(null);
}
}
public static IForm<HardwareDialog> BuildForm()
{
return new FormBuilder<HardwareDialog>()
.Message("Welcome to Service Ticket Bot!")
.Build();
}
}
}
Query builder
public enum HardwareOptions
{
Desktop, KeyBoard, Laptop, Monitor, Mouse, Printer, Scanner, Server, Tablet
};
[Serializable]
public class HardwareQuery
{
[Prompt("Choose your {&} ? {||}")]
public HardwareOptions? Hardware;
[Prompt("Please enter {&}")]
[Pattern(Utility.Phone)]
public string PhoneNumber { get; set; }
[Prompt("Please enter {&} ")]
[Pattern(Utility.Email)]
public string Email { get; set; }
[Prompt("Please provide your business need / {&} below")]
public string Justification { get; set; }
public static IForm<HardwareQuery> BuildForm()
{
OnCompletionAsyncDelegate<ServiceTicket> processOrder = async (context, state) =>
{
await context.PostAsync($"Once we resolve it; we will get back to you....");
};
return new FormBuilder<HardwareQuery>()
.Message("Welcome !")
.Build();
}
}
}
Expected result
Asking for confirmation
Updating the result set
To ask for a confirmation of the selected values in FormFlow, you could use the .Confirm() while building the form. In case you want to prompt for a customized message, you could use .Confirm("Do you confirm the chosen hardware - {Hardware} and your email - {Email} and phone number {PhoneNumber}"). For detailed approaches, have a look at the official documentation. Based on the choice of the confirmation, bot framework's form flow will automatically handle the posting in the questions to change and updating the results.
P.S. On a side note, I am not sure why you are trying to use FormDialog.FromForm and the other unnecessary code, you could simplify the HardwareDialog as follows:
namespace FormBot.Dialogs
{
[Serializable]
public class HardwareDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to the Hardware solution helpdesk!");
var HardwareFormDialog = new FormDialog<HardwareQuery>(new HardwareQuery(), HardwareQuery.BuildForm, FormOptions.PromptInStart);
context.Call(HardwareFormDialog, this.ResumeAfterHardwareFormDialog);
}
private async Task ResumeAfterHardwareFormDialog(IDialogContext context, IAwaitable<HardwareQuery> result)
{
try
{
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the HardwareDialog";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
finally
{
context.Done<object>(null);
}
}

Error using QnAMaker sample with feedback

I have been trying to use Microsoft Cognitive and AI toolkit with QnAMaker API, in order to create a simplistic chat bot.
While my normal qnaMakerAi chat bot works fine, there is an issue while I was trying to enhance it's feature and include the bot feedback within the response.
I have been following the exact code sample as is referred here.
The issue I'm having is:
Exception: Object reference not set to an instance of an object.
[File of type 'text/plain'].
The debugger is giving error in the code section - (in the file WebApiConfig.cs)
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
};
I have also raised a detailed description of the issue in - https://github.com/Microsoft/BotBuilder/issues/4267.
Please check and suggest.
Based on the user comments, here is the code for MessagesController -
using System;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.Dialogs;
using System.Web.Http.Description;
using System.Net.Http;
using QnABot.Dialogs;
namespace Microsoft.Bot.Sample.QnABot
{
[BotAuthentication]
public class MessagesController : ApiController
{
/// <summary>
/// POST: api/Messages
/// receive a message from a user and send replies
/// </summary>
/// <param name="activity"></param>
[ResponseType(typeof(void))]
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
// check if activity is of type message
if (activity.GetActivityType() == ActivityTypes.Message)
{
//await Conversation.SendAsync(activity, () => new RootDialog());
await Conversation.SendAsync(activity, () => new QnaDialog());
}
else
{
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
}
For QnADialog -
using Microsoft.Bot.Builder.Azure;
using Microsoft.Bot.Builder.CognitiveServices.QnAMaker;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace QnABot.Dialogs
{
[Serializable]
public class QnaDialog : QnAMakerDialog
{
public QnaDialog() : base(new QnAMakerService(new QnAMakerAttribute("b372e477-0a2f-4a5a-88d5-3a664d16a4c3", "4ee02ead3xxxxxx", "Sorry, I couldn't find an answer for that", 0.5)))
{
}
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
// answer is a string
var answer = result.Answers.First().Answer;
Activity reply = ((Activity)context.Activity).CreateReply();
string[] qnaAnswerData = answer.Split(';');
int dataSize = qnaAnswerData.Length;
string title = qnaAnswerData[0];
string description = qnaAnswerData[1];
string url = qnaAnswerData[2];
string imageURL = qnaAnswerData[3];
HeroCard card = new HeroCard
{
Title = title,
Subtitle = description,
};
card.Buttons = new List<CardAction>
{
new CardAction(ActionTypes.OpenUrl, "Learn More", value: url)
};
card.Images = new List<CardImage>
{
new CardImage( url = imageURL)
};
reply.Attachments.Add(card.ToAttachment());
await context.PostAsync(reply);
}
protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
// get the URL
var answer = result.Answers.First().Answer;
string[] qnaAnswerData = answer.Split(';');
string qnaURL = qnaAnswerData[2];
// pass user's question
var userQuestion = (context.Activity as Activity).Text;
context.Call(new FeedbackDialog(qnaURL, userQuestion), ResumeAfterFeedback);
}
private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<IMessageActivity> result)
{
if (await result != null)
{
await MessageReceivedAsync(context, result);
}
else
{
context.Done<IMessageActivity>(null);
}
}
}
}
For FeedBackDialog -
using Microsoft.ApplicationInsights;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace QnABot.Dialogs
{
[Serializable]
public class FeedbackDialog : IDialog<IMessageActivity>
{
private string qnaURL;
private string userQuestion;
public FeedbackDialog(string url, string question)
{
// keep track of data associated with feedback
qnaURL = url;
userQuestion = question;
}
public async Task StartAsync(IDialogContext context)
{
var feedback = ((Activity)context.Activity).CreateReply("Did you find what you need?");
feedback.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new CardAction(){ Title = "👍", Type=ActionTypes.PostBack, Value=$"yes-positive-feedback" },
new CardAction(){ Title = "👎", Type=ActionTypes.PostBack, Value=$"no-negative-feedback" }
}
};
await context.PostAsync(feedback);
context.Wait(this.MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var userFeedback = await result;
if (userFeedback.Text.Contains("yes-positive-feedback") || userFeedback.Text.Contains("no-negative-feedback"))
{
// create telemetry client to post to Application Insights
TelemetryClient telemetry = new TelemetryClient();
if (userFeedback.Text.Contains("yes-positive-feedback"))
{
// post feedback to App Insights
var properties = new Dictionary<string, string>
{
{"Question", userQuestion },
{"URL", qnaURL },
{"Vote", "Yes" }
// add properties relevant to your bot
};
telemetry.TrackEvent("Yes-Vote", properties);
}
else if (userFeedback.Text.Contains("no-negative-feedback"))
{
// post feedback to App Insights
}
await context.PostAsync("Thanks for your feedback!");
context.Done<IMessageActivity>(null);
}
else
{
// no feedback, return to QnA dialog
context.Done<IMessageActivity>(userFeedback);
}
}
}
}
1st, bad config
Ok, the 1st problem is the fact that you inverted 2 parameters in your QnaDialog declaration:
public QnaDialog() : base(new QnAMakerService(new QnAMakerAttribute("b372e477-0a2f-4a5a-88d5-3a664d16a4c3", "4ee02ead3xxxxxx", "Sorry, I couldn't find an answer for that", 0.5)))
The syntax is: Qn
public QnAMakerAttribute(string subscriptionKey, string knowledgebaseId, ...
Here you inverted your Key and your knowledgebaseId. The Guid should be in 2nd position, not 1st.
Note that I modified your subscription key in the question and reply, you should note share them like that.
Code improvement
The sample that you used seems to be not valid:
in the case there is no match
when your answer is not made of a string with ; separator (like when you type "hi", the reply is "hello"
I added some security to avoid errors in those cases:
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
// answer is a string
var answer = result.Answers.First().Answer;
Activity reply = ((Activity)context.Activity).CreateReply();
var qnaAnswerData = answer.Split(';');
var dataSize = qnaAnswerData.Length;
if (dataSize == 3)
{
var title = qnaAnswerData[0];
var description = qnaAnswerData[1];
var url = qnaAnswerData[2];
var imageUrl = qnaAnswerData[3];
var card = new HeroCard
{
Title = title,
Subtitle = description,
Buttons = new List<CardAction>
{
new CardAction(ActionTypes.OpenUrl, "Learn More", value: url)
},
Images = new List<CardImage>
{
new CardImage(url = imageUrl)
},
};
reply.Attachments.Add(card.ToAttachment());
}
else
{
reply.Text = answer;
}
await context.PostAsync(reply);
}
protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
if (result.Answers.Count > 0)
{
// get the URL
var answer = result.Answers.First().Answer;
var qnaAnswerData = answer.Split(';');
var dataSize = qnaAnswerData.Length;
if (dataSize == 3)
{
var qnaUrl = qnaAnswerData[2];
// pass user's question
var userQuestion = (context.Activity as Activity).Text;
context.Call(new FeedbackDialog(qnaUrl, userQuestion), ResumeAfterFeedback);
}
else
{
await ResumeAfterFeedback(context, new AwaitableFromItem<IMessageActivity>(null));
}
}
else
{
await ResumeAfterFeedback(context, new AwaitableFromItem<IMessageActivity>(null));
}
}
private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<IMessageActivity> result)
{
if (await result != null)
{
await MessageReceivedAsync(context, result);
}
else
{
context.Done<IMessageActivity>(null);
}
}

Categories