3
votes

I am using Bot Framework SDK v-4.x based on .Net Core. I have couple of dialogs created which I am able to flow through using DialogContext.Begin, DialogContext.End and DialogContext.Continue. Everything works fine, but now I want to implement a FormFlow in the middle of a conversation. I referred this link- https://docs.microsoft.com/en-us/azure/bot-service/dotnet/bot-builder-dotnet-formflow?view=azure-bot-service-3.0

I posted this on Github (https://github.com/MicrosoftDocs/bot-docs/issues/227) and based on the solution this is what I tried-

[Serializable]
public class HelpForm
{
    public string FullName { get; set; }
    public string EmailID { get; set; }
    public string Question { get; set; }
    public DateTime BestTimeToContact { get; set; }
    public List<Priority> Priority { get; set; }
    public static IForm<HelpForm> BuildForm()
    {
        return new FormBuilder<HelpForm>()
            .Message("Please fill out the details as prompted.")
            .Build();
    }
}

public enum Priority
{
    Low,
    Medium,
    High
}

In my OnTurn event of my bot, I am doing something like this-

await Microsoft.Bot.Builder.Classic.Dialogs.Conversation.SendAsync(context, () => FormDialog.FromForm(HelpForm.BuildForm)); //context is of type ITurnContext

This doesn't seem to work and I get a response in the emulator as

Sorry, my bot code is having an issue.

Also, this link- https://github.com/Microsoft/botbuilder-dotnet/wiki/Using-Classic-V3-Dialogs-with-V4-SDK says that Microsoft.Bot.Builder.Classic is not supported in .Net Core. Any help with this please?

Update

Based on Fei's comment, I got the exception details. System.Runtime.Serialization.SerializationException: Type 'System.RuntimeType' in Assembly 'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e' is not marked as serializable.. Although my HelpForm class is marked with Serializable.

Looking at the metadata for FormFlow class it is not marked with Serializable attribute.

enter image description here

Note sure if that is what the error is about.

3
Sorry, my bot code is having an issue. You can try to trace/check the detailed exception message via CatchExceptionMiddleware.Fei Han
@FeiHan I get error something like this- System.Runtime.Serialization.SerializationException: Type 'System.RuntimeType' in Assembly 'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e' is not marked as serializable.. Although my HelpForm class is marked with Serializable.Souvik Ghosh
Microsoft.Bot.Builder.Classic has been removed. FormFlow is not currently supported in v4. I've been working on a port here: github.com/EricDahlvang/Microsoft.Bot.Builder.FormFlow (not ready for release yet)Eric Dahlvang
There is an github issue for FormBuilder in V4 github.com/Microsoft/botbuilder-dotnet/issues/561sschoof
The Bot Builder Community released a FormFlow version for Bot Builder v4 last week. Read meDana V

3 Answers

2
votes

Unfortunately, as you've discovered, FormFlow doesn't work with v4.

V4 comes with a very structured waterfall dialog however, and this can be used to implement a functionality similar to FormFlow. The sample here shows how to use waterfall dialogs to ask a user a series of questions such as name, age and address. The Prompt dialog class in particular is suited for this type of task. There is a confirm prompt included that you can implement at the end of the dialog to mimic FormFlow's confirmation.

2
votes

Bot Builder V3 FormFlow dialogs can now be used in the context of a V4 bot by using the recently released Bot.Builder.Community.Dialogs.FormFlow library.

Your HelpForm can be added to a V4 DialogSet in the same way as other V4 ComponentDialogs:

_dialogs.Add(FormDialog.FromForm(HelpForm.BuildForm));

Here is a more complete example:

Accessors:

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

    public ConversationState ConversationState { get; }
    public IStatePropertyAccessor<DialogState> ConversationDialogState { get; set; }
}

Startup.cs ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
    services.AddBot<TestEchoBotBot>(options =>
    {
        IStorage dataStore = new MemoryStorage();
        options.State.Add(new ConversationState(dataStore));
        options.Middleware.Add(new AutoSaveStateMiddleware(options.State.ToArray()));

        var secretKey = Configuration.GetSection("botFileSecret")?.Value;
        var botFilePath = Configuration.GetSection("botFilePath")?.Value;

        // Loads .bot configuration file and adds a singleton that your Bot can access through dependency injection.
        var botConfig = BotConfiguration.Load(botFilePath ?? @".\TestEchoBot.bot", secretKey);
        services.AddSingleton(sp => botConfig ?? throw new InvalidOperationException($"The .bot config file could not be loaded. ({botConfig})"));

        // Retrieve current endpoint.
        var environment = _isProduction ? "production" : "development";
        var service = botConfig.Services.Where(s => s.Type == "endpoint" && s.Name == environment).FirstOrDefault();
        if (!(service is EndpointService endpointService))
        {
            throw new InvalidOperationException($"The .bot file does not contain an endpoint with name '{environment}'.");
        }

        options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);
    });

    services.AddSingleton(sp =>
    {
        var options = sp.GetRequiredService<IOptions<BotFrameworkOptions>>().Value;
        var conversationState = options.State.OfType<ConversationState>().FirstOrDefault();
        var accessors = new TestEchoBotAccessors(conversationState)
        {
            ConversationDialogState = conversationState.CreateProperty<DialogState>("DialogState")
        };
        return accessors;
    });
}

Bot code:

public class TestEchoBotBot : IBot
{
    private readonly TestEchoBotAccessors _accessors;
    private DialogSet _dialogs;

    public TestEchoBotBot(TestEchoBotAccessors accessors, ILoggerFactory loggerFactory)
    {
        if (loggerFactory == null)
        {
            throw new System.ArgumentNullException(nameof(loggerFactory));
        }

        _dialogs = new DialogSet(accessors.ConversationDialogState);
        _dialogs.Add(FormDialog.FromForm(HelpForm.BuildForm));
        _accessors = accessors ?? throw new System.ArgumentNullException(nameof(accessors));
    }

    public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        if (turnContext.Activity.Type == ActivityTypes.Message)
        {
            var dialogContext = await _dialogs.CreateContextAsync(turnContext, cancellationToken);
            if (turnContext.Activity.Text?.ToUpper() == "HELP")
            {
                await dialogContext.BeginDialogAsync(typeof(HelpForm).Name, null, cancellationToken);
            }
            else
            {
                var dialogResult = await dialogContext.ContinueDialogAsync(cancellationToken);
                if ((dialogResult.Status == DialogTurnStatus.Cancelled || dialogResult.Status == DialogTurnStatus.Empty))
                {
                    var responseMessage = $"You sent '{turnContext.Activity.Text}'\n";
                    await turnContext.SendActivityAsync(responseMessage);
                }
            }                
        }
    }
}