MultipleResumeHandlerExeption when forwarding from LuisDialog to IDialog [duplicate] - dialog

I'm creating a bot for FAQ . When bot start conversations send a PromptDialog with 2 options: english, french.
I want to forward the dialog to EnglishLuis when user chooses English button, and FrenchLuis when choosing French.
Here is my code :
Rootdialog.cs
public class RootDialog : IDialog<object>
{
private const string EnglishMenu = "English";
private const string FrenchMenu = "French";
private const string QAMenu = "Q&A";
private List<string> mainMenuList = new List<string>() { EnglishMenu, FrenchMenu, QAMenu };
private string location;
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to Root Dialog");
context.Wait(MessageReceiveAsync);
}
private async Task MessageReceiveAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var reply = await result;
if (reply.Text.ToLower().Contains("help"))
{
await context.PostAsync("You can implement help menu here");
}
else
{
await ShowMainmenu(context);
}
}
private async Task ShowMainmenu(IDialogContext context)
{
//Show menues
PromptDialog.Choice(context, this.CallDialog, this.mainMenuList, "What do you want to do?");
}
private async Task CallDialog(IDialogContext context, IAwaitable<string> result)
{
//This method is resume after user choise menu
// this.luisResult = result;
// var message = await result;
var selectedMenu = await result;
var message = await result;
switch (selectedMenu)
{
case EnglishMenu:
//Call child dialog without data
// context.Call(new EnglishLuis(),ResumeAfterDialog);
// context.Call(new EnglishLuis(), ResumeAfterDialog);
await Conversation.SendAsync(context.MakeMessage(), () => new EnglishLuis());
break;
case FrenchMenu:
//Call child dialog with data
context.Call(new HotelDialog(location), ResumeAfterDialog);
break;
case QAMenu:
context.Call(new LuisCallDialog(),ResumeAfterDialog);
break;
}
}
private async Task ResumeAfterDialog(IDialogContext context, IAwaitable<object> result)
{
//Resume this method after child Dialog is done.
var test = await result;
if (test != null)
{
location = test.ToString();
}
else
{
location = null;
}
await this.ShowMainmenu(context);
}
}
}
EnglishLuis.cs :
public class EnglishLuis : LuisDialog<object>
{
private string location;
// string message = $"welcome to english dialog";
public async Task None(IDialogContext context, LuisResult result)
{
string message = $"Sorry, I did not understand '{result.Query}'. Please try again";
await context.PostAsync(message);
context.Wait(this.MessageReceived);
context.Done(true);
}
[LuisIntent("gretting")]
[LuisIntent("intentfr")]
public async Task Greeting(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
await context.PostAsync("Welcome :) ");
context.Wait(MessageReceived);
context.Done(true);
}
[LuisIntent("test")]
public async Task test(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
await context.PostAsync("Do you want to test our bot ? We suggest to type : hi or who are you, help etc..");
// context.Done(true);
context.Wait(MessageReceived);
context.Done(true);
}
My problem is that when i choose English ( even French ) i got this error : here was an error sending this message to your bot: HTTP status code InternalServerError
Can you please help me how to start luis dialog ?
P.S if i start from MessagesController.cs directly works good...but my intentions is to let people choose between two languages.
I try to call the luis using : await context.Forward(new EnglishLuis(), ResumeAfterDialog, message, CancellationToken.None); but without result .
new file RootDialog.cs ( updated ) :
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System.Threading;
namespace TeamsBot.Dialogs
{
[Serializable]
public class RootDialog : IDialog<object>
{
private const string EnglishMenu = "English";
private const string FrenchMenu = "French";
private const string QAMenu = "Q&A";
private List<string> mainMenuList = new List<string>() { EnglishMenu,
FrenchMenu, QAMenu };
private string location;
private string originalMessage;
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Welcome to Root Dialog");
context.Wait(MessageReceiveAsync);
}
private async Task MessageReceiveAsync(IDialogContext context,
IAwaitable<IMessageActivity> result)
{
var reply = await result;
this.originalMessage = reply.Text;
if (reply.Text.ToLower().Contains("help"))
{
await context.PostAsync("You can implement help menu here");
}
else
{
await ShowMainmenu(context);
}
}
private async Task ShowMainmenu(IDialogContext context)
{
//Show menues
PromptDialog.Choice(context, this.CallDialog, this.mainMenuList,
"What do you want to do?");
}
private async Task CallDialog(IDialogContext context, IAwaitable<string>
result)
{
var selectedMenu = await result;
switch (selectedMenu)
{
case EnglishMenu:
//Call child dialog without data
var newMessage = context.MakeMessage();
newMessage.Text = reply.Text;
await context.Forward(new EnglishLuis(), ResumeAfterDialog, newMessage, CancellationToken.None);
break;
case FrenchMenu:
//Call child dialog with data
// context.Call(new HotelDialog(location), ResumeAfterDialog);
var frenchLuis = new FrenchLuis();
var messageToForward = await result;
// await context.Forward(new FrenchLuis(), ResumeAfterDialog, messageToForward, CancellationToken.None);
break;
case QAMenu:
context.Call(new LuisCallDialog(),ResumeAfterDialog);
break;
}
}
private async Task ResumeAfterDialog(IDialogContext context, IAwaitable<object> result)
{
//Resume this method after child Dialog is done.
var test = await result;
if (test != null)
{
location = test.ToString();
}
else
{
location = null;
}
await this.ShowMainmenu(context);
}
}
}

