未调用 Botframework 恢复对话框

Botframework resume dialog not called

我在 botframework V4 中的组件对话框有问题。

我正在使用一个包含 Waterfalldialog 和 Setupdialog 的根对话框。初始对话框是设置对话框。像这样:

public RootDialog(SetupDialog setupDialog)
            : base(nameof(RootDialog))
        {
            AddDialog(setupDialog);
            AddDialog(new TextPrompt(nameof(TextPrompt)));
            AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
            {
                ProcessStep
            }));
            InitialDialogId = nameof(SetupDialog);
        }

在设置对话框中,我要求输入一些值。如果设置对话框继续,我会检查值,如果符合我的要求,我会结束对话框。

public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
        {
            if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
            {
                return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
                {
                    Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
                });
            }

            return outerDc.EndDialogAsync(recursions);
        }

如果我这样结束对话框,难道不应该在 RootDialog 中调用 ResumeDialog 吗?

这里是整个对话框:

public class RootDialog : ComponentDialog
    {
        private int recursions;

        public RootDialog(SetupDialog setupDialog)
            : base(nameof(RootDialog))
        {
            AddDialog(setupDialog);
            AddDialog(new TextPrompt(nameof(TextPrompt)));
            AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
            {
                ProcessStep
            }));
            InitialDialogId = nameof(SetupDialog);
        }

        public async Task<DialogTurnResult> ProcessStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {

            await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
            {
                Prompt = MessageFactory.Text("Process step in root dialog")
            });

            if(Dialogs.Find(nameof(RecursiveDialog)) == null)
            {
                AddDialog(new RecursiveDialog(new DialogSet(), recursions));
            }

            if (recursions > 0)
            {
                return await stepContext.BeginDialogAsync(nameof(RecursiveDialog));
            }

            return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
            {
                Prompt = MessageFactory.Text("Recursion lower or eqaul 0")
            });
        }

        public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
        {
            var dialogContext = CreateChildContext(outerDc);
            await dialogContext.PromptAsync(nameof(TextPrompt), new PromptOptions
            {
                Prompt = MessageFactory.Text("Begin root dialog")
            });

            return await base.BeginDialogAsync(outerDc, options, cancellationToken);
        }

        public override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
        {
            if(innerDc.ActiveDialog != null)
            {
                return await innerDc.ContinueDialogAsync();
            }

            return await base.OnContinueDialogAsync(innerDc, cancellationToken);
        }

        public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext outerDc, DialogReason reason, object result = null, CancellationToken cancellationToken = default)
        {
            recursions = Convert.ToInt32(result);

            await outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions
            {
                Prompt = MessageFactory.Text($"Resume root dialog with recursion value {recursions}")
            });

            return await outerDc.BeginDialogAsync(nameof(WaterfallDialog));
        }
    }

设置对话框:

 public class SetupDialog : ComponentDialog
    {
        public SetupDialog()
            : base(nameof(SetupDialog))
        {
            AddDialog(new TextPrompt(nameof(TextPrompt)));
        }

        public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
        {
            if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
            {
                return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
                {
                    Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
                });
            }

            return outerDc.EndDialogAsync(recursions);
        }

        public override Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default)
        {
            return innerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
            {
                Prompt = MessageFactory.Text("Wie viel rekursionen sollen erstellt werden?")
            });
        }
    }

所以,我触发了 ResumeDialog 方法。

要触发 ResumeDialog 方法,结束的对话框和要恢复到的对话框必须在同一个对话框堆栈上!

我的场景是 BotDialogContext[RootDialog] -> RootDialogContext[SetupDialog],但我需要这样的上下文 BotDialogContext[RootDialog, SetupDialog]。

其中一个问题是,您启动的每个 ComponentDialog 都会创建自己的 DialogContext。因此,如果您在一个对话框中开始一个对话框,它会被推送到内部 DialogContext 的堆栈上,依此类推。但是ResumeDialog方法的描述是

Called when a child dialog on the parent's dialog stack completed this turn, returning control to this dialog component.

要将子对话框放在父对话框堆栈上,您必须在外部对话框上下文中调用 BeginDialog 方法。此上下文还需要在其对话集中包含 "child dialog"。

这是我的例子:

RootDialog.cs:

public class RootDialog : ComponentDialog
{
    private int recursions;

