bot frame work chatbot.Bot encounters bug - c#

A bot doesn't go to origin!=null. I have tried taking origin as a separate step, still the same bug occurs.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using Microsoft.Recognizers.Text.DataTypes.TimexExpression;
using Newtonsoft.Json;
namespace Microsoft.BotBuilderSamples.Dialogs
{
public class MainDialog : ComponentDialog
{
private readonly FlightBookingRecognizer _luisRecognizer;
protected readonly ILogger Logger;
// Dependency injection uses this constructor to instantiate MainDialog
public MainDialog(FlightBookingRecognizer luisRecognizer, BookingDialog bookingDialog, ILogger<MainDialog> logger)
: base(nameof(MainDialog))
{
_luisRecognizer = luisRecognizer;
Logger = logger;
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(bookingDialog);
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
IntroStepAsync,
ActStepAsync,
OriginStepAsync,
OriginConfirmAsync,
FinalStepAsync,
}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private const string DestinationStepMsgText = "Where would you like to travel to?";
private const string OriginStepMsgText = "Where are you traveling from?";
private async Task<DialogTurnResult> IntroStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if (!_luisRecognizer.IsConfigured)
{
await stepContext.Context.SendActivityAsync(
MessageFactory.Text("NOTE: LUIS is not configured. To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' to the appsettings.json file.", inputHint: InputHints.IgnoringInput), cancellationToken);
return await stepContext.NextAsync(null, cancellationToken);
}
// Use the text provided in FinalStepAsync or the default if it is the first time.
var messageText = stepContext.Options?.ToString() ?? "What can I help you with today?\nSay something like \"Book a flight from Paris to Berlin on March 22, 2020\"";
var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput);
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
}
private async Task<DialogTurnResult> ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if (!_luisRecognizer.IsConfigured)
{
// LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance.
return await stepContext.BeginDialogAsync(nameof(BookingDialog), new BookingDetails(), cancellationToken);
}
// Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
var luisResult = await _luisRecognizer.RecognizeAsync<FlightBooking>(stepContext.Context, cancellationToken);
switch (luisResult.TopIntent().intent)
{
//case FlightBooking.Intent.BookFlight:
// await ShowWarningForUnsupportedCities(stepContext.Context, luisResult, cancellationToken);
// // Initialize BookingDetails with any entities we may have found in the response.
// //var bookingDetails = new BookingDetails()
// //{
// // // Get destination and origin from the composite entities arrays.
// // Destination = luisResult.ToEntities.Airport,
// // Origin = luisResult.FromEntities.Airport,
// // TravelDate = luisResult.TravelDate,
// //};
// return await stepContext.BeginDialogAsync(nameof(BookingDialog), bookingDetails, cancellationToken);
case FlightBooking.Intent.TravellerDeatail:
//await ShowWarningForUnsupportedCities(stepContext.Context, luisResult, cancellationToken);
// Initialize BookingDetails with any entities we may have found in the response.
var bookingDetails = new BookingDetails()
{
// Get destination and origin from the composite entities arrays.
Destination = luisResult.DestinationEntities.Destination,
Origin = luisResult.OriginEntities.Origin,
TravelDate = luisResult.TravelDate,
};
if (bookingDetails.Destination == null)
{
var messageText1 = stepContext.Options?.ToString() ?? DestinationStepMsgText;
var promptMessage1 = MessageFactory.Text(messageText1, messageText1, InputHints.ExpectingInput);
await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage1 }, cancellationToken);
var promptMessage3 = "";
return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage3, cancellationToken);
}
if (bookingDetails.Destination != null)
{
string[] SplitDest;
string combined = "";
string collection = "";
string PreDest = bookingDetails.Destination;
string ConDest = bookingDetails.ConDestination;
try
{
if (PreDest.Length != 3)
{
var keyword = PreDest;
string[] strCity = File.ReadAllText(#"D:\Ravvise project\Attarbot2-src\AdditionalIN\Airports.txt").Split('$');
foreach (string str in strCity)
{
if (str.IndexOf(keyword, StringComparison.CurrentCultureIgnoreCase) >= 0)
{
combined += str + '$';
}
}
}
SplitDest = combined.Split("$");
foreach (var prepoll in SplitDest)
{
if (SplitDest.Length == 1)
{
ConDest = SplitDest[0];
ConDest = SplitDest[0].Split("[")[1].Replace("]", "");
}
else if (SplitDest.Length > 1)
{
string DestBody = File.ReadAllText(#"D:\Ravvise project\Attarbot2-src\AdditionalIN\DestBody.txt");
string strTemp = "";
collection = "";
foreach (string city in SplitDest)
{
if (city != "")
{
ConDest = city.Split("[")[1].Replace("]", "");
strTemp = DestBody.Replace("{ZZZ}", city);
strTemp = strTemp.Replace("{YYY}", ConDest);
collection += strTemp;
}
}
collection = collection.TrimEnd(',');
string DestTemplate = File.ReadAllText(#"D:\Ravvise project\Attarbot2-src\AdditionalIN\DestTemplate.txt");
DestTemplate = DestTemplate.Replace("{XXX}", collection);
var cardAttachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(DestTemplate.ToString()),
};
var opts = new PromptOptions
{
Prompt = new Activity
{
Attachments = new List<Attachment>() { cardAttachment },
Type = ActivityTypes.Message,
Text = "Kindly select any Combinations from below.", // You can comment this out if you don't want to display any text. Still works.
}
};
// Display a Text Prompt and wait for input
return await stepContext.PromptAsync(nameof(TextPrompt), opts);
}
}
}
catch (Exception ex) { }
return await stepContext.ContinueDialogAsync();
}
if (bookingDetails.Origin == null)
{
var messageText1 = stepContext.Options?.ToString() ?? "Wow 😍😍😍😍😍😍😍😍" + bookingDetails.Destination + " is a great place to explore.Kindly tell your Origin.";
var promptMessage1 = MessageFactory.Text(messageText1, messageText1, InputHints.ExpectingInput);
await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage1 }, cancellationToken);
var promptMessage3 = "";
return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage3, cancellationToken);
}
var promptMessage5 = "";
return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage5, cancellationToken);
case FlightBooking.Intent.Greeting:
var messageText = stepContext.Options?.ToString() ?? "Gretings Mate!!!!!!πŸ˜‰πŸ˜‰πŸ˜‰I wink cus I wanna help you with yourπŸ˜ƒπŸ˜ƒπŸ˜ƒπŸ˜ƒπŸ˜ƒSo.....Kindly tell me your travel deatails.";
var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput);
await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
var promptMessage2 = "";
return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage2, cancellationToken);
// Run the BookingDialog giving it whatever details we have from the LUIS call, it will fill out the remainder.
case FlightBooking.Intent.GetWeather:
// We haven't implemented the GetWeatherDialog so we just display a TODO message.
var getWeatherMessageText = "TODO: get weather flow here";
var getWeatherMessage = MessageFactory.Text(getWeatherMessageText, getWeatherMessageText, InputHints.IgnoringInput);
await stepContext.Context.SendActivityAsync(getWeatherMessage, cancellationToken);
break;
default:
// Catch all for unhandled intents
var didntUnderstandMessageText = $"Sorry, I didn't get that. Please try asking in a different way (intent was {luisResult.TopIntent().intent})";
var didntUnderstandMessage = MessageFactory.Text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
await stepContext.Context.SendActivityAsync(didntUnderstandMessage, cancellationToken);
break;
}
return await stepContext.NextAsync(null, cancellationToken);
}
// Shows a warning if the requested From or To cities are recognized as entities but they are not in the Airport entity list.
// In some cases LUIS will recognize the From and To composite entities as a valid cities but the From and To Airport values
// will be empty if those entity values can't be mapped to a canonical item in the Airport.
private async Task<DialogTurnResult> OriginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var bookingDetails = (BookingDetails)stepContext.Options;
bookingDetails.ConDestination = (string)stepContext.Result;
if (bookingDetails.Origin == null)
{
var messageText1 = stepContext.Options?.ToString() ?? "Wow 😍😍😍😍😍😍😍😍" + bookingDetails.Destination + " is a great place to explore.Kindly tell your Origin.";
var promptMessage1 = MessageFactory.Text(messageText1, messageText1, InputHints.ExpectingInput);
await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage1 }, cancellationToken);
}
var promptMessage3 = "";
return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage3, cancellationToken);
}
private async Task<DialogTurnResult> OriginConfirmAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
string[] SplitOrigin;
string combined = "";
string collection = "";
string PConOrigin = "";
var bookingDetails = (BookingDetails)stepContext.Options;
bookingDetails.Origin = (string)stepContext.Result;
string PreOrigin = bookingDetails.Origin;
string ConOrigin = bookingDetails.ConOrigin;
try
{
if (PreOrigin.Length != 3)
{
var keyword = PreOrigin;
string[] strCity = File.ReadAllText("E:/Airports.txt").Split('$');
foreach (string str in strCity)
{
if (str.IndexOf(keyword, StringComparison.CurrentCultureIgnoreCase) >= 0)
{
combined += str + '$';
}
}
}
SplitOrigin = combined.Split("$");
foreach (var prepoll in SplitOrigin)
{
if (SplitOrigin.Length == 1)
{
ConOrigin = SplitOrigin[0];
ConOrigin = SplitOrigin[0].Split("[")[1].Replace("]", "");
}
else if (SplitOrigin.Length > 1)
{
string DestBody = File.ReadAllText("E:/DestBody.txt");
string strTemp = "";
collection = "";
foreach (string city in SplitOrigin)
{
if (city != "")
{
ConOrigin = city.Split("[")[1].Replace("]", "");
strTemp = DestBody.Replace("{ZZZ}", city);
strTemp = strTemp.Replace("{YYY}", ConOrigin);
collection += strTemp;
}
}
collection = collection.TrimEnd(',');
string DestTemplate = File.ReadAllText("E:/DestTemplate.txt");
DestTemplate = DestTemplate.Replace("{XXX}", collection);
var cardAttachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(DestTemplate.ToString()),
};
var opts = new PromptOptions
{
Prompt = new Activity
{
Attachments = new List<Attachment>() { cardAttachment },
Type = ActivityTypes.Message,
Text = "Kindly select any Combinations from below.", // You can comment this out if you don't want to display any text. Still works.
}
};
// Display a Text Prompt and wait for input
return await stepContext.PromptAsync(nameof(TextPrompt), opts);
}
}
}
catch (Exception ex) { }
return await stepContext.NextAsync(bookingDetails.ConOrigin, cancellationToken);
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// If the child dialog ("BookingDialog") was cancelled, the user failed to confirm or if the intent wasn't BookFlight
// the Result here will be null.
if (stepContext.Result is BookingDetails result)
{
// Now we have all the booking details call the booking service.
// If the call to the booking service was successful tell the user.
var timeProperty = new TimexProperty(result.TravelDate);
var travelDateMsg = timeProperty.ToNaturalLanguage(DateTime.Now);
var messageText = $"I have you booked to {result.Destination} from {result.Origin} on {travelDateMsg}";
var message = MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput);
await stepContext.Context.SendActivityAsync(message, cancellationToken);
}
// Restart the main dialog with a different message the second time around
var promptMessage = "What else can I do for you?";
return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage, cancellationToken);
}
}
}
After destination != null, bot doesn't go to bookingdetails.origin= null. If it is not possible please guide any other way to do it like different steps to get different values. I tried to do it but still, the same problem occurs.