First, doing
context.Wait(this.MessageReceived);
context.Done(true);
it's wrong. You need to choose: or you wait for a new message in the EnglishDialog or you end the EnglishDialog (with Done)
Then, you are trying to send a string in the context.Forward and you need to forward an IMessageActivity. And I suspect you want to send the original message, so you will need to save that in global variable before continuing with the prompt. Try with:
var newMessage = context.MakeMessage();
newMessage.Text = this.originalMessageText //the variable that contains the text of the original message that you will have to save at MessageReceiveAsync
await context.Forward(new EnglishLuis(), ResumeAfterDialog, newMessage, CancellationToken.None);
MessageReceivedAsync in RootDialog should looks like:
private async Task MessageReceiveAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var reply = await result;
if (reply.Text.ToLower().Contains("help"))
{
await context.PostAsync("You can implement help menu here");
}
else
{
this.originalMessage = reply.Text;
await ShowMainmenu(context);
}
}

This is how I would have implemented your method for calling the different dialogs. I tend to use dependency injection of dialogs so I don't have to constantly new them up.
private async Task CallDialog(IDialogContext context, IAwaitable<string> result)
{
//These two variables will be exactly the same, you only need one
//var selectedMenu = await result;
var message = await result;
switch (selectedMenu)
{
case EnglishMenu:
// Forward the context to the new LuisDialog to bring it to the top of the stack.
// This will also send your message to it so it gets processed there.
await context.Forward<object>(new EnglishLuis(), ResumeAfterDialog, message , CancellationToken.None);
break;
case FrenchMenu:
await context.Forward<object>(new HotelDialog(location), ResumeAfterDialog, message , CancellationToken.None);
break;
case QAMenu:
await context.Forward<object>(new LuisCallDialog(), ResumeAfterDialog, message , CancellationToken.None);
context.Call(new LuisCallDialog(),ResumeAfterDialog);
break;
}
}
There is an issue in your EnglishLuis dialog where you are using:
context.Wait(this.MessageReceived);
context.Done(true);
The issue is that both of these lines will execute when it passes through the dialog. The context.Done will cause this dialog to leave the stack so you'll end up going to the previous dialog instead, which clashes with the fact you're trying to wait for a response.
There shouldn't really be a context.Done in your luis dialog unless you want to go back to the previous Dialog. So if you're choosing to use context.Done either put it in the resumeAfter method with an appropriate condition or under a single intent for exiting this part of your program.
You didn't include a stack trace, but something that can cause issues when using Luis is if you're using one from a region other than the US. In which case you need to set the properties accordingly where the domain points to the right Luis service.
public EnglishLuis(ConstructorParameters parameters)
: base(new LuisService(new LuisModelAttribute(
"<AppId>",
"<SubscriptionKey>",
domain: "westeurope.api.cognitive.microsoft.com")))
{
// Constructor Stuff...
}

