如何添加多个组件对话框?

How do I add multiple ComponentDialogs?

我创建了一个新的 ComponentDialog,方法是为它创建一个 class 扩展 ComponentDialog,如下所示:

public class GetPersonInfoDialog : ComponentDialog
{
        protected readonly ILogger Logger;

        public GetPersonInfoDialog(IConfiguration configuration, ILogger<GetPersonInfoDialog> logger)
            : base("get-person-info", configuration["ConnectionName"])
        { }
        // code ommitted
    }
}

然后我在Startup.cs中添加了它:

public void ConfigureServices(IServiceCollection services)
    {

        // ...

        services.AddSingleton<GreetingDialog>();
        services.AddTransient<IBot, AuthBot<GreetingDialog>>();

        // My new component dialog:
        services.AddSingleton<GetPersonInfoDialog>();
        services.AddTransient<IBot, AuthBot<GetPersonInfoDialog>>();
    }
}

但我注意到只有最后一个对话框被使用。 GreetingDialog 现在不再有效,说:

DialogContext.BeginDialogAsync(): A dialog with an id of 'greeting' wasn't found. The dialog must be included in the current or parent DialogSet. For example, if subclassing a ComponentDialog you can call AddDialog() within your constructor.

不过,我确实注意到,GetPersonInfo 对话框确实开始了,而 Greeting 对话框不再出现了。就像我只能使用一个或另一个。我注意到只有最后添加的 "transient" 被使用,就好像它覆盖了之前的瞬变。

如何在我的 Startup.cs 文件中添加 多组件对话框 ?还是我什至正确地解决了这个问题?我没有找到任何说明如何拥有多个 ComponentDialogs 的文档。

在Startup.cs

我将从您的 Startup.cs 文件开始,因为这是第一个问题所在,然后我会建议一个替代设计。

您使用以下代码块有效执行的操作是:

services.AddSingleton<GreetingDialog>();
services.AddTransient<IBot, AuthBot<GreetingDialog>>();

services.AddSingleton<GetPersonInfoDialog>();
services.AddTransient<IBot, AuthBot<GetPersonInfoDialog>>();
  1. 正在为您的机器人注册 GreetingDialog 的单个实例(基本上是静态的)。
  2. 正在将 IBot 接口注册到 return 类型为 GreetingDialog 的新 AuthBot 每次IBot已请求。
  3. 正在为您的机器人注册 GetPersonInfoDialog 的单个实例(基本上是静态的)。
  4. 注册 IBot 接口(再次)到 return 一个新的 AuthBot 类型 GetPersonInfoDialog 每次请求 IBot 时(这将覆盖在步骤 2 中注册)。

您可以阅读更多有关服务生命周期的内容 here

所以你真正想要的更像下面这样:

public void ConfigureServices(IServiceCollection services)
{
    // Other code

    // Register dialogs
    services.AddTransient<GreetingDialog>();
    services.AddTransient<GetPersonInfoDialog>();

    // Some more code

    // Configure bot
    services.AddTransient<IBot, DialogBot<GreetingDialog>>();
}

错误信息

DialogContext.BeginDialogAsync(): A dialog with an id of 'greeting' wasn't found. The dialog must be included in the current or parent DialogSet. For example, if subclassing a ComponentDialog you can call AddDialog() within your constructor.

此错误消息是因为您的 GetPersonInfoDialog 不知道您的 GreetingDialog(而且它不应该)。我认为这是一个运行时错误,因为我记得 运行 我自己遇到过类似的问题。因为你没有为你的 GetPersonInfoDialog class 提供完整的实现,所以我必须假设你在里面的某个地方试图做如下的事情:

dialogContext.BeginDialogAsync("greeting");

or

dialogContext.BeginDialogAsync(nameof(GreetingDialog));