Related

How call a step of waterfall on a particular condition?

I have a bot made using c# and v4 framework. It always asks user do they want to ask more questions from bot with a yes/no prompt:
i want that on "clicking on a particular condition of waterfall should be called" otherwise on all other conditions that step of waterfall should skipped and bot should function normally.I am also adding code just for reference.
The code of main dialog
public MainDialog(ILogger<MainDialog> logger, IBotServices botServices, UserState userState) :
base(nameof(MainDialog))
{ _botServices = botServices;// ?? throw new System.ArgumentNullException(nameof(botServices));
_logger = logger;
_userState = userState;
AddDialog(new ProductIssue($"{nameof(MainDialog)}.fromMain", _botServices, _userState));
AddDialog(new ProductIssue($"{nameof(Confirm)}.fromConfirm", _botServices, _userState));
AddDialog(new ProductIssue($"{ nameof(Resolution)}.resolution", _botServices, _userState));
AddDialog(new PurchaseFlow(_userState));
AddDialog(new Resolution( _userState));
AddDialog(new Confirm(_botServices,_userState));
AddDialog(new ChoicePrompt($"{nameof(MainDialog)}.issue"));
AddDialog(new TextPrompt($"{nameof(MainDialog)}.callDialog"));
AddDialog(new TextPrompt($"{nameof(MainDialog)}.number", ValidationAsync));
AddDialog(new WaterfallDialog($"{nameof(MainDialog)}.mainFlow", new WaterfallStep[]
{
MoblieNumberAsync,
ChoiceCardStepAsync,
ShowCardStepAsync,
CallingDialogsAsync
}));
AddDialog(new TextPrompt(nameof(TextPrompt)));
InitialDialogId = $"{nameof(MainDialog)}.mainFlow";
}
private async Task<DialogTurnResult> MoblieNumberAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values[UserInfo] = new UserInput();
var options = new PromptOptions()
{
Prompt = MessageFactory.Text("Kindly enter your 10 digit mobile number without any spaces, dashes and country code. We will be sending an OTP later to this number "),
RetryPrompt = MessageFactory.Text("Incorrect mobile number entered. Please only enter the 10 digits of your mobile without any spaces, dashes and country code.")
};
return await stepContext.PromptAsync($"{nameof(MainDialog)}.number", options, cancellationToken);
}
private async Task<DialogTurnResult> ChoiceCardStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var userStateAccessors = _userState.CreateProperty<UserInput>(nameof(UserInput));
var userinfo = await userStateAccessors.GetAsync(stepContext.Context, () => new UserInput());
userinfo.phone_no = (string)stepContext.Result;
CustomerDetails customerDetails = new CustomerDetails();
//API-Get Customer Details from CRM
try
{
BotAPIBLL botApiBLL = new BotAPIBLL();
var response = botApiBLL.GetCustomerDetail(stepContext.Context.Activity.Text);
customerDetails = JsonConvert.DeserializeObject<CustomerDetails>(response);
}
catch(Exception ex)
{
}
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Fetching your details from our systems. This may take a moment"), cancellationToken);
if (customerDetails.D != null && !string.IsNullOrEmpty(customerDetails.D.TelNumber) && !string.IsNullOrEmpty(customerDetails.D.NameFirst))
{
DbConnection dbConnection = new DbConnection();
dbConnection.SaveCutomerInfo(customerDetails);
userinfo.Name = customerDetails.D.NameFirst;
var options = new PromptOptions()
{
Prompt = MessageFactory.Text("Welcome " + customerDetails.D.NameFirst + ", How can we serve you ? "),
RetryPrompt = MessageFactory.Text("That was not a valid choice, please select a option between 1 to 5."),
Choices = GetChoices(),
Style = ListStyle.HeroCard
};
return await stepContext.PromptAsync($"{nameof(MainDialog)}.issue", options, cancellationToken);
}
else
{
var options = new PromptOptions()
{
Prompt = MessageFactory.Text("Welcome Guest_" + userinfo.phone_no + ", How can we serve you ? "),
RetryPrompt = MessageFactory.Text("That was not a valid choice, please select a option between 1 to 5."),
Choices = GetChoices(),
Style = ListStyle.HeroCard
};
return await stepContext.PromptAsync($"{nameof(MainDialog)}.issue", options, cancellationToken);
}
}
private async Task<DialogTurnResult> ShowCardStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var userinfo = (UserInput)stepContext.Values[UserInfo];
userinfo.choiceselected = ((FoundChoice)stepContext.Result).Value;
var attachments = new List<Attachment>();
var reply = MessageFactory.Text("");
var user_choice = ((FoundChoice)stepContext.Result).Value;
switch (user_choice)
{
case "Product issue":
reply.Attachments.Add(Cards.CreateAdaptiveCardAttachment3());
break;
case "Register Product":
reply.Attachments.Add(Cards.GetHeroCard1().ToAttachment());
break;
case "Online Purchase":
return await stepContext.BeginDialogAsync(nameof(PurchaseFlow), null, cancellationToken);
case "Customer Grivance":
reply.Attachments.Add(Cards.GetHeroCard3().ToAttachment());
break;
case "Order Status":
reply.Attachments.Add(Cards.GetHeroCard6().ToAttachment());
break;
default:
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments.Add(Cards.CreateAdaptiveCardAttachment3());
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments.Add(Cards.GetHeroCard1().ToAttachment());
break;
}
if (user_choice == "Register Product" || user_choice == "Online Purchase" || user_choice == "Customer Grivance")
{
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
Random r = new Random();
var validationcard = Cards.CreateAdaptiveCardAttachment2(_cards[r.Next(_cards.Length)]);
//var feedbackcard = Cards.CustomerFeedback();
await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(validationcard), cancellationToken);
//await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(feedbackcard), cancellationToken);
var accessor = _userState.CreateProperty<UserInput>(nameof(UserInput));
await accessor.SetAsync(stepContext.Context, userinfo, cancellationToken);
return await stepContext.EndDialogAsync(null, cancellationToken);
}
else
{
var options2 = new PromptOptions() { Prompt = reply, RetryPrompt = MessageFactory.Text("Retry") };
return await stepContext.PromptAsync($"{nameof(MainDialog)}.callDialog", options2, cancellationToken);
}
}
private async Task<DialogTurnResult> CallingDialogsAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
var recognizerResult = await _botServices.Dispatch.RecognizeAsync(stepContext.Context, cancellationToken);
// Top intent tell us which cognitive service to use.
var topIntent = recognizerResult.GetTopScoringIntent();
switch (topIntent.intent)
{
case "Mainissue":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.fromMain", stepContext.Values[UserInfo], cancellationToken);
case "ConfirmIntent":
return await stepContext.BeginDialogAsync(nameof(Confirm), null, cancellationToken);
case "EndBotIntent":
return await stepContext.CancelAllDialogsAsync(true, null, null, cancellationToken);
case "InverterData":
await ProcessSampleQnAAsync(stepContext, cancellationToken);
break;
default:
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"I'm sorry I don't know what you mean."), cancellationToken);
break;
}
return await stepContext.EndDialogAsync(null, cancellationToken);
}
private IList<Choice> GetChoices()
{
var cardOptions = new List<Choice>()
{
new Choice() { Value = "Product issue", Synonyms = new List<string>() { "adaptive" } },
new Choice() { Value = "Register Product", Synonyms = new List<string>() { "hero" } },
new Choice() { Value = "Online Purchase", Synonyms = new List<string>() { "hero" } },
new Choice() { Value = "Customer Grivance", Synonyms = new List<string>() { "hero" } },
new Choice() { Value = "Order Status", Synonyms = new List<string>() { "hero" } }
};
return cardOptions;
}
private Task<bool> ValidationAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
{
string value = (string)promptContext.Context.Activity.Text;
if (Regex.IsMatch(value, "^[0-9]{10}$"))
{
return Task.FromResult(true);
}
else
{
return Task.FromResult(false);
}
}
private async Task ProcessSampleQnAAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessSampleQnAAsync");
var results = await _botServices.SampleQnA.GetAnswersAsync(stepContext.Context);
if (results.Any())
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
}
else
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
}
}
}
You can skip the step by evaluating the condition and calling NextAsync, like this:
if (condition)
{
await stepContext.NextAsync(stepContext.Result, cancellationToken);
}
// otherwise process this step
The NextAsync immediately passes control to the next step with whatever you used as the 1st parameter. In this case, I used stepContext.Result, which is the result of the previous step that will be passed to the next step.

