Search code examples
botframework

Delayed dialog with resume handler


I have the following scenario I think I'm doing it wrong.

I have a RootDialog which calls a ResultDialog. The ResultDialog presents the user a list of results (using HeroCard).

The ResultDialog closes itself using context.Done(..) after the message was posted.

In the RootDialog- AfterResultDialog Resume handler I want to ask the user if he has found the matching result, using another dialog (NotificationDialog), but I want to do that after 30 seconds.

After some research, this seems like it must be done using proactive messages. It this example, I found a way to post the NotificationDialog in a proactive way.

private async Task AfterResultDialog(IDialogContext context, IAwaitable<object> result)
{
    var message = await result as IMessageActivity;

    var conversationReference = context.Activity.ToConversationReference();
    ConversationStarter.conversationReference = JsonConvert.SerializeObject(conversationReference);

    t = new Timer(timerEvent);
    t.Change(30000, Timeout.Infinite);
    context.Wait<string>(NotificationDialogAfter);
}

public void timerEvent(object target)
{
    t.Dispose();
    ConversationStarter.Resume();
}

But the problem I have is that I'm interested in the result of this NotifcationDialog to know what the user wants to do next. But all examples I found using proactive-messages do not regard the result of a proactive message with dialog:

public class ConversationStarter
{
    public static string conversationReference;

    public static async Task Resume()
    {
        var message = JsonConvert.DeserializeObject<ConversationReference>(conversationReference).GetPostToBotMessage();
        var client = new ConnectorClient(new Uri(message.ServiceUrl));
        using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
        {
            var botData = scope.Resolve<IBotData>();
            await botData.LoadAsync(CancellationToken.None);

            var task = scope.Resolve<IDialogTask>();

            // here it seems only to be possible to call a dialog using Void
            var dialog = new NotificationDialog();
            task.Call(dialog.Void<object, IMessageActivity>(), null);

            await task.PollAsync(CancellationToken.None);
            await botData.FlushAsync(CancellationToken.None);
        }
    }
}

The NotificationDialogAfter handler should decide based on the user input which dialog to call next:

private async Task NotificationDialogAfter(IDialogContext context, IAwaitable<string> result)
{
    var whereToContinue = await result;

    if (whereToContinue.Equals("Start over"))
    {
        context.ClearAllConversationDataKeys();
        context.Call(new TagDialog(), this.TagDialogAfter);
    }
    else if (whereToContinue == "Tell friends")
    {
        context.Call(new TellFriendsDialog(), TellFriendsDialogAfter);
    }
    else if (whereToContinue == "Feedback")
    {
        context.Call(new FeedbackDialog(), this.FeedbackDialogAfter);
    }
}

So what I basically want is that the result of the NotificationDialog is forwarded to the NotificationDialogAfter handler which the Root dialog is waiting for. Is this even possible?


Solution

  • I solve the problem by defining static continue handlers (in GlobalContinueHandler), that I can provide inside the NotificationDialog, when calling other dialogs.

    [Serializable]
    public class NotificationDialog : IDialog<string>
    {
        public Task StartAsync(IDialogContext context)
        {
            PromptDialog.Choice(context, Resume, new List<string> { "yes", "no" },
                "Found what you're looking for?");
            return Task.CompletedTask;
        }
    
        private async Task Resume(IDialogContext context, IAwaitable<string> result)
        {
            var message = await result;
            if (message == "yes")
            {
                context.Call(new SignupDialog(), GlobalContinueHandler.SignupDialogAfter);
            }
            else
            {
                context.Call(new FeedbackDialog(), GlobalContinueHandler.FeedbackDialogAfter);
            }
        }
    }
    

    I'm really not fan of this solution but for now it seems to work.