FormDialog RESET - 如何重新初始化上下文中可用的数据?

时间:2018-01-23 00:06:29

标签: c# botframework

我在下面有以下示例类,它从context.UserData启动FirstName和emailAddress的值。

当我调用表单时这很有效,所以我没有被要求使用FirstName或emailAddress,因为我总是在测试时设置它。

但是,如果我输入RESET,FormDialog会自动处理,我的调试器不会进入StartAsync(),我有一个断点。然后我被要求提供我的FirstName,即使它已经可用。

以下是我的对话片段: enter image description here

我是否在StartAsync()中正确初始化数据?即使调用RESET,我如何使它坚持下去?

我意识到的一个奇怪的事情是,如果我发表评论.Field(nameof(AdultOptionCount).Field(nameof(ChildOptionCount),那么RESET不会要求我提供我的名字。

以下是我使用的完整代码,它总是要求重置后的FirstName。

using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using Microsoft.Bot.Connector;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace MyDialogs
{
[Serializable]
public class RootDialog : IDialog<object>
{
    public RootDialog()
    {
    }

    public async Task StartAsync(IDialogContext context)
    {
        //This is a test so I am filling up CustomerAccount data here
        CustomerAccount ca = new CustomerAccount();
        ca.FirstName = "Oyen";
        ca.Email = "oyen@email.com";
        context.UserData.SetValue<CustomerAccount>("CustomerAccount", ca);

        context.Wait(this.MessageReceivedAsync);
    }

    private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
    {
        //this message is always ignored
        var message = await result;

        await context.PostAsync("Hello there! Let's get started.");

        //a bunch of questions are usually asked here to determine which LUIS Dialog I will call because I have several
        //right now, let's just go straight to the Application dialog

        var app = new MyApplication();
        context.Call(app, ApplicationResumeAfter);
    }

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

        //do nothing else in this test
        context.Done(true);
    }
}

[Serializable]
public class CustomerAccount
{
    public string FirstName { get; set; }
    public string Email { get; set; }
}


[Serializable]
public class MyApplication : IDialog<string>
{
    public async Task StartAsync(IDialogContext context)
    {
        context.Call(new ApplyForm(), CompleteApplication);
    }

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

        context.Done("SUCCESSFUL");
    }
}

[Serializable]
public class ApplyForm : IDialog<ApplyForm>
{
    public async Task StartAsync(IDialogContext context)
    {
        var state = new ApplyForm();

        CustomerAccount CustomerAccount;
        if (context.UserData.TryGetValue<CustomerAccount>("CustomerAccount", out CustomerAccount))
        {
            state.FirstName = CustomerAccount.FirstName;
            state.EmailAddress = CustomerAccount.Email;
        }

        var form = new FormDialog<ApplyForm>(
            state,
            BuildForm,
           FormOptions.PromptInStart);

        context.Call(form, this.AfterBuildForm);
    }

    [Prompt("What is your **{&}**?")]
    public string FirstName { get; set; }

    [Prompt("What is your **{&}**?")]
    public string EmailAddress;

    [Numeric(0, 15)]
    [Prompt("How many adults will attend?")]
    public int AdultOptionCount { get; set; }

    [Numeric(0, 15)]
    [Prompt("How many children will attend?")]
    public int ChildOptionCount { get; set; }

    public static IForm<ApplyForm> BuildForm()
    {
        var form = new FormBuilder<ApplyForm>()
                .Field(nameof(FirstName),
                active: (state) =>
                {
                    return string.IsNullOrEmpty(state.FirstName);
                })
                .Field(nameof(EmailAddress),
                active: (state) =>
                {
                    return string.IsNullOrEmpty(state.EmailAddress);
                })
                .Field(nameof(AdultOptionCount),
                validate: async (state, value) =>
                {
                    var result = new ValidateResult { Value = value };
                    result.IsValid = IsInteger(value);
                    return result;
                })
                .Field(nameof(ChildOptionCount),
                validate: async (state, value) =>
                {
                    var result = new ValidateResult { Value = value };
                    result.IsValid = IsInteger(value);
                    return result;
                })
                .Build();

        return (IForm<ApplyForm>)form;
    }

    private async Task AfterBuildForm(IDialogContext context, IAwaitable<ApplyForm> result)
    {
        context.Done(result);
    }

    private static bool IsInteger(object value)
    {
        try
        {
            var s = value.ToString();
            int n;
            if (int.TryParse(s, out n))
                return true;
        }
        catch (Exception ex)
        {
            throw ex;
        }
        return false;
    }
}

}