WaterfallDialog choiceprompt working in emulator but not working in directline webchannel in Bot framework v4

I have recently migrated my project from bot framework v3 to bot V4.
I have FeedbackDialog file in this i have implemented waterfallsteps, choiceprompts.
Everything and the flow is working correctly in Emulator.
But it is not working properly means the flow is not working fine in Webchannel
Please provide the solution to this problem.
Here is my code.
public FeedbackDialog() : base(nameof(FeedbackDialog))
{
AddDialog(new ChoicePrompt("ShowChoicePrompt") { Style = ListStyle.HeroCard });
AddDialog(new ChoicePrompt(nameof(ConfirmPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
StartAsync,
MessageReceivedAsync,
ResumeAfterPositiveFeedbackSelectionClarification
}));
InitialDialogId = nameof(WaterfallDialog);
}
public async Task<DialogTurnResult> StartAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var reply = ((Activity)stepContext.Context.Activity).CreateReply(BotConstants.feedbackRequestText);
reply.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new CardAction(){ Title = "πŸ‘", Type=ActionTypes.PostBack, Value=BotConstants.positiveFeedbackValue },
new CardAction(){ Title = "πŸ‘Ž", Type=ActionTypes.PostBack, Value=BotConstants.negativeFeedbackValue }
}
};
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
// return await stepContext.PromptAsync(nameof(ConfirmPrompt), promptOptions, cancellationToken);
}
public async Task<DialogTurnResult> MessageReceivedAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var data=stepContext.Result;
var res = stepContext.Result;
// string res= Convert.ToString(data.GetType().GetProperty("Synonym").GetValue(data, null));
var feedbackDetails = (FeedbackData)stepContext.Options;
var userFeedback =Convert.ToString(res);
// var userFeedback = feedbackDetails.userFeedback;
string userQuestion = feedbackDetails.userQuestion;
string intent = feedbackDetails.intent;
int Id = feedbackDetails.Id;
if (userFeedback.Contains(BotConstants.positiveFeedbackValue) || userFeedback.Contains(BotConstants.negativeFeedbackValue))
{
// create telemetry client to post to Application Insights
TelemetryClient telemetry = new TelemetryClient();
telemetry.InstrumentationKey = Configuration.CDPOSConfigurationManager.GetAppSetting("InstrumentationKey");
RequestRepository requestRepository = new RequestRepository();
if (userFeedback.Contains(BotConstants.positiveFeedbackValue))
{
// post feedback to App Insights
var properties = new Dictionary<string, string>
{
{"Question", userQuestion },
{"LuisIntent", intent },
{"Vote", "Yes" }
// add properties relevant to your bot
};
telemetry.TrackEvent("Yes-Vote", properties);
var chatFeedback = true;
//requestRepository.AddChatbotEntryToDB(userQuestion, intent, chatFeedback);
requestRepository.updateChatFeedbackStatus(Id, chatFeedback);
var options = BotConstants.positiveFeedbackOption;
var descriptions = BotConstants.positiveFeedbackOptionDesc;
Activity textPrompt = stepContext.Context.Activity.CreateReply(BotConstants.positiveFeedbackResponse);
List<Microsoft.Bot.Builder.Dialogs.Choices.Choice> choices = new List<Microsoft.Bot.Builder.Dialogs.Choices.Choice>()
{
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.YeshaveAnotherQuestionOption, Synonyms = new List<string> { "Yes" } },
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.NothankyouOption, Synonyms = new List<string> { "No" }}
};
try
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions {Prompt= MessageFactory.Text(BotConstants.positiveFeedbackResponse), Choices= choices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
catch (Exception ex)
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions { Prompt = MessageFactory.Text(BotConstants.positiveFeedbackResponse), Choices = choices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
}
else if (userFeedback.Contains(BotConstants.negativeFeedbackValue))
{
var properties = new Dictionary<string, string>
{
{"Question", userQuestion },
{"LuisIntent", intent },
{"Vote", "Yes" }
// add properties relevant to your bot
};
telemetry.TrackEvent("No-Vote", properties);
var chatFeedback = false;
//requestRepository.AddChatbotEntryToDB(userQuestion, intent, chatFeedback);
requestRepository.updateChatFeedbackStatus(Id, chatFeedback);
await stepContext.Context.SendActivityAsync(BotConstants.negativeFeedbackResponse);
await stepContext.Context.SendActivityAsync(BotConstants.logTicketResponse);
var options = BotConstants.negativeFeedbackOption;
var descriptions = BotConstants.negativeFeedbackDesc;
List<Microsoft.Bot.Builder.Dialogs.Choices.Choice> NegativeChoices = new List<Microsoft.Bot.Builder.Dialogs.Choices.Choice>()
{
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.logTicket, Synonyms = new List<string> { BotConstants.logTicketOption } },
new Microsoft.Bot.Builder.Dialogs.Choices.Choice { Value = BotConstants.rephraseQuestionOption, Synonyms = new List<string> { BotConstants.rephraseQuestionOption }}
};
try
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions { Prompt = MessageFactory.Text(BotConstants.positiveFeedbackResponse), Choices = NegativeChoices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
catch (Exception ex)
{
await stepContext.PromptAsync("ShowChoicePrompt", new PromptOptions { Prompt = MessageFactory.Text(BotConstants.question), Choices = NegativeChoices }, cancellationToken);
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
}
}
else
{
return await stepContext.BeginDialogAsync(nameof(RootDialog), null, cancellationToken);
// no feedback, return to main dialog
// return await stepContext.EndDialogAsync(userFeedback, cancellationToken);
//return stepContext.BeginDialogAsync(userFeedback, cancellationToken);
}
return await stepContext.EndDialogAsync(userFeedback, cancellationToken);
}
private async Task<DialogTurnResult> ResumeAfterPositiveFeedbackSelectionClarification(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var data = stepContext.Result;
if (stepContext.Result != null)
{
string selection = Convert.ToString(data.GetType().GetProperty("Synonym").GetValue(data, null));
// var selection = stepContext.Result;
if (selection == BotConstants.YeshaveAnotherQuestionOption || selection == BotConstants.haveAnotherQuestionOption || selection == BotConstants.rephraseQuestionOption)
{
await stepContext.Context.SendActivityAsync(BotConstants.askAnotherQuestion);
}
else
{
await stepContext.Context.SendActivityAsync(BotConstants.logaTicketResponse);
}
}
return await stepContext.EndDialogAsync(null, cancellationToken);
}
public static void Register(HttpConfiguration config)
{
var builder = new ContainerBuilder();
// builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterType<MessagesController>().InstancePerRequest();
// The ConfigurationCredentialProvider will retrieve the MicrosoftAppId and
// MicrosoftAppPassword from Web.config
builder.RegisterType<ConfigurationCredentialProvider>().As<ICredentialProvider>().SingleInstance();
// Create the Bot Framework Adapter with error handling enabled.
builder.RegisterType<AdapterWithErrorHandler>().As<IBotFrameworkHttpAdapter>().SingleInstance();
// The Memory Storage used here is for local bot debugging only. When the bot
// is restarted, everything stored in memory will be gone.
IStorage dataStore = new MemoryStorage();
// Create Conversation State object.
// The Conversation State object is where we persist anything at the conversation-scope.
var conversationState = new ConversationState(dataStore);
builder.RegisterInstance(conversationState).As<ConversationState>().SingleInstance();
// Register the main dialog, which is injected into the DialogBot class
builder.RegisterType<RootDialog>().SingleInstance();
// Register the DialogBot with RootDialog as the IBot interface
builder.RegisterType<DialogBot<RootDialog>>().As<IBot>();
var container = builder.Build();
var resolver = new AutofacWebApiDependencyResolver(container);
config.DependencyResolver = resolver;
}
When a bot only works when it's running locally, memory storage is very often the issue. When a bot is deployed to an app service, there may be multiple instances of the service running simultaneously to provide high availability, and each instance will have its own memory. Like all REST API's, your bot needs to not rely on memory for the state it accesses across multiple turns. You need external storage so that all instances of the service have access to the same data, and so the data isn't lost when the service restarts. Please refer to the documentation for more information.

