Botframework v4:如何简化此瀑布对话框?

时间:2019-01-06 05:08:47

标签: c# botframework

我有这段代码,但是我认为它过于复杂并且可以简化。 如果用户在不重新启动整个对话框的情况下键入“ back”,是否还可以返回到spefici Waterfall步骤?我是新来的,因为它是新的,所以很难找到关于botframework v4的指南或在线课程。任何帮助将不胜感激,谢谢!

  public GetNameAndAgeDialog(string dialogId, IEnumerable<WaterfallStep> steps = null) : base(dialogId, steps)
    {
        var name = "";
        var age = "";

        AddStep(async (stepContext, cancellationToken) =>
        {
            return await stepContext.PromptAsync("textPrompt",
                new PromptOptions
                {
                    Prompt = stepContext.Context.Activity.CreateReply("What's your name?")
                });
        });

        AddStep(async (stepContext, cancellationToken) =>
        {
            name = stepContext.Result.ToString();

            return await stepContext.PromptAsync("numberPrompt",
                new PromptOptions
                {
                    Prompt = stepContext.Context.Activity.CreateReply($"Hi {name}, How old are you ?")
                });
        });

        AddStep(async (stepContext, cancellationToken) =>
        {
            age= stepContext.Result.ToString();

            return await stepContext.PromptAsync("confirmPrompt",
              new PromptOptions
              {
                  Prompt = stepContext.Context.Activity.CreateReply($"Got it you're {name}, age {age}. {Environment.NewLine}Is this correct?"),
                  Choices = new[] {new Choice {Value = "Yes"},
                                   new Choice {Value = "No"},
                   }.ToList()
              });

        });

        AddStep(async (stepContext, cancellationToken) =>
        {
            var result = (stepContext.Result as FoundChoice).Value;

            if(result == "Yes" || result == "yes" || result == "Yeah" || result == "Correct" || result == "correct")
            {
                var state = await (stepContext.Context.TurnState["FPBotAccessors"] as FPBotAccessors).FPBotStateAccessor.GetAsync(stepContext.Context);
                state.Name = name;
                state.Age = int.Parse(age);

                return await stepContext.BeginDialogAsync(MainDialog.Id, cancellationToken);
            }
            else
            {
                //restart the dialog
                return await stepContext.ReplaceDialogAsync(GetNameAndAgeDialog.Id);
            }

        });

    }

    public static string Id => "GetNameAndAgeDialog";
    public static GetNameAndAgeDialog Instance { get; } = new GetNameAndAgeDialog(Id);
}

这是我的访问者代码:

    public class FPBotAccessors
{
    public FPBotAccessors(ConversationState conversationState)
    {
        ConversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
    }

    public static string FPBotAccessorName { get; } = $"{nameof(FPBotAccessors)}.FPBotState";
    public IStatePropertyAccessor<FPBotState> FPBotStateAccessor { get; internal set; }

    public static string DialogStateAccessorName { get; } = $"{nameof(FPBotAccessors)}.DialogState";
    public IStatePropertyAccessor<DialogState> DialogStateAccessor { get; internal set; }
    public ConversationState ConversationState { get; }
    //
    public static string ConversationFlowName { get; } = "ConversationFlow";
    public IStatePropertyAccessor<ConversationFlow> ConversationFlowAccessor { get; set; }
}

1 个答案:

答案 0 :(得分:3)

因此,您的代码有很多问题,您可以做一些改进来改善它。

对话框中的状态

首先,让我们从关闭构造函数中的局部变量并从代表步骤的闭包中访问局部变量开始。现在这种“有效”,但最终存在缺陷。最初的缺陷会有所不同,具体取决于您实例化GetNameAndAgeDialog对话框所采用的方法。

如果您将其作为单例使用,则意味着用户与您的机器人之间的所有活动对话都将通过该实例,并且您将遇到并发问题,即两个用户同时与该机器人对话将它们的值存储到相同的内存(这些变量)中,并逐步访问彼此的数据。

根据您要遵循的示例,也有可能在每个回合中都实例化GetNameAndAgeDialog。这意味着在每次对话时,这些变量都将初始化为一个空字符串,并且您将失去对原始值的跟踪。

最终,无论使用哪种实例化方法,无论采用哪种扩展方式,该方法最终都将存在缺陷,因为充其量您的状态将固定在单个服务器实例上,并且如果在{ {1}},而下一轮对话在ServerA进行,则ServerM将没有前一轮的值。

好的,很明显,您需要使用某种适当的状态管理机制来存储它们。显然,您已经对使用ServerM(无论是对话还是用户范围)有所了解,因为您已经在使用状态属性访问器,但是存储您在多回合中收集的值可能为时过早提示进入更永久的地方,直到您完成收集过程。幸运的是,对话框本身存储在状态中,您可以在为BotState设置状态属性访问器时弄清楚它们的状态,因此可以提供一种临时持久性机制,该机制与对话框栈中每个对话框的生命周期相关联。使用此状态尚不明显或尚未很好地记录(尚未),但是DialogState实际上更进一步,并通过其WaterfallDialog伴随类公开了第一类Values集合,该集合被馈送到每个步骤中。这意味着瀑布流的每个步骤都可以将值添加到WaterfallStepContext集合中,并访问以前的步骤可能已放入其中的值。在标题为Create advanced conversation flow using branches and loops的文档页面上有一个很好的示例。

没有充分利用提示

  • 您正在使用Values作为名称,这很完美,并且您会从中获得一个字符串并进行设置。尽管您可能要考虑在其上扔一个验证器,以确保获得的东西看起来像名称,而不是仅仅允许任何旧值。
  • 您似乎在使用TextPrompt作为年龄(至少以NumberPrompt<T>为名),但是随后您"numberPrompt" .ToString()并最终做了{ {1}}在最后一步。使用step.Result可以保证您获得int.Parse,并且可以(应该)直接使用该值,而不必将其返回为字符串,然后稍后再次对其进行解析。
  • 您有一个名为NumberPrompt<int>的提示,但它似乎不是实际的int,因为您正在完成所有"confirmPrompt"的工作和正值检测(例如检查表示“是”)。如果您实际使用ConfirmPrompt ,它将为您完成所有这一切,其结果将是Choice,您可以根据自己的逻辑轻松对其进行测试。 li>

次要东西

  • 当前您正在使用ConfirmPrompt来创建活动。很好,但是long绕且不必要。我强烈建议您仅使用bool API。
  • 我将始终确保将stepContext.Context.Activity.CreateReply传递给所有采用它的MessageFactory API……这是一个好习惯。
  • 您的最后一个步骤是,如果CancellationToken不确定详细信息,则重新启动,或者如果XXXAsync确认详细信息,则启动。以GetNameAndAgeDialog重新启动真是太棒了,这是正确的方法!我只是想指出,通过使用MainDialog启动ReplaceDialogAsync意味着在对话的整个生命周期中,您实际上将BeginDialogAsync留在了堆栈的底部。没什么大不了的,但是考虑到您可能永远也不会弹出堆栈,我建议您也使用MainDialog来启动GetNameAndAgeDialog

重构代码

以下是使用以上所有建议重写的代码:

ReplaceDialogAsync
  

如果用户在不重新启动整个对话框的情况下键入“ back”,是否还可以返回到spefici Waterfall步骤?

不,今天没有办法做到这一点。在与团队进行内部讨论时提出了这个话题,但尚未做出任何决定。如果您认为这是一个有用的功能,请please submit an issue over on GitHub,我们可以看看它是否获得了足够的动力来添加该功能。