1 个答案:

答案 0 :(得分:1)

感谢您的演示,我最后重现了您的问题并理解问题是在reset之后,如果第一个字段上有active条件,它将忽略它并仍然提示第一个字段领域。

输入StartAsync时,实际再次调用reset方法,因此您已成功设置默认值。这可以通过在完成整个流程之后检查结果来证明,即使第一个字段再次被询问并且用户输入该字段的另一个值,它仍然将提交StartAsync中获得的预设值。作为最终结果。

所以为了避免这个问题,我只能建议暂时不要在FormFlow的第一个字段中提示有条件激活的字段,这意味着有很多解决方法可以解决这个问题,但解决方案基于这个想法似乎并不优雅。

例如,以下是一种解决方法:根据预设FormFlow使用不同的CustomerAccount

public async Task StartAsync(IDialogContext context)
{
    var state = new ApplyForm();

    CustomerAccount customerAccount;
    FormDialog<ApplyForm> form;
    if (context.UserData.TryGetValue<CustomerAccount>("CustomerAccount", out customerAccount))
    {
        state.FirstName = customerAccount.FirstName;
        state.EmailAddress = customerAccount.Email;

        form = new FormDialog<ApplyForm>(
            state,
            BuildForm2,
            FormOptions.PromptInStart);
    }
    else
    {
        form = new FormDialog<ApplyForm>(
              state,
              BuildForm1,
              FormOptions.PromptInStart);
    }
    context.Call(form, this.AfterBuildForm);
}

[Prompt("What is your **{&}**?")]
public string FirstName { get; set; }

[Prompt("What is your **{&}**?")]
public string EmailAddress { get; set; }

[Numeric(0, 15)]
[Prompt("How many adults will attend?")]
public int AdultOptionCount { get; set; }

[Numeric(0, 15)]
[Prompt("How many children will attend?")]
public int ChildOptionCount { get; set; }

public static IForm<ApplyForm> BuildForm1()
{
    var form = new FormBuilder<ApplyForm>()
        .Field(nameof(FirstName))
        .Field(nameof(EmailAddress))
        .Field(nameof(AdultOptionCount),
            validate: async (state, value) =>
            {
                var result = new ValidateResult { Value = value };
                result.IsValid = IsInteger(value);
                return result;
            })
            .Field(nameof(ChildOptionCount),
            validate: async (state, value) =>
            {
                var result = new ValidateResult { Value = value };
                result.IsValid = IsInteger(value);
                return result;
            })
            .Build();

    return (IForm<ApplyForm>)form;
}

public static IForm<ApplyForm> BuildForm2()
{
    var form = new FormBuilder<ApplyForm>()
            .Field(nameof(AdultOptionCount),
            validate: async (state, value) =>
            {
                var result = new ValidateResult { Value = value };
                result.IsValid = IsInteger(value);
                return result;
            })
            .Field(nameof(ChildOptionCount),
            validate: async (state, value) =>
            {
                var result = new ValidateResult { Value = value };
                result.IsValid = IsInteger(value);
                return result;
            })
            .Build();

    return (IForm<ApplyForm>)form;
}

//other code goes here

这种解决方法是不合理的,因为它在构建active时放弃了Field FormFlow条件的使用。

与此同时,我已提交此问题,如果我收到任何新信息,我会更新我的答案。感谢您分享您的演示。