C# bot V4 Text Prompt inside a waterfall dialog

I need to add a question 'Did this help?' after getting the response from QnA and take the feedback from user. If there is no response for this and if the next input is a completely new query, the flow should restart from bot.cs
I tried using a textprompt, but when tested in emulator, bot doesn't wait for user input after the prompt.
Bot.cs
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
var activity = turnContext.Activity;
var dc = await _dialogs.CreateContextAsync(turnContext);
if (turnContext == null)
{
throw new ArgumentNullException(nameof(turnContext));
}
if (turnContext.Activity.Type == ActivityTypes.Message)
{
if (turnContext.Activity.Text != null)
{
var luisResults = await _services.LuisServices[LuisConfiguration].RecognizeAsync(dc.Context, cancellationToken);
var luisProperties = LuisEntities.FromLuisResults(luisResults);
await _luisEntitiesAccessor.SetAsync(turnContext, luisProperties);
var topScoringIntent = luisResults?.GetTopScoringIntent();
var topIntent = topScoringIntent.Value.intent;
switch (topIntent)
{
case NoneIntent:
await dc.BeginDialogAsync(QnADialog.Name);
break;
case GreetingsIntent:
await dc.BeginDialogAsync(QnAGreetingsDialog.Name);
break;
case CredentialsIntent:
await dc.BeginDialogAsync(CredentialsDialog.Name);
break;
case ContactusIntent:
await dc.BeginDialogAsync(FeedbackDialog.Name);
break;
case FeedbackIntent:
await dc.BeginDialogAsync(FeedbackDialog.Name);
break;
default:
await dc.Context.SendActivityAsync("I didn't understand what you just said to me.");
break;
}
}
else if (string.IsNullOrEmpty(turnContext.Activity.Text))
{
await HandleSubmitActionAsync(turnContext, userProfile);
}
}
else if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate)
{
if (turnContext.Activity.MembersAdded != null)
{
await SendWelcomeMessageAsync(turnContext);
}
}
else if (turnContext.Activity.Type == ActivityTypes.Event)
{
await SendWelcomeMessageAsync(turnContext);
}
else
{
await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected");
}
// Save the dialog state into the conversation state.
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
QnADialog.cs - dialog in which I want the prompt to work
public class QnADialog : ComponentDialog
{
public const int QnaNumResults = 1;
public const double QnaConfidenceThreshold = 0.5;
public const string QnaConfiguration = "QnAFaqSubscriptionKey";
private const string QnAFeedbackDialog = "qnaDialog";
public const string Name = "QnA";
public const string TextPrompt = "textPrompt";
private readonly BotServices _services;
private readonly IStatePropertyAccessor<UserProfile> _userProfileAccessor;
Action<string, string, bool, int, int> updateQna;
private int InvalidMessageCount = 0;
string Query = string.Empty;
List<int> qnaIdStorage;
UserProfile userProfile = new UserProfile();
public QnADialog(Action<string, string, bool, int, int> updateQna, bool isCollection, List<int> rotationTemStorage, BotServices services, UserProfile _userProfile, IStatePropertyAccessor<UserProfile> userProfileAccessor, int invalidMessageCount = 0, string dialogId = null)
: base(Name)
{
_services = services ?? throw new ArgumentNullException(nameof(services));
_userProfileAccessor = userProfileAccessor ?? throw new ArgumentNullException(nameof(userProfileAccessor));
userProfile = _userProfile;
this.updateQna = updateQna;
this.InvalidMessageCount = invalidMessageCount;
qnaIdStorage = rotationTemStorage;
var waterfallSteps = new WaterfallStep[]
{
BeginStepAsync,
FetchFAQResultStepAsync,
FeedbackStepAsync,
FeedbackResponseStepAsync,
};
AddDialog(new WaterfallDialog(QnAFeedbackDialog, waterfallSteps));
AddDialog(new TextPrompt("userFeed"));
}
public async Task<DialogTurnResult> BeginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
{
var messageToForward = stepContext.Context.Activity;
UserProfile.previousQuestion = messageToForward.Text;
string[] supportList = { "HELP", "FEEDBACK", "SUPPORT", "ESCALATE", "AGENT" };
if (messageToForward.Text == null || messageToForward.Text.ToLower() == "no")
{
await stepContext.Context.SendActivityAsync("Sorry, I was not able to help you.");
return await stepContext.EndDialogAsync();
}
else if (messageToForward.Text == null || supportList.Any(x => x == messageToForward.Text.ToUpper()))
{
await stepContext.Context.SendActivityAsync("Please reach out to... ");
return await stepContext.EndDialogAsync();
}
else
{
return await stepContext.NextAsync();
}
}
private async Task<DialogTurnResult> FetchFAQResultStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var message = stepContext.Context.Activity;
var qnaResult = await FaqQnaMakerService.GetQnaResult(_services, stepContext, this.Query);
var qnaIdColl = GetQnaIdColl(this.Query, qnaResult);
int qnaPreviousId = 0;
int qnaNewId = 0;
if (qnaIdColl != null && qnaIdColl.Count > 1)
{
qnaIdColl = qnaIdColl.Distinct().OrderBy(x => x).ToList();
//Compare the previous Qnaid collection and existing collection , if it is matching produce the result.
var matchItem = qnaIdColl.Intersect(qnaIdStorage);
if (matchItem.Count() == 0)
{
//If there is no previous collection Qna id then take the first item from the existing Qna collection
qnaNewId = qnaIdColl.FirstOrDefault();
}
else
{
//If there any previous Qnaid that contain in the existing collection then pick the next value and generate a new qna result.
qnaPreviousId = matchItem.FirstOrDefault();
qnaNewId = GetNextRotationKey(qnaIdColl, qnaPreviousId);
}
//Create a new response based on selected new qna id.
qnaResult = new[] { qnaResult.Where(x => x.Id == qnaNewId).Single() };
}
if (qnaResult.First().Answer.Length > 0)
{
if (qnaResult.First().Score > 0)
{
updateQna(this.Query, qnaResult.First().Answer, false, qnaPreviousId, qnaNewId);
InvalidMessageCount = 0;
var QuestionCollection = TextFormatter.FormattedQuestionColl(qnaResult.First().Answer);
if (QuestionCollection != null)
{
userProfile.IsAswerCollection = true;
updateQna(this.Query, qnaResult.First().Answer, true, qnaPreviousId, qnaNewId);
var replyMessage = stepContext.Context.Activity.CreateReply();
replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.QnaAttachment(new Tuple<string, string[]>(QuestionCollection.Item2, QuestionCollection.Item3)) };
if (!string.IsNullOrEmpty(QuestionCollection.Item1))
{
await stepContext.Context.SendActivityAsync(QuestionCollection.Item1);
}
await stepContext.Context.SendActivityAsync(replyMessage);
return await stepContext.EndDialogAsync();
}
else
{
await stepContext.Context.SendActivityAsync(qnaResult.First().Answer);
}
}
else
{
InvalidMessageCount++;
return await stepContext.ContinueDialogAsync();
}
}
return await stepContext.NextAsync();
}
private async Task<DialogTurnResult> FeedbackStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync("userFeed", new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply("Did this help?")
});
}
private async Task<DialogTurnResult> FeedbackResponseStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var message = stepContext.Context.Activity;
var mesgActivity = message as Activity;
string var = userProfile.qnaData;
var qnaResultModel = new { InvalidMessageCount = 0, originalQueryText = string.Empty };
NeedMoreInformation needmoreInfo = NeedMoreInformation.NotSelected;
if (message != null && message.Text == null && message.Value != null)
{
dynamic value = mesgActivity.Value.ToString();
UserReply response = JsonConvert.DeserializeObject<UserReply>(value);
if (!string.IsNullOrEmpty(response.Reply))
{
mesgActivity.Text = response.Reply;
}
}
//This if condition work only the user reply back to the question "Did this help?"
if (userProfile.needMoreInformation == true && message?.Text?.ToLower() != "yes" && message?.Text?.ToLower() != "no")
{
//The response message pass to LUIS service to understand the intention of the conversation is β€œyes” or β€œno”
bool? moreInformationYes = await LUISService.GetResultAESChatBotYesNo(message?.Text);
if (moreInformationYes != null && moreInformationYes == true)
{
//Once the LUIS understand the conversation change the original message to yes.
message.Text = "yes";
//needmoreInfo = NeedMoreInformation.Yes;
}
else if (moreInformationYes != null && moreInformationYes == false)
{
////Once the LUIS understand the conversation change the original message to no.
message.Text = "no";
needmoreInfo = NeedMoreInformation.No;
}
else
{
needmoreInfo = NeedMoreInformation.None;
}
}
if (userProfile.needMoreInformation == true && message?.Text?.ToLower() == "yes")
{
userProfile.qnaInvalidMessageCount = 0;
userProfile.needMoreInformation = false;
dynamic value = stepContext.Context.Activity.Value;
var output = JsonConvert.DeserializeObject<UserReply>(stepContext.Context.Activity.Value.ToString());
if (userProfile.feedbackCard == false)
{
var replyMessage = stepContext.Context.Activity.CreateReply();
replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.FeedbackAdapativecard() };
await stepContext.Context.SendActivityAsync(replyMessage);
}
if (output.Reply != "yes")
{
await AdaptiveCardReplyAsync(_services, stepContext, userProfile);
}
}
else if (userProfile.needMoreInformation == true && message?.Text?.ToLower() == "no")
{
userProfile.qnaInvalidMessageCount = 0;
userProfile.needMoreInformation = false;
dynamic value = stepContext.Context.Activity.Value;
if (value.Type == "GetMoreContent")
{
await AdaptiveCardGetMoreContent(_services, stepContext, userProfile);
}
else if (value.Type == "GetHelpSubmit")
{
await AdaptiveCardReplyAsync(_services, stepContext, userProfile);
}
else if (userProfile.getMoreContentCard == false)
{
var replyMessage = stepContext.Context.Activity.CreateReply();
replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.GetMoreContent() };
await stepContext.Context.SendActivityAsync(replyMessage);
}
// context.Wait(AdaptiveCardGetMoreContent);
}
else
{
await stepContext.BeginDialogAsync(nameof(Bot.cs));
}
return await stepContext.EndDialogAsync();
}
}
After this prompt it should go to the next step as added in the waterfall steps but it does not. Any possible suggestions/help would be greatly appreciated. Thanks in advance!
Without seeing the code for your other steps inside the Waterfall such as BeginStepAsync and FetchFAQResultStepAsync it is difficult to give you an exact answer for your scenario.
How I would suggest you accomplish this is through the use of a message with suggested actions underneath this message, once either of this actions is clicked both options will disappear, thus removing the potential for multiple submissions by the same user for the same answer reply.
You have a couple of options here:
1) Use this dated sample that uses v3.9.0 of the Microsoft.Bot.Builder NuGet package, the meat of which is in the QnADialog and FeedbackDialog classes.
The important part is that the QnADialog implements QnAMakerDialog.
2) Right after where you send the reply to the user with the answer (inside FetchFAQResultsStepAsync I assume) you could add the following code:
var feedback = ((Activity)context.Activity).CreateReply("Did you find what you need?");
feedback.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new CardAction(){ Title = "Yes", Type=ActionTypes.PostBack, Value=$"yes-positive-feedback" },
new CardAction(){ Title = "No", Type=ActionTypes.PostBack, Value=$"no-negative-feedback" }
}
};
await context.PostAsync(feedback);
EDIT
Thank you for providing the full code for your QnADialog class, unfortunately I cannot run it locally because the implementations for methods such as GetQnaIdColl, GetNextRotationKey, TextFormatter.FormattedQuestionColl among other methods and classes that you call but haven't provided. Your code for prompting a user for a response looks right but it sounds like you're not even getting the feedback prompt to show, or you're getting the feedback prompt to show but you get stuck on there - can you confirm which it is? Have you tried stepping through the code to see which path it takes?
Another suggestion would be to separate out your QnA step and Feedback steps into separate Dialogs, I have proved and example feedback dialog below.
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using System.Threading;
using System.Threading.Tasks;
namespace ChatBot.VirtualAssistant.Dialogs
{
public class RateAnswerDialog : ComponentDialog
{
public RateAnswerDialog()
: base(nameof(RateAnswerDialog))
{
InitialDialogId = nameof(RateAnswerDialog);
var askToRate = new WaterfallStep[]
{
AskRating,
FinishDialog
};
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(InitialDialogId, askToRate));
}
private async Task<DialogTurnResult> AskRating(WaterfallStepContext sc, CancellationToken cancellationToken)
{
PromptOptions promptOptions = new PromptOptions
{
Prompt = MessageFactory.Text("Was this helpful?")
};
return await sc.PromptAsync(nameof(TextPrompt), promptOptions);
}
private async Task<DialogTurnResult> FinishDialog(WaterfallStepContext sc, CancellationToken cancellationToken)
{
return await sc.EndDialogAsync(sc);
}
protected override async Task<DialogTurnResult> EndComponentAsync(DialogContext outerDc, object context, CancellationToken cancellationToken)
{
var waterfallContext = (WaterfallStepContext)context;
var userResponse = ((string)waterfallContext.Result).ToLowerInvariant();
if (userResponse == "yes")
{
await waterfallContext.Context.SendActivityAsync("Thank you for your feedback");
}
else if (userResponse == "no")
{
await waterfallContext.Context.SendActivityAsync("Sorry I couldn't help you");
}
else
{
await waterfallContext.Context.SendActivityAsync("The valid answers are 'yes' or 'no'");
// TODO reprompt if required
}
return await outerDc.EndDialogAsync();
}
}
}