Related

Microsoft Bot Framework - Proactive message, suspend current dialog

I've created a simple news bot which sends updates to a user every 24 hours. I've created a callback controller which handles requests from an external service, processes the resumptionCookie, then sends a carousel of articles with two buttons to the user. One of the buttons opens a browser window, the other should trigger a new dialog (OptionsDialog).
Is this implementation correct? and is it possible to suspend any active dialog, whilst the user interacts with the news article message? For example, if i'm going through a particular dialog, then suddenly I get a news alert, is it possible to suspend the current dialog, to allow the user to update the options of the alerts (almost like the news alert is outside the normal dialog flow), then once they've finished they'll return to the previous dialog. Hopefully, the question is clear enough. Any help will be greatly appreciated.
public class CallbackController : ApiController
{
public async Task<IHttpActionResult> Post(ResumptionCookie resumptionCookie)
{
var activity = (Activity)resumptionCookie.GetMessage();
var reply = activity.CreateReply();
reply.Text = "We found 7 news articles that match your criteria";
reply.Attachments = new List<Attachment>
{
new ThumbnailCard
{
Buttons = new List<CardAction>
{
new CardAction(ActionTypes.OpenUrl, "BBC", null, "http://www.bbc.co.uk"),
new CardAction(ActionTypes.PostBack, "Update Options", null, "Update Options")
}
}.ToAttachment()
};
var client = new ConnectorClient(new Uri(activity.ServiceUrl));
await client.Conversations.ReplyToActivityAsync(reply);
return Ok(new { success = true });
}
}
This is my Main dialog
[Serializable]
public class MainDialog : IDialog
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(ProcessMessage);
}
private async Task ProcessMessage(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var response = await result;
if (response.Text.Equals("Update Options", StringComparison.OrdinalIgnoreCase))
{
context.Call(new OptionsDialog(), FinishMainDialog);
}
else
{
PromptDialog.Confirm(context, ProcessChoice, "Do you wish to continue?");
}
}
private async Task ProcessChoice(IDialogContext context, IAwaitable<bool> result)
{
var choice = await result;
if(choice)
{
context.Call(new DialogOne(), FinishMainDialog);
}
else
{
context.Done(true);
}
}
private async Task FinishMainDialog(IDialogContext context, IAwaitable<object> result)
{
context.Done(true);
}
}
Here is my frequency dialog
[Serializable]
public class OptionsDialog : IDialog
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("You can update your to options here");
context.Wait(ProcessMessage);
}
public async Task ProcessMessage(IDialogContext context, IAwaitable<IMessageActivity> activity)
{
await context.PostAsync("Hello, World!");
context.Done(true);
}
}

How to deal with Context.Done(R value)

