I've been following Bot Framework documentation to store Bot-Data but even after using the code snippet provided here for handling concurrency I still get error 412 in the emulator. Can you please tell me if I'm saving & retrieving the Bot state correctly.
I've tried setting breakpoints when I'm saving the bot state but it doesn't hit either of the catch blocks.
Emulator's screenshot
namespace HealthBot
{
[Serializable]
[LuisModel("id", "password")]
public class RootDialog : LuisDialog<object>
{
private const string EntityDateTime = "builtin.datetimeV2.date";
protected override async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> item)
{
var message = await item;
if (message.Text == null)
{
await Help(context, item, null);
}
else
{
await base.MessageReceived(context, item);
}
}
[LuisIntent("Claims")]
private async Task Claims(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
StateClient stateClient = context.Activity.GetStateClient();
BotData userData = stateClient.BotState.GetPrivateConversationData(context.Activity.ChannelId, context.Activity.Conversation.Id ,context.Activity.From.Id);
bool isVerified = userData.GetProperty<bool>("isVerified");
if (isVerified)
{
string message = "Yes, but please tell me your claim ID";
var response = context.MakeMessage();
response.Text = message;
response.Speak = message;
response.InputHint = InputHints.ExpectingInput;
await context.PostAsync(response);
context.Wait(MessageReceived);
}
else
{
await StartVerification(context, activity);
}
}
private async Task StartVerification(IDialogContext context, IAwaitable<IMessageActivity> activity)
{
string message = "What is your customer ID";
var response = context.MakeMessage();
response.Text = message;
response.Speak = message;
response.InputHint = InputHints.ExpectingInput;
await context.PostAsync(response);
context.Wait(VerifyCustomerId);
}
private async Task VerifyCustomerId(IDialogContext context, IAwaitable<IMessageActivity> activity)
{
var message = await activity;
string customerID = message.Text.Replace(" ", "");
string responseMessage = $"You provided, {customerID}";
try
{
StateClient stateClient = context.Activity.GetStateClient();
BotData userData = await stateClient.BotState.GetPrivateConversationDataAsync(context.Activity.ChannelId, context.Activity.Conversation.Id, context.Activity.From.Id, CancellationToken.None);
userData.SetProperty<bool>("isVerified", true);
await stateClient.BotState.SetPrivateConversationDataAsync(context.Activity.ChannelId, context.Activity.Conversation.Id, context.Activity.From.Id, userData);
}
catch (HttpOperationException err)
{
responseMessage = "Oops! something went wrong";
}
catch (Exception ex)
{
responseMessage = ex.Message;
}
finally
{
await context.PostAsync(CreateMessageWith(context, responseMessage));
context.Wait(MessageReceived);
}
}
private IMessageActivity CreateMessageWith(IDialogContext context, string response)
{
var reply = context.MakeMessage();
reply.Text = response;
reply.Speak = response;
reply.InputHint = InputHints.ExpectingInput;
return reply;
}
}
}
In the context of a dialog, I would try using context.PrivateConversationData instead of the StateClient.
You can check the State C# sample to fully understand how this works.
Related
I am trying to call a dialog from my luis intent.
But I am getting error. please check my code.It reaches the UserInteraction dialog but the control does not go to Getuserpurpose.It shows an error "my bot code is having an issue"
I tried debugging and I found out that the control transfers to Webapi config class after StartAsync Task.
[LuisIntent("Getfile")]
public async Task GetfileIntent(IDialogContext context, LuisResult result)
{
await context.PostAsync("Sure we can help you on that... ");
//PromptDialog.Text(context, UserEnteredInfo, "can you please provide us with below info. " +
// "Purpose" + "Client name" + "Team Name");
await context.PostAsync("But first i need to know the purpose ");
// Conversation.SendAsync(Activity, () => new UserInteraction());
context.Call(new UserInteraction(),ResumeAfterFeedback);
}
private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<object> result)
{
await context.PostAsync("Prepared an email for you");
context.Wait(MessageReceived);
}
[Serializable]
public class UserInteraction : IDialog<object>
{
protected string Purpose { get; set; }
protected string ClientName { get; set; }
protected string Teamname { get; set; }
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Please enter the purpose");
// context.Wait(this.MessageReceivedAsync);
context.Wait(Getuserpurpose);
}
public async Task Getuserpurpose(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
this.Purpose = message.Text;
await context.PostAsync("Now please tell me the client name");
context.Wait(Getuserclient);
}
I just tested this out, and it does work as expected.
[LuisIntent("Getfile")]
public async Task GetfileIntent(IDialogContext context, LuisResult result)
{
await context.PostAsync("Sure we can help you on that... ");
await context.PostAsync("But first i need to know the purpose ");
context.Call(new UserInteraction(),ResumeAfterFeedback);
}
private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<object> result)
{
var interactionResult = await result as UserInteraction;
await context.PostAsync("Prepared an email for you");
context.Wait(MessageReceived);
}
[Serializable]
public class UserInteraction : IDialog<object>
{
protected string Purpose { get; set; }
protected string ClientName { get; set; }
protected string Teamname { get; set; }
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Please enter the purpose");
context.Wait(Getuserpurpose);
}
public async Task Getuserpurpose(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
this.Purpose = message.Text;
await context.PostAsync("Now please tell me the client name");
context.Wait(Getuserclient);
}
private async Task Getuserclient(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
ClientName = message.Text;
await context.PostAsync("Now please tell me Team Name");
context.Wait(GetUserTeamName);
}
private async Task GetUserTeamName(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
Teamname = message.Text;
await context.PostAsync("Got it");
context.Done(this);
}
}
I'm currently making a chatbot with Microsoft's Bot Framework. In my flow I have a final dialog that lets the user know, that they are participating in the competition. There is also an error-handling method for unknown input. The two methods are seen here:
[Serializable]
public class ConcertCityDialog : AbstractBasicDialog<DialogResult>
{
private static FacebookService FacebookService => new FacebookService(new FacebookClient());
[LuisIntent("ConcertCity")]
public async Task ConcertCityIntent(IDialogContext context, LuisResult result)
{
var fbAccount = await FacebookService.GetAccountAsync(context.Activity.From.Id);
var selectedCityName = result.Entities.FirstOrDefault()?.Entity;
concert_city selectedCity;
using (var concertCityService = new ConcertCityService())
{
selectedCity = concertCityService.FindConcertCity(selectedCityName);
}
if (selectedCity == null)
{
await NoneIntent(context, result);
return;
}
user_interaction latestInteraction;
using (var userService = new MessengerUserService())
{
var user = userService.FindByFacebookIdIncludeInteractions(context.Activity.From.Id);
latestInteraction = user.user_interaction.MaxBy(e => e.created_at);
}
latestInteraction.preferred_city_id = selectedCity.id;
latestInteraction.gif_created = true;
using (var userInteractionService = new UserInteractionService())
{
userInteractionService.UpdateUserInteraction(latestInteraction);
}
var shareIntroReply = context.MakeMessage();
shareIntroReply.Text = "Great choice! You are now participating in the competition. If you dare then pass your message \uD83D\uDE0E";
await context.PostAsync(shareIntroReply);
var reply = await MessageUtility.MakeShareMessageCard(context, fbAccount, latestInteraction, false);
await context.PostAsync(reply);
context.Done(DialogResult.Done);
}
[LuisIntent("")]
[LuisIntent("None")]
public async Task NoneIntent(IDialogContext context, LuisResult result)
{
messenger_user user;
using (var userService = new MessengerUserService())
{
user = userService.FindByFacebookId(context.Activity.From.Id);
}
var phrase = CreateMisunderstoodPhrase(user, result.Query);
using (var misunderstoodPhraseService = new MisunderstoodPhraseService())
{
misunderstoodPhraseService.CreatePhrase(phrase);
}
List<concert_city> concertCities;
using (var concertCityService = new ConcertCityService())
{
concertCities = concertCityService.GetUpcomingConcertCities().ToList();
}
// Prompt city
var reply = context.MakeMessage();
reply.Text = "I'm not sure what you mean \uD83E\uDD14<br/>Which Grøn Koncert would you like to attend?";
reply.SuggestedActions = new SuggestedActions
{
Actions = concertCities.Select(e => MessageUtility.MakeQuickAnswer(e.name)).ToList()
};
await context.PostAsync(reply);
context.Wait(MessageReceived);
}
protected override void OnDeserializedCustom(StreamingContext context)
{
}
}
And here is the AbstractBasicDialog implementation:
[Serializable]
public abstract class AbstractBasicDialog<T> : LuisDialog<T>
{
protected AbstractBasicDialog() : base(new LuisService(new LuisModelAttribute(
ConfigurationManager.AppSettings["LuisAppId"],
ConfigurationManager.AppSettings["LuisAPIKey"],
domain: ConfigurationManager.AppSettings["LuisAPIHostName"])))
{
}
[LuisIntent("Cancel")]
public virtual async Task CancelIntent(IDialogContext context, LuisResult result)
{
var randomQuotes = new List<string>
{
"If you say so, I'll leave you alone for now",
"alright then, I'll leave you alone",
"Okay then, I won't bother you anymore"
};
await context.PostAsync(MessageUtility.RandAnswer(randomQuotes));
context.Done(DialogResult.Cancel);
}
[LuisIntent("Start")]
public virtual async Task StartIntent(IDialogContext context, LuisResult result)
{
context.Done(DialogResult.Restart);
}
[LuisIntent("CustomerSupport")]
public async Task CustomerSupportIntent(IDialogContext context, LuisResult result)
{
using (var userService = new MessengerUserService())
{
var user = userService.FindByFacebookId(context.Activity.From.Id);
if (user != null)
{
user.receiving_support = true;
userService.UpdateUser(user);
}
}
await context.PostAsync("I'll let customer service know, that you want to talk to them. They will get back to you within 24 hours.<br/>If at any time you want to return to me, and start passing a message, just type \"Stop customer support\".");
context.Call(new CustomerSupportDialog(), ResumeAfterCustomerSupport);
}
private async Task ResumeAfterCustomerSupport(IDialogContext context, IAwaitable<DialogResult> result)
{
context.Done(await result);
}
protected misunderstood_phrase CreateMisunderstoodPhrase(messenger_user user, string phrase)
{
return new misunderstood_phrase
{
phrase = phrase,
dialog = GetType().Name,
messenger_user_id = user.id
};
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
OnDeserializedCustom(context);
}
protected abstract void OnDeserializedCustom(StreamingContext context);
}
The call chain starts at this dialog:
[Serializable]
public class BasicLuisDialog : LuisDialog<DialogResult>
{
private static FacebookService FacebookService => new FacebookService(new FacebookClient());
public BasicLuisDialog() : base(new LuisService(new LuisModelAttribute(
ConfigurationManager.AppSettings["LuisAppId"],
ConfigurationManager.AppSettings["LuisAPIKey"],
domain: ConfigurationManager.AppSettings["LuisAPIHostName"])))
{
}
[LuisIntent("")]
[LuisIntent("None")]
public async Task NoneIntent(IDialogContext context, LuisResult result)
{
var facebookAccount = await FacebookService.GetAccountAsync(context.Activity.From.Id);
RegisterUser(facebookAccount, null, out var user);
var phrase = CreateMisunderstoodPhrase(user, result.Query);
using (var misunderstoodPhraseService = new MisunderstoodPhraseService())
{
misunderstoodPhraseService.CreatePhrase(phrase);
}
var reply = context.MakeMessage();
reply.SuggestedActions = new SuggestedActions
{
Actions = new List<CardAction>
{
new CardAction { Title = "Get started", Type = ActionTypes.ImBack, Value = "Get started" },
new CardAction { Title = "Customer support", Type = ActionTypes.ImBack, Value = "Customer support" }
}
};
var name = string.IsNullOrEmpty(facebookAccount.FirstName) ? "" : $"{facebookAccount.FirstName} ";
reply.Text = $"Hm, I'm not sure what you mean {name} \uD83E\uDD14 Here are some ways you can interact with me:";
await context.PostAsync(reply);
context.Wait(MessageReceived);
}
[LuisIntent("Greeting")]
[LuisIntent("Positive")]
[LuisIntent("Start")]
public async Task GreetingIntent(IDialogContext context, LuisResult result)
{
var rnd = new Random();
var facebookAccount = await FacebookService.GetAccountAsync(context.Activity.From.Id);
// Initial Greeting
var greetings = new List<string>
{
"Well hello there",
"Hi there"
};
if (!string.IsNullOrEmpty(facebookAccount.FirstName))
{
greetings.Add("Hi {0}");
greetings.Add("Hello {0}");
greetings.Add("Welcome {0}");
}
if (facebookAccount.Gender == "male")
greetings.Add("Hey handsome");
else if (facebookAccount.Gender == "female")
greetings.Add("Hi gorgeous");
var randIndex = rnd.Next(greetings.Count);
var greeting = string.Format(greetings[randIndex], facebookAccount.FirstName);
await context.PostAsync(greeting);
await MessageUtility.StartTyping(context, 300);
country country;
using (var countryService = new CountryService())
{
country = countryService.FindCountry(facebookAccount.Locale);
}
var userHasCountry = RegisterUser(facebookAccount, country, out var user);
// If user contry not found prompt for answer
if (!userHasCountry)
{
var countryReply = context.MakeMessage();
countryReply.Text = "You are hard to keep track of - where are you from?";
countryReply.SuggestedActions = new SuggestedActions
{
Actions = new List<CardAction>
{
MessageUtility.MakeQuickAnswer("Denmark"),
MessageUtility.MakeQuickAnswer("Norway"),
MessageUtility.MakeQuickAnswer("Sweden"),
MessageUtility.MakeQuickAnswer("Other")
}
};
await context.PostAsync(countryReply);
context.Call(new CountryDialog(), AfterCountryDialog);
}
else
{
await FunPrompt(context, country);
}
}
private async Task AfterCountryDialog(IDialogContext countryContext, IAwaitable<country> countryAwaitable)
{
var country = await countryAwaitable;
var facebookAccount = await FacebookService.GetAccountAsync(countryContext.Activity.From.Id);
using (var userService = new MessengerUserService())
{
var user = userService.FindByFacebookId(facebookAccount.Id);
user.country = country;
userService.UpdateUser(user);
}
var reply = countryContext.MakeMessage();
reply.Text = "That's cool \uD83D\uDE0E";
await countryContext.PostAsync(reply);
await MessageUtility.StartTyping(countryContext, 350);
await FunPrompt(countryContext, country);
}
private async Task FunPrompt(IDialogContext context, country country)
{
if (country?.name == "norway" && DateTime.Now < new DateTime(2018, 8, 13))
{
var reply = context.MakeMessage();
reply.Text = "Unfortunately the competition isn't open in Norway yet. You can still talk to customer support if you want to";
reply.SuggestedActions = new SuggestedActions
{
Actions = new List<CardAction>
{
MessageUtility.MakeQuickAnswer("Customer support")
}
};
await context.PostAsync(reply);
context.Wait(MessageReceived);
}
else if ((country?.name == "denmark" && DateTime.Now >= new DateTime(2018, 7, 29)) ||
(country?.name == "norway" && DateTime.Now >= new DateTime(2018, 10, 21)))
{
var reply = context.MakeMessage();
reply.Text = "The competition has ended. You can still talk to customer support if you want to";
reply.SuggestedActions = new SuggestedActions
{
Actions = new List<CardAction>
{
MessageUtility.MakeQuickAnswer("Customer support")
}
};
await context.PostAsync(reply);
context.Wait(MessageReceived);
}
else
{
await context.PostAsync("Are you up for some fun?");
context.Call(new IntroductionDialog(), ResumeAfterDialog);
}
}
[LuisIntent("CustomerSupport")]
public async Task CustomerSupportIntent(IDialogContext context, LuisResult result)
{
using (var userService = new MessengerUserService())
{
var user = userService.FindByFacebookId(context.Activity.From.Id);
if (user != null)
{
user.receiving_support = true;
userService.UpdateUser(user);
}
}
await context.PostAsync("I'll let customer support know, that you want to talk to them. They should be messaging you shortly.<br/>You can end your conversation with customer support at any time by typing \"Stop customer support\".");
context.Call(new CustomerSupportDialog(), ResumeAfterDialog);
}
private async Task ResumeAfterDialog(IDialogContext context, IAwaitable<DialogResult> result)
{
var resultState = await result;
if (resultState == DialogResult.Restart)
await GreetingIntent(context, null);
else if (resultState == DialogResult.CustomerSupport)
await ResumeAfterCustomerSupport(context);
else if (resultState == DialogResult.Done || resultState == DialogResult.Cancel)
context.Done(resultState);
else
context.Wait(MessageReceived);
}
private async Task ResumeAfterCustomerSupport(IDialogContext context)
{
using (var userService = new MessengerUserService())
{
var user = userService.FindByFacebookId(context.Activity.From.Id);
if (user != null)
{
user.receiving_support = false;
userService.UpdateUser(user);
}
}
await context.PostAsync("I hope you got the help you needed. Would you like to pass a message to a friend?");
context.Call(new IntroductionDialog(), ResumeAfterDialog);
}
private bool RegisterUser(FacebookAccount fbAccount, country country, out messenger_user user)
{
if (string.IsNullOrEmpty(fbAccount?.Id))
{
user = null;
return false;
}
using (var userService = new MessengerUserService())
{
user = userService.FindByFacebookId(fbAccount.Id);
if (user != null)
return user.country != null;
user = new messenger_user
{
id = fbAccount.Id,
country = country
};
userService.CreateUser(user);
return user.country != null;
}
}
protected misunderstood_phrase CreateMisunderstoodPhrase(messenger_user user, string phrase)
{
return new misunderstood_phrase
{
phrase = phrase,
dialog = GetType().Name,
messenger_user_id = user.id
};
}
}
This works most of the time. The user is told that their registration was a success and the flow exits with the context.Done() call. Sometimes however the chatbot doesn't register the dialog as being exited, as seen here:
As you can see the chatbot is still in the same Dialog even though I have called the Done() method. This is a general problem in my chatbot, as it happens sometimes in all my dialogs.
Do you have any input as to what could be wrong?
EDIT:
When debugging this I've added breakpoints every time it calls context.Call. When my issue arises it stops hitting these breakpoints afterwards. Could this be a side-effect of some DI or something? This is my DI code:
Conversation.UpdateContainer(builder =>
{
builder.RegisterModule(new DialogModule());
builder.RegisterModule(new ReflectionSurrogateModule());
builder.RegisterModule(new DialogModule_MakeRoot());
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
var store = new TableBotDataStore(ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
builder.Register(c => new CachingBotDataStore(store,
CachingBotDataStoreConsistencyPolicy
.ETagBasedConsistency))
.As<IBotDataStore<BotData>>()
.AsSelf()
.InstancePerLifetimeScope();
builder.RegisterType<BasicLuisDialog>().As<LuisDialog<DialogResult>>().InstancePerDependency();
});
I think I finally found the problem. In my code I had implemented a helper method in a static class that would send a typing response and wait a certain amount of time. Seeing as the context was passed into this static method it seems that this was causing some issues.
After changing the method to an extension method of the LuisDialog I no longer have this issue.
I would appreciate if anyone can expand on why this might have been a problem.
EDIT: The method in question:
public static async Task StartTyping(IDialogContext context, int sleep)
{
var typingMsg = context.MakeMessage();
typingMsg.Type = ActivityTypes.Typing;
await context.PostAsync(typingMsg);
await Task.Delay(sleep);
}
I faced a very similar issue and while moving the typing sending into a base class from a static helper class as Frederik did help to highly reduce the number of times the problem occured, the final solution was this: https://github.com/Microsoft/BotBuilder/issues/4477
In short, I had to downgrade the bot-related NuGet packages (Microsoft.Bot.Builder, Microsoft.Bot.Builder.History, Microsoft.Bot.Connector) to 3.13.1 and the issue disappeared.
since in [LuisIntent("ConcertCity")] you are using context.Done() so the current dialog gets exit from the stack. This is why the next message is being handled by the previous dialog or the message controller where the 'None' intent is being called and you are getting this response
reply.Text = "I'm not sure what you mean \uD83E\uDD14<br/>Which Grøn Koncert would you like to attend?";
You should not do context.Done() every places, this should only be called when you have to go to the previous dialog on the stack.
I am creating a simple movie ticket booking application where I use a hero card to select a movie and upon selecting a movie, a confirmation message with YES/NO option will be displayed.
Here is the high level code flow
1. MovieDialog which has the HeroCard to select a movie
2. ConfirmationDialog to show the confirmation option with YES/NO
Issue:
I have a resume method (OnOptionConfirmationSelected) in ConfirmationDialog which should execute after the prompt option is selected but this method gets called immediately after clicking the movie button. Can you please check the code and tell me where the issue is?
Code:
MovieDialog
[Serializable]
public class MovieDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
//return Task.CompletedTask;
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
Activity activity = (Activity)context.Activity;
string szMovieDate = "";
string szMovieLanguage = "";
string szMovieName = "";
context.ConversationData.TryGetValue(ContextConstants.MovieDateKey, out szMovieDate);
context.ConversationData.TryGetValue(ContextConstants.LanguageKey, out szMovieLanguage);
if (!(context.ConversationData.TryGetValue(ContextConstants.MovieNameKey, out szMovieName)))
{
var reply = context.MakeMessage();
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments = GetCardsAttachments(context);
await context.PostAsync(reply);
}
else
{
context.ConversationData.SetValue(ContextConstants.MovieNameKey, Convert.ToString(activity.Text));
await context.Forward(new ConfirmationDialog(), this.ResumeAfterConfirmationDialog, activity, CancellationToken.None);
//await context.PostAsync(activity);
}
context.Wait(this.MessageReceivedAsync);
//Below is Test line
//await context.PostAsync("List of movies in " + Convert.ToString(activity.Text));
//If Context Forward is there then ContextWait is not needed.
//context.Wait(MessageReceivedAsync);
}
private static IList<Attachment> GetCardsAttachments(IDialogContext context)
{
List<Attachment> oImageAttachment = new List<Attachment>();
Dictionary<string, string> lstmovie = new Dictionary<string, string>();
lstmovie.Add("Movie1", #"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg");
lstmovie.Add("Movie2", #"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg");
lstmovie.Add("Movie3", #"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg");
lstmovie.Add("Movie4", #"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg");
lstmovie.Add("Movie5", #"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg");
foreach (KeyValuePair<string, string> movielist in lstmovie)
{
oImageAttachment.Add(
GetHeroCard(
movielist.Key,
new CardImage(url: movielist.Value),
new CardAction(ActionTypes.PostBack, "Book","",movielist.Key))
);
}
context.ConversationData.SetValue(ContextConstants.MovieNameKey, "");
return oImageAttachment;
}
private static Attachment GetHeroCard(string title, CardImage cardImage, CardAction cardAction)
{
var heroCard = new HeroCard
{
Title = title,
Images = new List<CardImage>() { cardImage },
Buttons = new List<CardAction>() { cardAction },
};
return heroCard.ToAttachment();
}
private async Task ResumeAfterConfirmationDialog(IDialogContext context, IAwaitable<object> result)
{
// Store the value that NewOrderDialog returned.
// (At this point, new order dialog has finished and returned some value to use within the root dialog.)
Activity activity = (Activity)context.Activity;
var optionSelected = activity.Text;
//await context.PostAsync(Convert.ToString(resultFromNewOrder));
// Again, wait for the next message from the user.
context.Done(optionSelected);
}
}
ConfirmationDialog
[Serializable]
public class ConfirmationDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
//return Task.CompletedTask;
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
Activity activity = (Activity)context.Activity;
this.ShowOptions(context);
//Below is Test line
//context.ConversationData.TryGetValue(ContextConstants.MovieDateKey, out szMovieDate);
//await context.PostAsync("List of movies in " + Convert.ToString(activity.Text));
//If Context Forward is there then ContextWait is not needed.
//context.Wait(MessageReceivedAsync);
}
private void ShowOptions(IDialogContext context)
{
string szMovieDate = "";
string szMovieLanguage = "";
string szMovieName = "";
context.ConversationData.TryGetValue(ContextConstants.MovieDateKey, out szMovieDate);
context.ConversationData.TryGetValue(ContextConstants.LanguageKey, out szMovieLanguage);
context.ConversationData.TryGetValue(ContextConstants.MovieNameKey, out szMovieName);
string szConfirmationMessage = "";
szConfirmationMessage = String.Format("You have booked ticket for movie {0} for this date {1}", szMovieName, szMovieDate);
PromptDialog.Choice(context, this.OnOptionConfirmationSelected, new List<string>() { "Yes", "No" }, szConfirmationMessage, "Not a valid option", 3);
//context.Wait(this.MessageReceivedAsync);
}
private async Task OnOptionConfirmationSelected(IDialogContext context, IAwaitable<string> result)
{
Activity activity = (Activity)context.Activity;
var optionSelected = activity.Text;
if (optionSelected.ToUpper() == "YES" || optionSelected.ToUpper() == "NO")
{
//await context.PostAsync(Convert.ToString(optionSelected));
context.Done(optionSelected);
}
else
{
context.Wait(this.MessageReceivedAsync);
}
}
}
The problem is that in the MessageReceivedAsync method of the MovieDialog you are doing a context.Wait(this.MessageReceivedAsync); outside the if/else clause an so you are waiting in that dialog and at the same time you are forwarding the message to the ConfirmationDialog. You should move the wait line inside the if, right after the PostAync
I am trying to authenticate a user before proceeding to the rest of the bots functions and have this code to ensure a user exists
[Serializable]
public class ModifyLicenceDialog: IDialog<object>
{
private const string CreateLicence = "Create a new licence";
private const string ModifyEndDate = "Modify end date";
private const string CancelLicence = "Cancel a licence";
public async Task StartAsync(IDialogContext context)
{
if (!CommonConversation.User.IsAuthorized)
{
context.Call(new AuthDialog(), ResumeAfterAuth);
}
}
private async Task ResumeAfterAuth(IDialogContext context, IAwaitable<object> result)
{
await result;
PromptDialog.Choice(context, ResumeAfterChoice, new[] { CreateLicence, ModifyEndDate, CancelLicence },
"What licence function would you like?",
"I am sorry i did not understand that, please select one of the below options");
}
private async Task ResumeAfterChoice(IDialogContext context, IAwaitable<string> result)
{
string selected = await result;
switch (selected)
{
case CreateLicence:
context.Call(new CreateLicenseDialog(), AfterLicenseDialog(CreateLicence));
break;
case ModifyEndDate:
break;
case CancelLicence:
break;
}
}
private ResumeAfter<object> AfterLicenseDialog(IDialogContext context, string licenseEvent)
{
switch (licenseEvent)
{
case CancelLicence:
await context.PostAsync("Auth done");
context.Done(licenseEvent);
break;
}
}
However when my code goes into the AuthDialog when it tries to call the api to get if the user with the provided email exists (specifically at res = await client.GetAsync(uri)) once the api returns a result the bot returns to the parent dialog (ModifyLicenceDialog) and executes ResumeAfterAuth
[Serializable]
public class AuthDialog: IDialog<object>
{
private const string InputEmail = "Please input your email address to continue.";
private int _attempts = 3;
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync(InputEmail);
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
IMessageActivity activity = await result;
string email = activity.Text;
await context.PostAsync("Please wait while we verify this...");
HttpResponseMessage res = new HttpResponseMessage();
HttpClient client = AuthFunctions.Client;
string uri = $"api/v1/auth/licenses/{email}";
res = await client.GetAsync(uri);
if (res.IsSuccessStatusCode)
{
CommonConversation.User = JsonConvert.DeserializeObject<CustomerUser>(await res.Content.ReadAsStringAsync());
await context.PostAsync($"Welcome {CommonConversation.User.FirstName}!");
context.Done(email);
}
else if (_attempts > 0)
{
_attempts--;
await context.PostAsync("I am sorry we do not recognize that email address, lets try again. Please input your email address");
context.Wait(MessageReceivedAsync);
}
else
{
context.Fail(new Exception("I am afraid you are not an authorized user"));
}
}
}
}
Update after some guidance from Nicolas R it turns out that at that point an exception is thrown
the exception is as follows
{"Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host."}
" at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n at SupportHelpdeskBot.Dialogs.AuthDialog.<MessageReceivedAsync>d__3.MoveNext() in C:\\Workspace\\SupportBot\\SupportHelpdeskBot\\SupportHelpdeskBot\\Dialogs\\AuthDialog.cs:line 41"
I have tried System.Net.ServicePointManager.Expect100Continue = false; to stop this from happening but this hasnt work any suggesitons?
There is no error in your implementation: you are completing your AuthDialog here with your context.Done(email):
if (res.IsSuccessStatusCode)
{
CommonConversation.User = JsonConvert.DeserializeObject<CustomerUser>(await res.Content.ReadAsStringAsync());
await context.PostAsync($"Welcome {CommonConversation.User.FirstName}!");
context.Done(email);
}
That's the normal behaviour. What do you want to implement? I think your question is incomplete
I am trying write a webapi which tries to post a webapi call using async and await,my current issue is as soon as I call await client.PostAsync(url, content); it hangs.
1.How to debug why it is hanging?
2.Is there a way to do it without async and await?I want to do it sequentially
public static async Task<string> testWCF2(string xmlConfig)
{
string submitOut;
using (var client = new System.Net.Http.HttpClient())
{
var url = "http://server:8100/api/SoftwareProductBuild";
var content = new StringContent(xmlConfig, Encoding.UTF8, "application/xml");
var response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
submitOut = responseBody;
}
else
{
submitOut = string.Format("Bad Response {0} \n", response.StatusCode.ToString());
submitOut = submitOut + response;
}
}
return submitOut;
}
public async Task<string> QlasrSubmit(List<XMLSiInfo> xmlConfigs)
{
string submitOut = "QLASR: ";
foreach (XMLSiInfo xmlConfig in xmlConfigs)
{
submitOut = submitOut + "\n" + await testWCF2(xmlConfig.xml);
}
return submitOut;
}
public async Task<string> QlasrPostcommit(string si, string sp, string variant = null)
{
.....
string submitStatus = await QlasrSubmit(siInfo);
.....
return submitStatus;
}
Service:
public async Task<string> QlasrPostcommit(string si, string sp, string variant = null)
{
return await DPR.QlasrPostcommit(si, sp, variant);
}
Controller:
[Route("api/DevPool/QlasrPostcommit")]
[HttpPost]
public ResponseObject QlasrPostcommit(string si, string sp, string variant = null)
{
ResponseObject response = new ResponseObject();
try
{
response.status = 200;
response.data = DPS.QlasrPostcommit(si, sp, variant);
return response;
}
catch (Exception e)
{
response.status = 200;
response.data = null;
response.message = e.Message;
return response;
}
}
You should use async all the way, as I mentioned in your previous question:
[Route("api/DevPool/QlasrPostcommit")]
[HttpPost]
public async Task<ResponseObject> QlasrPostcommit(string si, string sp, string variant = null)
{
ResponseObject response = new ResponseObject();
try
{
response.status = 200;
response.data = await DPS.QlasrPostcommit(si, sp, variant);
return response;
}
catch (Exception e)
{
response.status = 200;
response.data = null;
response.message = e.Message;
return response;
}
}
In this particular case, you're running into a deadlock because you're blocking on asynchronous code.
I solved it and it works perfectly, without deallock and with waiting result!!
You have fix the Service:
public string QlasrPostcommit(string si, string sp, string variant = null)
{
Task<string > task = Task.Run<string >(async () => await
DPR.QlasrPostcommit(si, sp, variant));
task.Result;
}
Generic answer:
public TypeToReturn MyAsyncMethod(myParams...)
{
Task<TypeToReturn> task = Task.Run<TypeToReturn>(async () => await
MyAsyncMethod(myParams...));
task.Result;
}