Search code examples
c#botframework

Botframework v4: Call parent dialog from child dialog without stackoverflow exception


According to this i think it is possible now to call the parent dialog from a child dialog. Before i can't do that because it will cause a Stack overflow exception. I've already updated to SDK 4.3 does anyone know how to implement this changes?

Main dialog calls dialog A.

WaterfallStep[] waterfallSteps = new WaterfallStep[]
     {
        FirstStepAsync,
        SecondStepAsync,
        ThirdStepAsync,
     };
    AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
    AddDialog(new DialogA(DialogAId));

  return await stepContext.BeginDialogAsync(DialogAId, cancellationToken: cancellationToken);

Dialog A calls Dialog Achild

    WaterfallStep[] waterfallSteps = new WaterfallStep[]
     {
        FirstStepAsync,
        SecondStepAsync,
        ThirdStepAsync,
     };
    AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
    AddDialog(new DialogAchild(DialogAchildId));

   return await stepContext.BeginDialogAsync(DialogAchildId, cancellationToken: cancellationToken);

Dialog Achild calls MainDialog, but this will produce a Stack overflow exception.

WaterfallStep[] waterfallSteps = new WaterfallStep[]
     {
        FirstStepAsync,
        SecondStepAsync,
        ThirdStepAsync,
     };
    AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
    AddDialog(new MainDialog(MainDialogId));

  return await stepContext.BeginDialogAsync(MainDialogId, cancellationToken: cancellationToken);

Solution

  • Assuming you're adding the Dialogs in the constructor of your dialog, then you're producing an infinite loop ultimately resulting in the stack overflow you're describing.

    MainDialog --> DialogA --> DialogAchild --> MainDialog --> infinite loop
    

    The PR you're mentioning refers to a slightly different problem.

    One way to work around this is to remove the AddDialog method causing the final loop from the constructor. Instead, move the call to a method like AddDialogA() and call it only if needed.

    Based on your scenario, you could build a base dialog offering this kind of functionality.

    Here is a sample of an AuthenticatedDialog offering you to add an OnboardingDialog when necessary. Note that the Onboarding Dialog itself inherits from AuthenticatedDialog which would cause an infinite loop as well when you're not offloading the AddDialog() call.

    Abstracting this in base dialogs is nice, because it gives you a little API you can use. Consider naming your stuff like AddComponentDialog or UseComponentDialog. This way you're expressing your intend quite good and a potential reader knows initally that you're using re-usable components.

    AuthenticatedDialog.cs

    using Microsoft.Bot.Builder.Dialogs;
    using Microsoft.Bot.Schema;
    using Bot.Dialogs.Authentication;
    using Bot.Dialogs.Main;
    using Bot.Dialogs.Onboarding;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace Bot.Dialogs.Shared
    {
        public class AuthenticatedDialog : EnterpriseDialog
        {
            private BotStateAccessors _accessors;
            private BotServices _botServices;
    
            public AuthenticatedDialog(BotServices botServices, string dialogId, BotStateAccessors accessors) : base(botServices, dialogId)
            {
                _accessors = accessors;
                _botServices = botServices;
    
                AddDialog(new AuthenticationDialog("", accessors));
            }
    
            protected async Task<DialogTurnResult> AskForOnboardingAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken, object stepResult = null)
            {
                return await stepContext.BeginDialogAsync(nameof(OnboardingDialog), stepResult, cancellationToken);
            }
    
            protected void AddOnboardingDialog()
            {
                AddDialog(new OnboardingDialog(_botServices,_accessors));
            }
    
        }
    }
    

    DialogA.cs

    public class DialogA : AuthenticatedDialog
    {
    
            public DialogA(BotServices botServices, BotStateAccessors accessors) : base(botServices, nameof(DialogA), accessors)
            {
    
                var preferencesDispatchSteps = new WaterfallStep[]
                {
                    WaterfallStepA,
                    WaterfallStepB
                };
    
                AddDialog(new FooDialog);
                AddOnboardingDialog();
            }
    }