根据 the documentation,第一个参数是要启动的对话框的 ID,此 ID 还用于从对话框堆栈中检索对话框。为了从另一个对话框中调用一个对话框,您需要将其添加到父对话框的 DialogSet。接受的方法是在父对话框的构造函数中添加一个调用,如下所示:

public ParentDialog(....)
    : base(nameof(ParentDialog)
{
    // Some code

    // Important part
    AddDialog(new ChildDialog(nameof(ChildDialog)));
}

这使用 Microsoft.Bot.Builder.Dialogs NuGet 包提供的 AddDialog 方法,并通过 ComponentDialog class.

公开

然后当你想显示 ChildDialog 你会调用:

dialogContext.BeginDialogAsync(nameof(ChildDialog));

在您的情况下,您可以将 ParentDialog 替换为 GetPersonInfoDialog,将 ChildDialog 替换为 GreetingDialog。由于您的 GreetingDialog 可能只被使用一次(它不是一个可以多次调用但参数不同的实用程序对话框 - 在这种情况下,您可能希望提供特定的 ID 而不是使用 nameof(GreetingDialog)) 可以使用 class 名称的字符串表示形式作为 DialogId,您可以在 AddDialog 调用中使用 "greeting",但您还必须更新 BeginDialogAsync 调用也使用 "greeting".


替代设计

由于我不认为您希望 GreetingDialogGetPersonInfoDialog 作为您的实际起点,因此我建议添加另一个名为 MainDialog 的对话框,它继承自 RouterDialog class(Microsoft.Bot.Builder.Solutions.Dialogs NuGet 包)。基于架构(虚拟助手模板)here,您的 MainDialog 会从您的 GreetingDialogGetPersonInfoDialog 中产生。

假设你的 GreetingDialog 只是一个单一的阶段,它向用户发送一张卡片或一些文本来欢迎他们,它可以完全被 OnStartAsync 方法取代,发送你的 card/message。让你的用户访问你的 GetPersonInfoDialog 将由 RouteAsync 方法 example here.

处理

您需要对现有项目进行更改以实现此连接(假设您保留 GreetingDialog):

  • Startup.cs 中为 GreetingDialogGetPersonInfoDialogMainDialog 添加临时注册。
  • 添加临时注册以映射 IBotAuthBot<MainDialog>
  • MainDialog 的构造函数中添加调用以添加子对话框 GreetingDialogGetPersonInfoDialog
  • MainDialogOnBeginDialogOnStartAsync开始你的GreetingDialog
  • MainDialogRouteAsync 中处理显示 GetPersonInfoDialog 之前的任何条件。
  • 我可能错过了一些额外的步骤。

有用的链接:


编辑

要在 OAuth sample 中实现您想要的,您可以执行以下操作:

LogoutDialog.cs中更改:

private async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))

protected virtual async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))

MainDialog.cs中添加:

protected override async Task<DialogTurnResult> InterruptAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
{
    if (innerDc.Context.Activity.Type == ActivityTypes.Message)
    {
        var text = innerDc.Context.Activity.Text.ToLowerInvariant();

        if (text == "check email")
        {
            //
            return innerDc.BeginDialogAsync(/*TODO*/);
        }
        else if (text == "check calender")
        {
            //
            return innerDc.BeginDialogAsync(/*TODO*/);
        }
        // etc


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

    return null;
}

使用 AddDialog 方法在 MainDialog 的构造函数中注册您的日历、电子邮件等对话框。

我强烈建议您考虑使用 Virtual Assistant Template. As it uses LUIS to determing the user's intent (check email, check calendar etc), then route them accordingly, the relevant code is in this method. Using LUIS to determine intents has the advantage of being able to tie multiple ways of asking the same thing to the same intent, so you're not relying on your users to explicitly type "check calendar", you can have "show me my calendar", "what is my availability for next Monday", "am I free this afternoon", "check if I have any appointments tomorrow" etc. In fact Microsoft has already built Skills 用于电子邮件,以及与虚拟助手模板一起使用的日历,应该很容易将登录代码移植到这个模板。