I have a msbot chat dialog that I want to have the following behaviour:
user -> get me some info about GARY
bot -> which gary, (prompt: choice options)
user -> gary peskett
bot -> sure, (hero card with gary's contact details)
I have this code
public class CustomerRepository
{
private IList<Customer> _customerList = new List<Customer>
{
new Customer
{
Name = "Gary Peskett"
},
new Customer
{
Name = "Gary Richards"
},
new Customer
{
Name = "Barry White"
}
};
public async Task<IEnumerable<Customer>> GetAll()
{
// usually calls a database (which is why async is on this method)
return _customerList;
}
}
public class XDialog : IDialog
{
private readonly IIntent _intent;
private readonly CustomerRepository _customerRepository;
public XDialog(IIntent intent, CustomerRepository customerRepository)
{
// An intent is decided before this point
_intent = intent;
_customerRepository = customerRepository;
}
public async Task StartAsync(IDialogContext context)
{
// // An intent can provide parameters
string name = _intent.Parameters["Name"] as string;
IEnumerable<Customer> customers = await _customerRepository.GetAll();
IList<Customer> limitedList = customers.Where(x => x.Name.Contains(name)).ToList();
if (limitedList.Any())
{
if (limitedList.Count > 1)
{
PromptDialog.Choice(context, LimitListAgain, limitedList,
"Can you specify which customer you wanted?");
}
else
{
Customer customer = limitedList.FirstOrDefault();
Finish(context, customer);
}
}
else
{
context.Done("No customers have been found");
}
}
private static async Task LimitListAgain(IDialogContext context, IAwaitable<Customer> result)
{
Customer customer = await result;
Finish(context, customer);
}
private static void Finish(IDialogContext context, Customer customer)
{
HeroCard heroCard = new HeroCard
{
Title = customer?.Name
};
context.Done(heroCard);
}
}
What i'm finding is that usually when I do context.Done(STRING) then that is output to the user, and this is really useful to end the dialog. As I want to end with a hero card, its outputing the typename
Microsoft.Bot.Connector.HeroCard
Can anyone help by either explaining a better way to use context.Done(R value) or help me return a hero card to end the dialog?
The dialog is being called with
Chain.PostToChain()
.Select(msg => Task.Run(() => _intentionService.Get(msg.ChannelId, msg.From.Id, msg.Text)).Result)
.Select(intent => _actionDialogFactory.Create(intent)) // returns IDialog based on intent
.Unwrap()
.PostToUser();
I think the problem is a side effect of using Chain.
As you may know, the context.Done doesn't post anything back to the user, it just ends the current dialog with the value provided.
The post to user is effectively happening in the .PostToUser() at the end of your Chain. Now, by looking into the PostToUser's code, I realized that at the end of the game, it's doing a context.PostAsync of item.ToString(), being item the payload provided in the context.Done in this case. See this.
One option (I haven't tested this), could be using .Do instead of .PostToUser() and manually perform what the PostToUserDialog does and finally perform a context.PostAsync() by creating a new IMessageActivity and adding the HeroCard as an attachment.

SaveChangesAsync not working inside Azure webjob

I have an Azure job which asynchronously saves records to the database. I am finding that it does not actually save anything to the database. I am definitely using async/awaits everywhere. I am adding parent (market) and child records. My database has referential constraints so the parent has to exist before the child, but that should be fine as I am doing them in the right order. I have no try-catches around my methods and there is nothing in the azure logs so it appears the job is succeeding. I have called Method1 with an await from a Winforms exe and it works fine from that. What can be wrong?
public static async Task MyJob([TimerTrigger("00:02:00", RunOnStartup = true)] TimerInfo timerInfo, TextWriter log)
{
await Jobs.Method1(Client, Logger);
}
public static async Task Method1(IClient client, ILogger logger)
{
await DataRepository.AddMarket(event.Id, event.MarketId);
await DataRepository.AddMarketChild(event.MarketId, 999);
}
public static async Task<Market> AddMarket(string eventId, string marketId)
{
using (var ctx = BTBEntities.CreateContext())
{
var market = new Market()
{
MarketId = marketId,
EventId = eventId,
};
ctx.Markets.Add(market);
await ctx.SaveChangesAsync();
return market;
}
}
public static async Task<HorseBet> AddHorseBet(string marketId, long selectionId)
{
using (var ctx = BTBEntities.CreateContext())
{
var bet = new MarketChild()
{
MarketId = marketId,
SelectionId = selectionId,
};
ctx.MarketChilds.Add(bet);
await ctx.SaveChangesAsync();
return bet;
}
}
Turns out there was a database problem it was just being swallowed by Azure. This is how I fixed it.
public override System.Threading.Tasks.Task<int> SaveChangesAsync()
{
try
{
return base.SaveChangesAsync();
}
catch (DbEntityValidationException ex)
{
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
var fullErrorMessage = string.Join("; ", errorMessages);
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}

C# Parse how to wait until Query returns a value

I'm trying to retrieve user data from Parse (xamarin.ios using c#). I'm using an async method with await. My challenge is,each time I navigate to the tableView in the app, which should populate the user data in question,the table is always empty.
I would like to wait until the results have been returned before proceeding with the other portion of code.I have tried to use the ContinueWith() function but constantly ran into a build error -
Cannot implicitly convert type 'void' to System.Collections.Generic.IEnumerable<Parse.ParseObject>
My Questions:
Is this the best way to wait for the result?
How do I solve the build error?
Here is my current implementation:
public async void retrieveData(string username)
{
try
{
this.requests.ClearRequests();
refreshed = false;
var query = ParseObject.GetQuery("Requests").WhereEqualTo("username", username);
IEnumerable<ParseObject> results = await query.FindAsync().ContinueWith(t =>{
if(results != null)
{
foreach(ParseObject parseObject in results)
{
UserRequest request = new UserRequest();
request.objectId = parseObject.ObjectId;
request.make = parseObject.Get<string> ("item1");
request.model = parseObject.Get<string> ("item2");
request.year = parseObject.Get<string> ("item3");
request.userName = parseObject.Get<string> ("username");
this.requests.addRequest (request);
}
refreshed = true;
}
});
}
catch(ParseException e) {
Console.WriteLine (e.Message + e.StackTrace);
}
}
You shouldn't need a ContinueWith...that's what the await should handle.
await waits on a Task and then brings back the result with the proper return type. ContinueWith returns a Task, so you would have to grab the Result from the task to make it usable.
For more on this type of thing, you may want to check out Difference between await and ContinueWith
You can try something like this.
public async void retrieveData(string username, )
{
try
{
this.requests.ClearRequests();
refreshed = false;
var query = ParseObject.GetQuery("Requests").WhereEqualTo("username", username);
IEnumerable<ParseObject> results = await query.FindAsync();
if(results != null)
{
foreach(ParseObject parseObject in results)
{
UserRequest request = new UserRequest();
request.objectId = parseObject.ObjectId;
request.make = parseObject.Get<string> ("item1");
request.model = parseObject.Get<string> ("item2");
request.year = parseObject.Get<string> ("item3");
request.userName = parseObject.Get<string> ("username");
this.requests.addRequest (request);
}
refreshed = true;
}
//This is your refresh method for your TableView
this.RefreshTableView();
//or, if in iOS
NSNotificationCenter.DefaultCenter.PostNotificationName("resultsRetrieved", null);
}
catch(ParseException e) {
Console.WriteLine (e.Message + e.StackTrace);
}
}
To show the results in the tableView, I would recommend moving the refreshing of the tableView to a separate method that gets triggered synchronously after the results have been retrieved and parsed. This is shown with the this.RefreshTableView() call above.
If in iOS on Xamarin, another option is to post a notification to the NSNotificationCenter (the Xamarin documentation for which is here). Use the PostNotificationName part seen above instead and then add an observer in the ViewControllers that you want to be dependent on the data. This is done as follows:
Make a notificationToken object:
NSObject notificationToken;
Then in your setup method (you could put this inside of your ViewDidLoad):
void Setup ()
{
notificationToken = NSNotificationCenter.DefaultCenter.AddObserver ("resultsRetrieved", RefreshData);
}
Make your RefeshData method:
void RefreshData (NSString notifString)
{
this.tableView.ReloadData();
}
And then, make sure you dispose of the notification observer when you tear down the class
void Teardown ()
{
NSNotificationCenter.DefaultCenter.RemoveObserver (notificationToken);
}
I had a similar issue so started using callbacks. I'm using them in Xamarin.Android, pretty sure they're available in Xamarin.iOS.
Method that starts the task method - Note I am passing in a method of this class as a parameter
private async void updatedData()
{
await Utils.DataTasks.getNewLiveTips(populateTipsList);
}
Method that calls for data from server
public class DataTasks
{
public static async Task getAllData(Action<IEnumerable<ParseObjects>> callback) {
var query = new ParseQuery<ParseObjects>().OrderByDescending("updatedAt").Limit(5);
IEnumerable<ParseObjects> parseTips = await query.FindAsync();
foreach (var tip in parseTips)
{
// Save data to DB if needed
}
callback(parseTips);
}
Method I passed as parameter in the first instance is now called
private void populateTipsList(IEnumerable<ParseObjects> results)
{
mAdapter = new TipAdapter(this.Activity, results);
mRecyclerView.SetAdapter(mAdapter);
refresher.Refreshing = false;
}

Windows 8 - await Task<Bool> - async call back on completed listener required

I have a function which opens a file from storage and returns back a Boolean specified that the file opened just fine.
private async Task<bool> SaveImage()
{
try
{
await filesave.openAsync(FileAccessMode.ReadWrite)
}
catch()
{
return false;
}
return true;
}
I want to call the await SaveImage() function, but somehow want a listener/event handler which tells me when this has completed.. upon completion I want to update my layout with new data. How is this possible using the new WINRT async/ await async methodology for windows 8? is there a work around/substitute.
How can I setup a event handler type scenario? (on complete)
You just await a call to your method, and follow it with the code that should run when it's completed. You don't need to manually register an event handler.
var succeeded = await SaveImage();
// Because of the "await" keyword in the above line, the current method
// will not continue until "SaveImage" has completed its async work and
// signaled its Task
if (succeeded) { ... }
Of course, since the above code uses the await keyword, it needs to be placed inside a method that's also marked async. If that method needs to signal its completion to its caller, then it should also return a Task or Task<T>; for example:
private async Task<string> MyHelperMethod() {
var succeeded = await SaveImage();
if (succeeded)
return "Success";
else
return "Failure";
}
// then, in some other method:
var message = await MyHelperMethod();
Or, if the method calling SaveImage is the end of the line -- say it's the handler for a Button's Click event -- then it can be async void:
private async void ButtonClick(object sender, EventArgs args) {
var succeeded = await SaveImage();
if (succeeded) { ... }
}
Joe's answer looks great, though if you insist on using an event - for example if your SaveImage() calls are in various areas of code unrelated to updating the layout - you can just raise an event when your operation completes. You can use the plain old CLR events or use a pub-sub pattern implementation like the EventAggregator from Prism or Messenger from MVVM Light. The POCE version could look like this
public event EventHandler<Tuple<StorageFile,bool>> SaveImageCompleted;
private async Task<bool> SaveImage(StorageFile file)
{
try
{
await file.OpenAsync(FileAccessMode.ReadWrite);
}
catch
{
if (SaveImageCompleted != null)
SaveImageCompleted(this, new Tuple<StorageFile, bool>(file, false));
return false;
}
if (SaveImageCompleted != null)
SaveImageCompleted(this, new Tuple<StorageFile, bool>(file, true));
return true;
}
This takes a little more code but is a VERY cool and useful way to deal with async operations, progress, cancellations and complete status in general. This was compiled in a VS2012 Winrt Store App and I ran it off a button click as you see here.
private void Save_File_Click(object sender, RoutedEventArgs e)
{
// create your op, bool = return type, string = progress report
IAsyncOperationWithProgress<bool, string> op;
// Call our async operation with progress sending the file name
op = OpenFileWithProgress("test.txt");
// not implemented here
//op.Cancel();
// when we get a progress update...
op.Progress = (info, progress) =>
{
// I'm just giving text feed back to user here
Debug.WriteLine(progress);
};
op.Completed = (info, status) =>
{
// check status for completion or cancellation...
switch (status)
{
case AsyncStatus.Completed:
// Do your completed work here
Debug.WriteLine("Completed");
break;
case AsyncStatus.Canceled:
// Operation canceled - not implemented...
Debug.WriteLine("Canceled");
break;
default:
// default stuff here
break;
}
};
}
public IAsyncOperationWithProgress<bool, string> OpenFileWithProgress(string fileName)
{
return System.Runtime.InteropServices.WindowsRuntime.AsyncInfo.Run<bool, string>((token, progress) =>
Task.Run<bool>(async () =>
{
progress.Report("Starting");
try
{
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName);
}
catch (Exception ex)
{
return false;
}
progress.Report("Finished");
return true;
}, token));
}

Resources