I want to ask a user which widget it wants to use after searching for widgets, which results in a list of widgets. I want to be able to click on the name of a widget, and then get the URL of the widget. When I run the following code, I get invalid need: expected Call, have Poll.
public async Task SelectAfterSearch(IDialogContext context, List<Widget> widgetlist)
{
PromptDialog.Choice(context, this.OnWidgetSelected, GetListOfWidgets("list"), "Which one do you want more information about?", "Not a valid option", 3);
}
public async Task OnWidgetSelected(IDialogContext context, IAwaitable<Widget> result)
{
var chosen = await result;
await context.PostAsync($"You have chosen {chosen.Name}: {chosen.Url}");
}
You are missing a context.Wait at the end of your OnWidgetSelected method.
public async Task OnWidgetSelected(IDialogContext context, IAwaitable<Widget> result)
{
var chosen = await result;
await context.PostAsync($"You have chosen {chosen.Name}: {chosen.Url}");
context.Wait(...) // => usually you Wait on the MessageReceived method.
}
Related
I have method A (a Post request using System.Web.Mvc.Controller ) that calls (and awaits) the (long running) asynchronous method B (which creates PDF files).
If, in the mean time (while method A and B are still running) method C (another Post request) is called, method C should wait until method B has finished, because it needs the results (the PDF files) from that method.
I could call method B again from method C, but that would be redundant, since it is already running...
Which concepts exist to achieve this?
Edit
Some simplified sample code to show what I have now:
public class OrderController : Controller
{
private MyContext _context; //Set by Dependency Injection
[HttpPost]
public async Task<IActionResult> SaveOrder(Order order) {
_context.Entry(order).Status = Modified;
_context.SaveChanges();
_ = GenerateAndSavePDF(order).ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
// Ideally, if the SaveOrder method is called multiple times in a short period (when the PDF generation is already running from the previous save), the already running tasks should be stopped and only the latest one should be run (in order to get a PDF with the latest order information)
return View(order);
}
public async Task GenerateAndSavePDF(Order order) {
var pdf = ...... // code to generate pdf
order.pdf = pdf;
_context.SaveChanges();
}
[HttpPost]
public async Task SendEmail(int orderId) {
Order = context.Orders.FirstOrDefault(order => order.Id = orderId);
var mail = ...... // code to generate mail;
mail.attachment = order.pdf; // <==== This is where I need to wait for method GenerateAndSavePdf to be finished before I can send the mail
mail.Send();
}
}
So if a user saves an order, the PDF gets generated. But if in the mean time, the users also chooses to send the email, the SendEmail method has to wait until the PDF is available...
Oversimplified example of what you need:
public class ExampleTaskTracker
{
// the task we're awaiting if there one
Task<object> task;
// initiates the process
public void Start()
{
if (task == null || task.IsCompleted)
task = Task.Delay(5000).ContinueWith((o) => new object());
}
// waits the process to finish if there's one
public async Task<object> End()
{
if (task == null)
return null;
return await task;
}
}
Also don't forget to use CancellationToken if you want to cancel the task at a certain point
async/await is what you need.
Just make sure each of described metods returns either Task or Task<T> (and never void) and var pdf = await GeneratePDF() await it throughtout a whole callstack. That's it, as simple as that.
Keep in mind that you have few embarrassingly simple combinators in the TPL: Task.WhenAny(...) and .WhenAll() to introduce more complicated behaviours. For example, .WhenAny(Task.Delay(timeout), ...) is perfect for timeout-guarding long-running operations.
Another important options is CancellationToken which can be passed over to make your async operations cancellable. Say, timeout fired and you'd like to (at least, try to) stop background operation rather then let it burn your CPU uselessly; without CancellationToken it wouldn't be an easy goal, but luckily it becomes dead simple when CancellationToken presents.
Definitions:
Method A: A post request using System.Web.Mvc.Controller
Method B: Long running asynchronous which creates PDF files
Method C: Controller method that wants the result
First of all, I would just have let Method A wait until B is ready and then return the result for it. That would work well and is the easiest solution.
If that's not what you want, I would see Method A as a job initiator which just returns a queue ticket which can be used to check the result.
To enable that, the class which contains method B must keep track of tickets and which Task they correspond to.
So you would have something like this in your controller:
public Task<string> MethodA(MyViewModel model)
{
var businessEntity = Convert(model);
return _pdfService.GenerateBuild(businessEnitty);
}
public Task<PdfContent> MethodC(string ticket)
{
if (_pdfService.TryGet(ticket, out var pdf)
return StreamContent(pdf.Stream);
return HttpCode(425); //Too early
}
The return types/methods are incorrect, but I think that you can find the correct ones.
Then in the PDF service where method B is, you need to manage a queue. Also note that this class must be registered as a single instance in the IoC container, as it should survive between http requests (a new controller instance is created for every request).
Here is a very simple example:
public class PdfService
{
Dictionary<string, Task> _workMap = new Dictionary<string, Task>();
public string StartGeneration(PdfModel model)
{
var pdfBuilder = new PdfBuilder();
var task = pdfBuildr.BuildAsync(model);
var ticket = Guid.NewGuid().ToString("N");
_workMap[ticket] = task;
return ticket;
}
public bool TryGet(string ticket, out PdfDocument doc)
{
var task = _workMap[ticket);
if (task.IsCompleted)
{
doc = task.Result;
return true;
}
return false;
}
}
Something like that. Do note that the code does not compile. It's here to illustrate a path that you can take.
Hi guys im trying to call a carouseldialog on my luis intent.
Luis Code:
[LuisIntent("Help")]
public async Task Help(IDialogContext context, LuisResult result)
{
context.Call(new CarouselCardsDialog(), DialogsCompleted);
}
private async Task DialogsCompleted(IDialogContext context, IAwaitable<object> result)
{
await context.PostAsync("Please choose from one of the topics above or Type in a new message.");
context.Wait(MessageReceived);
}
What happens is I have to type the message HELP 2x because on the first one, it appears as if nothing has happened, and it will only come out on the second attempt. Is there a way to call it like an attachment instead? I also notice that after loading the carousel any other message you enter after that will only return the carousel, I want it to retain the Luis dialog and simply call the carousel as an attachment.
There are 2 problems in your implementation as you noticed:
start of your CarouselCards dialog which is currently "not automatic"
end of this dialog as it's continuously displaynig the cards
Problem 1 - Start of Carousel Dialog
For the 1st problem it comes from the way you are sending the user to the dialog:
[LuisIntent("Help")]
public async Task Help(IDialogContext context, LuisResult result)
{
context.Call(new CarouselCardsDialog(), DialogsCompleted);
}
Here you are doing a Call so the child dialog will be started and... that's all. In fact, it's only waiting for your next input due to the implementation of the StartAsync in your child dialog:
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}
There are 2 solutions, both are working (and are doing more or less the same things), choose one (but not both, or you will loop!):
Solution #1 - Change the StartAsync content of your child dialog
It will look like this:
public async Task StartAsync(IDialogContext context)
{
// Force the call to MessageReceivedAsync instead of waiting
await MessageReceivedAsync(context, new AwaitableFromItem<IMessageActivity>(context.Activity.AsMessageActivity()));
}
Solution #2 - Change the way you send to your child: Forward a message instead of Call
Here you forward the last message (context.Activity) to the dialog.
[LuisIntent("Help")]
public async Task Help(IDialogContext context, LuisResult result)
{
//context.Call(new CarouselCardsDialog(), DialogsCompleted);
await context.Forward(new CarouselCardsDialog(), DialogsCompleted, context.Activity.AsMessageActivity(), CancellationToken.None);
}
Problem 2 - End of the dialog to stop displaying cards
As you mentioned the use of the sample here, you may not have ended the Dialog as you can see in the last line here:
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var reply = context.MakeMessage();
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments = GetCardsAttachments();
await context.PostAsync(reply);
context.Wait(this.MessageReceivedAsync);
}
So change context.Wait(this.MessageReceivedAsync); to context.Done<object>(null); and you will end the dialog after displaying the cards.
I recently got into Microsoft's Bot Framework, and this will be one of my first exposure to asynchronous programming in C#. I am creating a prompt that is designed as a selection tree. Using an XML document, I designed a hierarchy of topics the user can select -- I then abstracted the parsing of the XML using a HelpTopicSelector class.
The flow is as follows:
User types "help"
Context forwards to HelpDialog
Help Dialog creates prompt with list of options provided by the HelpTopicSelector
When user selects a prompt option, HelpTopicSelector "selects" the choise and updates a new list of choices from the subtree
Create another prompt with updated topics
Repeat until the last selected topic is the last node - call Context.Done
The help dialog is called from a basic dialog as follows:
private async Task ActivityRecievedAsync(IDialogContext context, IAwaitable<object> result)
{
Activity activity = await result as Activity;
if (activity.Text == "test")
{
await context.PostAsync("works");
}
else if(activity.Text == "help")
{
await context.Forward(new HelpDialog(), this.ResumeAfterHelp, activity.AsMessageActivity(), System.Threading.CancellationToken.None);
await context.PostAsync("Done Selection!");
}
context.Wait(ActivityRecievedAsync);
}
I am almost certain the problem in my code lies in the "loop" nature of my HelpDialog, but I genuinely have no idea WHY it fails.
class HelpDialog : IDialog
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Reached Help Dialog!");
context.Wait(ActivityRecievedAsync);
}
private async Task ActivityRecievedAsync(IDialogContext context, IAwaitable<object> result)
{
var message = await result;
await context.PostAsync("HelpDialog: Activity Received");
await HandleTopicSelection(context);
context.Wait(ActivityRecievedAsync);
}
private async Task HandleTopicSelection(IDialogContext context)
{
List<string> topics = HelpTopicSelector.Instance.Topics;
PromptDialog.Choice<string>(context, TopicSelectedAsync, topics, "Select A Topic:");
// Unecessary?
context.Wait(ActivityRecievedAsync);
}
private async Task TopicSelectedAsync(IDialogContext context, IAwaitable<string> result)
{
string selection = await result;
if (HelpTopicSelector.Instance.IsQuestionNode(selection))
{
await context.PostAsync($"You asked: {selection}");
HelpTopicSelector.Instance.Reset();
context.Done<string>(selection);
}
else
{
HelpTopicSelector.Instance.SelectElement(selection);
await HandleTopicSelection(context);
}
// Unecessary?
context.Wait(ActivityRecievedAsync);
}
}
What I Expect:
I believe the await keyword should hold a Task's execution until the awaited Task is done.
Similarily, I believe Context.Wait is called in the end of Tasks to loop back to the AcitivtyReceived method, which effectively makes the bot wait for a user input.
Assuming that logic is true, the help dialog enters in the StartAsync method and hands control to the ActivityReceivedAsync which responds to the "message" passed by Context.Forward of the parent dialog. Then, it awaits the HandleTopic method which is responsible for the prompt. The prompt continues execution in the TopicSelectedAsync as indicated by the ResumeAfter argument.
The TopicSelectedAsync method checks if the selected topic is at the end of the XML tree, and if so, ends the Dialog by calling Context.Done. Otherwise, it awaits another HandleTopic method, which recursively creates another prompt - effectively creating a loop until the dialog ends.
Given how hacky this looks, I wasn't surprised to face an error. The bot emulator throws a "Stack is Empty" exception
.
After attempting to debug with break points, I notice the HelpDialog abruptly ends and exits when it enters TopicSelectedAsync method (specifically when it awaits the result). Visual Studio throws the following exception:
invalid need: Expected Call, have Poll.
EXTRA NOTE:
I tried coding this logic inside my BasicDialog class initially without forwarding to any other dialog. To my surprise, it almost worked flawlessly.
This survey dialog sample is similar to your scenerio: https://github.com/Microsoft/BotBuilder-Samples/blob/45d0f8767d6b71b3a11b060c893521d5150ede7f/CSharp/core-proactiveMessages/startNewDialogWithPrompt/SurveyDialog.cs
Modifying it to be a help dialog:
[Serializable]
public class HelpDialog : IDialog
{
public async Task StartAsync(IDialogContext context)
{
PromptDialog.Choice<string>(context, TopicSelectedAsync, HelpTopicSelector.Instance.Topics, "Select A Topic:", attempts: 3, retry: "Please select a Topic");
}
private async Task TopicSelectedAsync(IDialogContext context, IAwaitable<object> result)
{
try
{
string selection = await result as string;
if (HelpTopicSelector.Instance.IsQuestionNode(selection))
{
await context.PostAsync($"You asked: {selection}");
HelpTopicSelector.Instance.Reset();
context.Done<string>(selection);
}
else
{
await this.StartAsync(context);
}
}
catch (TooManyAttemptsException)
{
await this.StartAsync(context);
}
}
}
Calling it from a parent dialog like this (using context.Call() instead of .Forward()):
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
Activity activity = await result as Activity;
if (activity.Text == "test")
{
await context.PostAsync("works");
context.Wait(MessageReceivedAsync);
}
else if (activity.Text == "help")
{
context.Call(new HelpDialog(), ResumeAfterHelp);
await context.PostAsync("Called help dialog!");
}
}
private async Task ResumeAfterHelp(IDialogContext context, IAwaitable<object> result)
{
var selection = await result as string;
context.Wait(MessageReceivedAsync);
}
When supplying a method for Context.Wait(), you are actually supplying a continuation delegate. The next message received from the user will be sent to the method last .Wait() 'ed on. If you are forwarding, or calling a separate dialog, the parent should not then also call .Wait(). Also, when calling context.Done(), there should not also be a .Wait() afterwards in the same dialog.
I keep getting this error and I don't know how to fix it: Exception: invalid need: expected Call, have Poll
PromptDialog.Text(context, setEmail, "What is the contact's email? ");
PromptDialog.Text(context, setPhone, "What is the contact's phone number? ");
private async Task setPhone(IDialogContext context, IAwaitable<string> result)
{
this.contact1.Phone = await result;
ReturnContact(context, contact1);
}
private async Task setEmail(IDialogContext context, IAwaitable<string> result)
{
this.contact1.Email = await result;
ReturnContact(context, contact1);
}
the prompt dialogs are part of a different method. How do I prompt the user twice in a row without getting this error?
PromptDialog.Text was not designed to be called twice, because you need two different answers from a user, so in terms of botframework it is like two separate "transactions".
Rather than making a double call you need to create a cascade of calls, where you initiate the Phone question from the Email question handler:
[Serializable]
public class SomeDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
private async Task OnPhoneSet(IDialogContext context, IAwaitable<string> result)
{
var res = await result;
}
private async Task OnEmailSet(IDialogContext context, IAwaitable<string> result)
{
var res = await result;
PromptDialog.Text(context, OnPhoneSet, "What is the contact's phone number? ");
}
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
PromptDialog.Text(context, OnEmailSet, "What is the contact's email? ");
}
}
The workflow is like the following:
User initiates a dialog for the first time. Callstack: StartAsync -> MessageReceivedAsync -> PromptDialog.Text(context, OnEmailSet). Now dialog is waiting for Email posted
User posts Email. Callstack: OnEmailSet -> PromptDialog.Text(context, OnPhoneSet. Now dialog is waiting for Phone posted
User posts Phone. Callstack: OnPhoneSet. On OnPhoneSet you'll do further actions, for example you can close dialog using Context.Done or something.
I am currently playing around with Bots and LUIS.
So I have a running Bot. In my RootDialog, I handle all the intents that I get from LUIS. Now I want to check if an Entity is missing for an intent.
if (result.Entities.Count == 0) {
var ct = new CancellationToken();
await context.Forward(new ParameterDialog(), ResumeAfterParameterDialog, message, ct);
If there is no Entity I'm creating a new child dialog.
public class ParameterDialog : IDialog<object> {
public async Task StartAsync(IDialogContext context) {
context.Wait(MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument) {
argument = new PromptDialog.PromptString("Please enter a parameter", "please try again", 2);
var prompt = await argument;
await context.PostAsync($"Your Parameter is: {prompt}");
context.Done(prompt);
}
}
If I could get user input I would then pass it back to my parent dialog.
Now I don't really know how I can stop the Bot and let it wait for user input.
Can someone please explain how I can accomplish that?
Thank you!
You are missing a context.Call of the PromptString dialog you are creating.
The context.Call method expects a dialog and a 'callback' method (ResumeAfter) that will be called once the dialog completes (in this case, when PromptString completes).
In your scenario your code should look like:
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var dialog = new PromptDialog.PromptString("Please enter a parameter", "please try again", 2);
context.Call(dialog, ResumeAfterPrompt)
}
private Task ResumeAfterPrompt(IDialogContext context, IAwaitable<string> result)
{
var parameter = await result;
context.Done(parameter);
}