    public RootDialog(SetupDialog setupDialog)
        : base(nameof(RootDialog))
    {
        AddDialog(setupDialog);
        AddDialog(new TextPrompt(nameof(TextPrompt)));
        AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
        {
            ProcessStep
        }));
        InitialDialogId = nameof(SetupDialog);
    }

    public async Task<DialogTurnResult> ProcessStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {

        await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
        {
            Prompt = MessageFactory.Text("Process step in root dialog")
        });

        if(Dialogs.Find(nameof(RecursiveDialog)) == null)
        {
            AddDialog(new RecursiveDialog(new DialogSet(), recursions));
        }

        if (recursions > 0)
        {
            return await stepContext.BeginDialogAsync(nameof(RecursiveDialog));
        }

        return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions
        {
            Prompt = MessageFactory.Text("Recursion lower or eqaul 0")
        });
    }

    public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
    {
        if (true)
        {
            return await outerDc.BeginDialogAsync(nameof(SetupDialog));
        }

        var dialogContext = CreateChildContext(outerDc);
        await dialogContext.PromptAsync(nameof(TextPrompt), new PromptOptions
        {
            Prompt = MessageFactory.Text("Begin root dialog")
        });

        return await base.BeginDialogAsync(outerDc, options, cancellationToken);
    }

    protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
    {
        if(innerDc.ActiveDialog != null)
        {
            return await innerDc.ContinueDialogAsync();
        }
        return await base.OnContinueDialogAsync(innerDc, cancellationToken);
    }

    public override async Task<DialogTurnResult> ResumeDialogAsync(DialogContext outerDc, DialogReason reason, object result = null, CancellationToken cancellationToken = default)
    {
        recursions = Convert.ToInt32(result);

        await outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions
        {
            Prompt = MessageFactory.Text($"Resume root dialog with recursion value {recursions}")
        });

        return await outerDc.BeginDialogAsync(nameof(WaterfallDialog));
    }
}

SetupDialog.cs:

public class SetupDialog : ComponentDialog
{
    public SetupDialog()
        : base(nameof(SetupDialog))
    {
        AddDialog(new TextPrompt(nameof(TextPrompt)));
    }

    public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default)
    {
        if (!int.TryParse(outerDc.Context.Activity.Text, out var recursions))
        {
            return outerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
            {
                Prompt = MessageFactory.Text($"{outerDc.Context.Activity.Text} konnte nicht in eine Zahl konvertiert werden.")
            });
        }

        return outerDc.EndDialogAsync(recursions);
    }

    public override Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default)
    {
        return base.BeginDialogAsync(outerDc, options, cancellationToken);
    }

    protected override Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default)
    {
        return innerDc.PromptAsync(nameof(TextPrompt), new PromptOptions()
        {
            Prompt = MessageFactory.Text("Wie viel rekursionen sollen erstellt werden?")
        });
    }
}

DialogBot.cs:

public class DialogBot<T> : ActivityHandler
    where T : Dialog
{
    protected readonly DialogSet Dialogs;
    protected readonly BotState ConversationState;
    protected readonly BotState UserState;
    protected readonly ILogger Logger;

    public DialogBot(ConversationState conversationState, UserState userState, IEnumerable<Dialog> dialogs, ILogger<DialogBot<T>> logger)
    {
        ConversationState = conversationState;
        UserState = userState;
        Logger = logger;

        Dialogs = new DialogSet(conversationState.CreateProperty<DialogState>(nameof(DialogState)));
        foreach(var dialog in dialogs)
        {
            Dialogs.Add(dialog);
        }
    }

    public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        await base.OnTurnAsync(turnContext, cancellationToken);

        // Save any state changes that might have occured during the turn.
        await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
        await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
    }

    protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
    {
        Logger.LogInformation("Running dialog with Message Activity.");

        var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken).ConfigureAwait(false);

        if (dc.ActiveDialog != null)
        {
            await dc.ContinueDialogAsync();
        }
        else
        {
            // Run the Dialog with the new message Activity.
            await dc.BeginDialogAsync(typeof(T).Name, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
        }
    }
}

在 IEnumerable 中有两个(RootDialog 和 SetupDialog)将两个对话框都放入 BotDialogContext 和 DialogSet