How to unit test dialogs that contains attachments or more objects in Bot Framework C#

Bot Info
SDK Platform: .NET
SDK Version: 3.14.0.7
Active Channels: Web
Deployment Environment: Local development with Emulator
Issue Description
We've trying to unit test every case that we have stored in a certain Dictionary, it seems to be working fine when the user sends and string and the test has to answer with a string. But we can't find any documentation on how to test the other kind of dialogs, like with attachments, buttons, etc.
We wish to make a dictionary of string,objects where the string is what we ask the bot and de object is either a string, Attachment, Dialog.
Code Example
This is how we store the answers:
public static Dictionary<string, object> data = new Dictionary<string, object>{
{"Nuevo", "Que quieres crear?"},
{"Ayuda", "Ya te ayudas!"},
{"Adios", "Nos vemos!"},
{
"Coche",
new Attachment() {
ContentUrl = "https://media.ed.edmunds-media.com/subaru/impreza/2006/oem/2006_subaru_impreza_sedan_sti_fq_oem_1_500.jpg",
ContentType = "image/png",
Name = "Subaru_Impreza.png"
}
},
{
"Moto",
new Attachment() {
ContentUrl = "http://motos.honda.com.co/sites/default/files/motos/cb-1000-r-cc-menu-honda.png",
ContentType = "image/png",
Name = "moto.png"
}
},
{
"Perro",
new Attachment() {
ContentUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/Taka_Shiba.jpg/1200px-Taka_Shiba.jpg",
ContentType = "image/png",
Name = "ShibaInu.png"
}
}
};
This is how the bot works and returns everything, this is working as intended for at least text and attachments but we haven't done it for more type of messages.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
var r = context.MakeMessage();
foreach (var item in data)
{
if (item.Key == activity.Text)
{
if (item.Value is Attachment)
{
r.Attachments = new List<Attachment>() { item.Value as Attachment };
}
if (item.Value is string)
{
r.Text = item.Value.ToString();
}
break;
}
}
// return our reply to the user
await context.PostAsync(r);
context.Wait(MessageReceivedAsync);
}
But when we want to make the test for it, it only works when what we send is a string not a IMessageActivity, which works in the emulator.
The code for the test:
[TestMethod]
public async Task Pregunta_respuesta_prueba()
{
foreach (var item in RootDialog.data)
{
var preg = item.Key;
var resp = item.Value;
if (item.Value is Attachment)
{
Attachment auxText = resp as Attachment;
resp = auxText.ContentUrl;
}
using (ShimsContext.Create())
{
// Arrange
var waitCalled = false;
object message = null;
var target = new RootDialog();
var activity = new Activity(ActivityTypes.Message)
{
Text = preg
};
var awaiter = new Microsoft.Bot.Builder.Internals.Fibers.Fakes.StubIAwaiter<IMessageActivity>()
{
IsCompletedGet = () => true,
GetResult = () => activity
};
var awaitable = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIAwaitable<IMessageActivity>()
{
GetAwaiter = () => awaiter
};
var context = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIDialogContext();
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.PostAsyncIBotToUserStringStringCancellationToken = (user, s1, s2, token) =>
{
message = s1;
Console.WriteLine(message);
return Task.CompletedTask;
};
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.WaitIDialogStackResumeAfterOfIMessageActivity = (stack, callback) =>
{
if (waitCalled) return;
waitCalled = true;
// The callback is what is being tested.
callback(context, awaitable);
};
// Act
await target.StartAsync(context);
// Assert
Assert.AreEqual(resp, message);
}
}
}
If you check this part of the code
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.PostAsyncIBotToUserStringStringCancellationToken = (user, s1, s2, token) =>
{
message = s1;
Console.WriteLine(message);
return Task.CompletedTask;
};
```
It does only works when the bot is returning an string, we can't even check if it is an activiy, this happens because the Fake Context that we create for the test is not working as expected.
That IDialogContext that we are faking doesnt seem to work at all when it is an object, but it does work when it is a string.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
/// Here when the test is running, this context.MakeMessage is null, but when the bot
/// is working, it wors perfectly.
var r = context.MakeMessage();
foreach (var item in data)
{
if (item.Key == activity.Text)
{
if (item.Value is Attachment)
{
r.Attachments = new List<Attachment>() { item.Value as Attachment };
}
if (item.Value is string)
{
r.Text = item.Value.ToString();
}
break;
}
}
// return our reply to the user
await context.PostAsync(r);
context.Wait(MessageReceivedAsync);
}
Reproduction Steps
To try this out you can try to test with an attachment, code is in this repository.
In stead of using PostAsyncIBotToUserStringStringCancellationToken, you can use context.PostAsyncIMessageActivityCancellationToken And, in the RootDialog's MessageReceivedWithTextAsync respond with an activity reply instead of just a string.
public async Task MessageReceivedWithTextAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
string r = "";
foreach (var item in dataText)
{
if (item.Key == activity.Text)
{
r = item.Value;
break;
}
}
var reply = activity.CreateReply(r);
foreach (var item in dataAtt)
{
if (item.Key == activity.Text)
{
reply.Attachments.Add(item.Value);
reply.Text = "attachment";
break;
}
}
if ((string.IsNullOrWhiteSpace(r) || r == null) && reply.Attachments.Count == 0)
{
reply.Text = "No tengo respuesta para eso.";
}
// return our reply to the user
await context.PostAsync(reply);
}
Here are the changes to the test method:
[TestMethod]
public async Task Bot_Test_Attachments()
{
foreach (var item in RootDialog.dataAtt)
{
var preg = item.Key;
var att = item.Value;
using (ShimsContext.Create())
{
var waitCalled = false;
IMessageActivity message = null;
var target = new RootDialog();
var activity = new Activity(ActivityTypes.Message)
{
Text = preg,
From = new ChannelAccount("id","name"),
Recipient = new ChannelAccount("recipid","recipname"),
Conversation = new ConversationAccount(false,"id","name")
};
var awaiter = new Microsoft.Bot.Builder.Internals.Fibers.Fakes.StubIAwaiter<IMessageActivity>()
{
IsCompletedGet = () => true,
GetResult = () => activity
};
var awaitable = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIAwaitable<IMessageActivity>()
{
GetAwaiter = () => awaiter
};
var context = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIDialogContext();
context.PostAsyncIMessageActivityCancellationToken = (messageActivity, token) => {
message = messageActivity;
return Task.CompletedTask;
};
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.WaitIDialogStackResumeAfterOfIMessageActivity = (stack, callback) =>
{
if (waitCalled) return;
waitCalled = true;
callback(context, awaitable);
};
await target.MessageReceivedWithTextAsync(context, awaitable);
Assert.AreEqual(att, message.Attachments[0]);
}
}
}

asynchronous module or handler completed while an asynchronous operation was still pending

I am working on bot technology, in one of my projet i wrote below lines of code
private async void DeliveryProgressReport(IDialogContext context, Activity message)
{
MessagesController.progressdetails = SQLDatabaseService.getinprogressdetails();
var progress = MessagesController.progressdetails;
if (progress.Count > 0)
{
try
{
Activity replyToConversation = message.CreateReply("**In Progress Report Details**");
replyToConversation.Recipient = message.From;
replyToConversation.Type = "message";
replyToConversation.Attachments = new List<Attachment>();
Dictionary<string, string> progresslist = new Dictionary<string, string>();
foreach (var progressreport in progress)
{
//Invoke the machine learning model for predicting the delivery status of delivery person
//var deliveryStatus= await InvokeRequestResponseServiceOfDeliveryPersonPredictionExp1();
//await Task.Delay(TimeSpan.FromSeconds(5));
var deliveryStatus = await InvokeRequestResponseServiceOfDeliveryPersonPredictionExp(progress[0].Name, progress[0].Mobile_Number);
progresslist.Add(progressreport.Name, progressreport.Mobile_Number);
List<CardImage> cardImages = new List<CardImage>();
cardImages.Add(new CardImage(url: progressreport.Photo_Url));
ThumbnailCard tlcard = new ThumbnailCard()
{
Title = "Name:" + progressreport.Name,
Subtitle = "Call:" + progressreport.Mobile_Number,
Images = cardImages,
Text = "Staus by Using Machine Learning Prediction:" + deliveryStatus
};
Attachment plAttachment = tlcard.ToAttachment();
replyToConversation.Attachments.Add(plAttachment);
}
replyToConversation.AttachmentLayout = AttachmentLayoutTypes.List;
await context.PostAsync(replyToConversation);
} catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
else
{
Activity replyToConversation = message.CreateReply("**There are no in progress deliveries are found**");
await context.PostAsync(replyToConversation);
}
}
private async Task<string> InvokeRequestResponseServiceOfDeliveryPersonPredictionExp(string name, string mobile_Number)
{
string status = "";
//Func<Stream, Task> copyStreamAsync = async stream =>
//{
//await Task.Factory.StartNew(async () =>
//{
//using (stream)
//using (var sourceStream = await sourceContent.Content.ReadAsStreamAsync())
//{
// await sourceStream.CopyToAsync(stream);
//}
//var client = new HttpClient();
using (var client = new HttpClient())
{
var scoreRequest = new
{
Inputs = new Dictionary<string, StringTable>() {
{
"input1",
new StringTable()
{
ColumnNames = new string[] {"Id", "Name", "Mobile_Number", "CourierCompany_Name", "Status", "EmailId"},
Values = new string[,] { { "", name, mobile_Number, "", "","" }, { "", name, mobile_Number, "", "", "" }, }
}
},
},
GlobalParameters = new Dictionary<string, string>()
{
}
};
const string apiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx=="; // Replace this with the API key for the web service
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.BaseAddress = new Uri("My Request URL");
// WARNING: The 'await' statement below can result in a deadlock if you are calling this code from the UI thread of an ASP.Net application.
// One way to address this would be to call ConfigureAwait(false) so that the execution does not attempt to resume on the original context.
// For instance, replace code such as:
// result = await DoSomeTask()
// with the following:
// result = await DoSomeTask().ConfigureAwait(false)
//var status = await PostRequest(scoreRequest,client).ConfigureAwait(false);
HttpResponseMessage response = await client.PostAsJsonAsync("", scoreRequest);//.ConfigureAwait(false);
string correctLocation = "";
string wrongLocation = "";
string notReached = "";
string personMismatch = "";
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
var results = JsonConvert.DeserializeObject<RootObject>(result);
foreach (var value in results.Results.output1.value.Values)
{
status = value[8].ToString();
correctLocation = value[4].ToString();
notReached = value[5].ToString();
personMismatch = value[6].ToString();
wrongLocation = value[7].ToString();
}
Debug.WriteLine("Result: {0}", result);
return status;
}
else
{
Debug.WriteLine(string.Format("The request failed with status code: {0}", response.StatusCode));
// Print the headers - they include the requert ID and the timestamp, which are useful for debugging the failure
Debug.WriteLine(response.Headers.ToString());
string responseContent = await response.Content.ReadAsStringAsync();
Debug.WriteLine(responseContent);
return status;
}
};
// return status;
}
After executing this below line I got the exception like asynchronous module or handler completed while an asynchronous operation was still pending
await context.PostAsync(replyToConversation);
Before posting this question I had followed through this below links, but I didn't resolve it.
Async Void, ASP.Net, and Count of Outstanding Operations
Web Api + HttpClient: An asynchronous module or handler completed while an asynchronous operation was still pending
Please tell how to resolve this exception.
-Pradeep
Finally, I resolved the above exception when I am return Task instead of void in DeliveryProgressReport method. and also where ever I was called the await DeliveryProgressReport() method there also I return Task instead of void.
-Pradeep

Categories