I have a root dialog that creates a child dialog like so...
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
string userName = context.Activity?.From.Name;
var customerForm = new FormDialog<CarValuationDialog>(
new CarValuationDialog(userName),
() => CarValuationDialog.BuildForm(),
FormOptions.PromptInStart);
context.Call(customerForm, FormSubmitted);
}
The FormSubmitted method looks like....
public async Task FormSubmitted(IDialogContext context, IAwaitable<CarValuationDialog> result)
{
try
{
var form = await result;
}
catch (FormCanceledException<CarValuationDialog> e)
{
string reply;
if (e.InnerException == null)
{
reply = e.Message;
}
else
{
reply = e.InnerException.Message;
}
context.Reset();
await context.PostAsync(reply);
}
}
When an exception occurs in the child dialog, the method FormSubmitted is executed and goes into the catch block. However, when that method finishes, I still see the "Sorry my bot had an issue" type message appear to the user.
How can I tell the bot code not to fire the unhandled exception code, I believe is in PostUnhandledExceptionToUser? Is there a flag type property I need to set to true or something?
It looks like you are making the dialog stack empty when you have an exception: you should not have this context.Reset() below:
public async Task FormSubmitted(IDialogContext context, IAwaitable<CarValuationDialog> result)
{
try
{
var form = await result;
}
catch (FormCanceledException<CarValuationDialog> e)
{
string reply;
if (e.InnerException == null)
{
reply = e.Message;
}
else
{
reply = e.InnerException.Message;
}
context.Reset();
await context.PostAsync(reply);
}
}
Remove this line and the next message will be handled by your root dialog
in your message controller in POST method, use defaultifexception
await Conversation.SendAsync(activity, () => new Dialogs.DialogLUIS().DefaultIfException());
Related
I'd like to allow all handler methods in a Razor Page to be wrapped by some sort of logic to handle specific exceptions that are more or less validation exceptions.
I've tried the following, but still get the developer exception page:
public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
try
{
await next();
}
catch(NotImplementedException ex)
{
_logger.LogWarning(ex, ex.Message);
ModelState.AddModelError(string.Empty, "Oops... this isn't all done yet.");
context.Result = Page();
}
catch (DomainValidationException ex)
{
ModelState.Include(ex.Results);
context.Result = Page();
}
}
The exception does not appear to bubble up from the await next() call and is handled in aspnetcore somehow.
It turns out that the next returns a result that needs to be inspected to get the exception and return the result.
The final implementation looks something like this:
public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
var result = await next();
if (result.Exception != null)
{
if (result.Exception is NotImplementedException nex)
{
result.ExceptionHandled = true;
_logger.LogWarning(nex, nex.Message);
ModelState.AddModelError(string.Empty, "Oops... this isn't all done yet.");
}
else if (result.Exception is DomainValidationException dex)
{
result.ExceptionHandled = true;
ModelState.Include(dex.Results);
}
if (result.ExceptionHandled)
{
result.Result = Page();
}
}
}
I get "invalid need: expected Wait, have None" exception in RootDialog, MessageReceivedAsync method's context.Forward line. Why could this be happening? what correction should I make? Please find my code below.
I use -C#, Visual Studio 2015, Microsoft Bot Framework, Bot emulator. This is for Web Chat.
MessageController
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
if (activity.Type == ActivityTypes.ConversationUpdate)
{
var greeting = activity.CreateReply("Hi! I'm Cmon. Enter the Id of the Employee you want to know about.");
await connector.Conversations.ReplyToActivityAsync(greeting);
}
else
{
this.HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
RootDialog -
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
try
{
if (message.Text != null)
{
await context.Forward(new DirectoryDialog(), null, message,CancellationToken.None);
}
}
catch (Exception ex)
{
await context.PostAsync($"Oops! Something went wrong :-(");
}
}
DirectoryDialog -
public async Task StartAsync(IDialogContext context)
{
var message = context.Activity as IMessageActivity;
var query = DirectoryQuery.Parse(message.Text);
await context.PostAsync($"Searching for {query.SearchKey}...");
try
{
await SearchEmployee(context, query);
}
catch (FormCanceledException ex)
{
await context.PostAsync($"Oops! Something went wrong :-(");
}
}
private async Task SearchEmployee(IDialogContext context, DirectoryQuery searchQuery)
{
DirectoryDal dal = new DirectoryDal();
DataSet ds = dal.GetEmployeeSearch(searchQuery.SearchKey);
if (ds.Tables.Count > 0 & ds.Tables[0].Rows.Count > 0)
{
StringBuilder employeeSearchMessageBuilder = new StringBuilder(100);
//do something with data table data
var employeeSearchReply = context.MakeMessage();
employeeSearchReply.Type = ActivityTypes.Message;
employeeSearchReply.TextFormat = TextFormatTypes.Markdown;
employeeSearchReply.Text = employeeSearchMessageBuilder.ToString();
await context.PostAsync(employeeSearchReply);
}
}
By passing null here:
await context.Forward(new DirectoryDialog(), null, message,CancellationToken.None);
you are breaking the dialog chain. You have to define your Resume method.
The Troubleshooting section of the documentation has a good section about that, here:
Ensure all dialog methods end with a plan to handle the next message.
All IDialog methods should complete with IDialogStack.Call,
IDialogStack.Wait, or IDialogStack.Done. These IDialogStack methods
are exposed through the IDialogContext that is passed to every IDialog
method. Calling IDialogStack.Forward and using the system prompts
through the PromptDialog static methods will call one of these methods
in their implementation.
I was able to resolve the issue by making the following modifications to my DirectoryDialog.
DirectoryDialog code -
public async Task StartAsync(IDialogContext context)
{
var message = context.Activity as IMessageActivity;
var query = DirectoryQuery.Parse(message.Text);
await context.PostAsync($"Searching for {query.SearchKey}...");
try
{
//await SearchEmployee(context, query);
context.Wait(MessageReceivedAsync);
}
catch (FormCanceledException ex)
{
await context.PostAsync($"Oops! Something went wrong :-(");
}
}
RootDialog code -
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
try
{
if (message.Text != null)
{
await context.Forward(new DirectoryDialog(), ResumeAfterOptionDialog, message,CancellationToken.None);
}
}
catch (Exception ex)
{
await context.PostAsync($"Oops! Something went wrong");
}
finally{
context.Done(true);
}
}
private async Task ResumeAfterOptionDialog(IDialogContext context, IAwaitable<object> result)
{
context.Wait(MessageReceivedAsync);
}
Thank you Tanmoy and Nicolas for helping! :)
context.wait(MessageReceivedAsync);
at the end of MessageReceivedAsync(IDialogContext context, IAwaitable result) method of Root Dialog.
and
context.done(true);
at the end of StartAsync method of DirectoryDialog or within both try and catch block
I have a bot where I am doing something like this:
1) A new Activity (message) arrives.
2) I dispatch the message to a RootDialog.
3) Depending on some Logic, RootDialog can:
a) Call a LuisDialog (handling natural language)
b) Call a CustomDialog (handles some business logic).
But when the user state is reset, and the flow leads to an intention inside the LuisDialog, it calls the intention method twice. Just the first time the state is empty, then it works fine.
Let me show you the code:
MessagesController:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
try
{
await Conversation.SendAsync(activity, () => new RootDialog());
}
catch (HttpRequestException e)
{
...
}
}
RootDialog:
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await MessageReceivedAsync(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> awaitable)
{
bool value = DoSomeCustomLogic();
if (value)
{
string message = DoSomething();
await context.PostAsync(message);
} else {
bool value2 = DoSomeCustomLogic2();
if (value2)
{
var answerValidationDialog = new ValidateAnswerWithUserDialog();
context.Call(answerValidationDialog, ValidateAnswerWithUserDialogCompletedCallback);
} else {
var luisDialog = new LuisDialog();
await context.Forward(luisDialog,LuisDialogCompletedCallback, context.Activity, CancellationToken.None);
}
}
}
Callbacks only do context.Done(true);
And LuisDialog has an Intention which goes like this:
[LuisIntent(LuisUtils.INTENT_MENU_SALUTE)]
public async Task SaluteOrMenu(IDialogContext context, LuisResult result)
{
if (LuisUtils.IntentScoreIsHighEnough(result))
{
string userName = context.Activity.From.Name;
ContextHelper helper = new ContextHelper(MessageReceived);
await helper.AskUserToDoSomeInitialAction(context, saluteWord, userName);
context.Done(true);
}
else
{
await None(context, result);
}
}
And finally class ContextHelper:
public class ContextHelper
{
private Func<IDialogContext, IAwaitable<IMessageActivity>, Task> MessageReceived;
public ContextHelper(Func<IDialogContext, IAwaitable<IMessageActivity>, Task> messageReceived)
{
MessageReceived = messageReceived;
}
public async Task AskUserToDoSomeInitialAction(IDialogContext context, string saluteWord, string userName)
{
var reply = context.MakeMessage();
List<CardAction> buttons = BuildInitialOptionActions();
List<CardImage> images = BuildInitialOptionImages();
string initialText = $"Hi stranger!"
var card = new HeroCard
{
Title = "Hello!"
Text = initialText,
Buttons = buttons,
Images = images
};
reply.Attachments = new List<Attachment> { card.ToAttachment() };
await context.PostAsync(reply);
context.Wait(AfterUserChoseOptionInSalute);
}
private async Task AfterUserChoseOptionInSalute(IDialogContext context, IAwaitable<IMessageActivity> result)
{
await ReDispatchMessageReceivedToDialog(context);
}
private async Task ReDispatchMessageReceivedToDialog(IDialogContext context)
{
await MessageReceived(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
}
The SaluteOrMenu Intention gets called twice (only the first time I interact with the bot or when I delete the state. After Debugging I saw that after doing context.Wait(AfterUserChoseOptionInSalute);, the bot calls that function (instead of waiting for an event to call it)
Any ideas?
Thanks in advance.
I found the line that was wrong. It was on the first dialog (the RootDialog):
public async Task StartAsync(IDialogContext context)
{
await MessageReceivedAsync(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
That is a line that re dispatches a message with the incoming activity. I had it somewhere in some chunk of code and (don't know why), thought it was a good idea to use it on StartAsync. So, because of that, two calls were occurring.
Dumb me.
I just changed it to this and worked:
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
In the bot web chat, when I type a message, the bot says "sending" first and then changes to "couldn't send, Retry". But the message is sent and I am getting the reply. How can I avoid this? Do I need to increase the message timeout? If so, where I need to set it?
This is the code snippet. I am using C# SDK where I have coded in MessageReceivedASync method
namespace Bot_Application1.Dialogs
{
public class HRBotDialog : IDialog<object>
{
public static string dialogcontext = "";
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
// Get the text passed
var message = await argument;
string typedText = message.Text.ToLower();
string answer = "My Answer";
if ((typedText == "hi") || (typedText == "hello"))
{
answer = message.Text;
}
else if ((typedText == "how many personal days do i have left") || (typedText.Contains("personal days")))
{
answer = "Looks like you have 2 remaining for this year";
}
I am adding the controller code here
//[BotAuthentication]
public class MessagesController : ApiController
{
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new HRBotDialog());
}
else
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
var reply = HandleSystemMessage(activity);
if (reply != null)
await connector.Conversations.ReplyToActivityAsync(reply);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
string replyMessage = string.Empty;
if (message.MembersAdded.Any(o => o.Id == message.Recipient.Id))
{
replyMessage += $"How can I help you? \n";
return message.CreateReply(replyMessage);
}
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
Please try adding the Serializable attribute to your HRBotDialog like this:
[Serializable]
public class HRBotDialog : IDialog<object>
{
//...code...
}
I'm developing an app and have run into a problem with asynchronous calls... Here's what i'm trying to do.
The app consumes a JSON API, and, when run, fills the ListBox within a panorama item with the necessary values (i.e. a single news article). When a user selects a ListBox item, the SelectionChanged event is fired - it picks up the articleID from the selected item, and passes it to an Update method to download the JSON response for the article, deserialize it with JSON.NET, and taking the user to the WebBrowser control which renders a html page from the response received.
The problem with this is that I have to wait for the response before I start the NavigationService, but I'm not sure how to do that properly. This way, the code runs "too fast" and I don't get my response in time to render the page.
The event code:
private void lstNews_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (lstNews.SelectedIndex == -1)
{
return;
}
ShowArticle _article = new ShowArticle();
ListBox lb = (ListBox)sender;
GetArticles item = (GetArticles)lb.SelectedItem;
string passId = ApiRepository.ApiEndpoints.GetArticleResponseByID(item.Id);
App.Current.JsonModel.JsonUri = passId;
App.Current.JsonModel.Update();
lstNews.SelectedIndex = -1;
NavigationService.Navigate(new Uri("/View.xaml?id=" + item.Id, UriKind.Relative));
}
OnNavigatedTo method in the View:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
long sentString = long.Parse(NavigationContext.QueryString["id"]);
string articleUri = ApiRepository.ApiEndpoints.GetArticleResponseByID(Convert.ToInt32(sentString));
//this throws an error, runs "too fast"
_article = App.Current.JsonModel.ArticleItems[0];
}
The update method:
public void Update()
{
ShowArticle article = new ShowArticle();
try
{
webClient.DownloadStringCompleted += (p, q) =>
{
if (q.Error == null)
{
var deserialized = JsonConvert.DeserializeObject<ShowArticle>(q.Result);
_articleItems.Clear();
_articleItems.Add(deserialized);
}
};
}
catch (Exception ex)
{
//ignore this
}
webClient.DownloadStringAsync(new Uri(jsonUri));
}
async callback pattern:
public void Update(Action callback, Action<Exception> error)
{
webClient.DownloadStringCompleted += (p, q) =>
{
if (q.Error == null)
{
// do something
callback();
}
else
{
error(q.Error);
}
};
webClient.DownloadStringAsync(new Uri(jsonUri));
}
call:
App.Current.JsonModel.Update(() =>
{
// executes after async completion
NavigationService.Navigate(new Uri("/View.xaml?id=" + item.Id, UriKind.Relative));
},
(error) =>
{
// error handling
});
// executes just after async call above