diff --git a/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Actions/BaseInvokeDialog.cs b/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Actions/BaseInvokeDialog.cs index d4a866afc3..d0c9426466 100644 --- a/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Actions/BaseInvokeDialog.cs +++ b/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/Actions/BaseInvokeDialog.cs @@ -98,17 +98,6 @@ protected virtual Dialog ResolveDialog(DialogContext dc) var se = new StringExpression($"={this.Dialog.ExpressionText}"); var dialogId = se.GetValue(dc.State) ?? throw new InvalidOperationException($"{this.Dialog.ToString()} not found."); var dialog = dc.FindDialog(dialogId); - - if (dialog == null) - { - var resourceExplorer = dc.Context.TurnState.Get(); - if (resourceExplorer != null) - { - dialog = resourceExplorer.LoadType($"{dialogId}.dialog"); - dc.Dialogs.Add(dialog); - } - } - return dialog; } diff --git a/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/AdaptiveDialog.cs b/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/AdaptiveDialog.cs index 41ccfc15ba..afd3aafca3 100644 --- a/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/AdaptiveDialog.cs +++ b/libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/AdaptiveDialog.cs @@ -383,6 +383,44 @@ public IEnumerable GetDependencies() yield break; } + /// + /// Finds a child dialog that was previously added to the container. + /// Uses DialogContext as fallback to gather the dialog from the . + /// + /// The ID of the dialog to lookup. + /// The dialog context where to find the dialog. + /// The Dialog if found; otherwise null. + /// + /// When the Dialog is gathered from the , + /// automatically will be loaded into the stack. + /// + public override Dialog FindDialog(string dialogId, DialogContext dc = null) + { + var dialog = FindDialog(dialogId); + if (dialog != null) + { + return dialog; + } + + if (dc == null) + { + throw new ArgumentNullException(nameof(dc)); + } + + var resourceExplorer = dc.Context.TurnState.Get(); + var resourceId = $"{dialogId}.dialog"; + var foundResource = resourceExplorer?.TryGetResource(resourceId, out _) ?? false; + if (!foundResource) + { + return null; + } + + dialog = resourceExplorer.LoadType(resourceId); + dialog.Id = dialogId; + dc.Dialogs.Add(dialog); + return dialog; + } + /// /// Gets the internal version string. /// @@ -680,20 +718,6 @@ protected async Task ContinueActionsAsync(DialogContext dc, ob var actionDC = CreateChildContext(actionContext); while (actionDC != null) { - if (actionDC.ActiveDialog != null) - { - var dialog = FindDialog(actionDC.ActiveDialog.Id); - if (dialog == null) - { - var resourceExplorer = actionDC.Context.TurnState.Get(); - if (resourceExplorer != null) - { - dialog = resourceExplorer.LoadType($"{actionDC.ActiveDialog.Id}.dialog"); - actionDC.Dialogs.Add(dialog); - } - } - } - // DEBUG: To debug step execution set a breakpoint on line below and add a watch // statement for actionContext.Actions. DialogTurnResult result; diff --git a/libraries/Microsoft.Bot.Builder.Dialogs/DialogContainer.cs b/libraries/Microsoft.Bot.Builder.Dialogs/DialogContainer.cs index 35a39ec0cd..58eb3c4cce 100644 --- a/libraries/Microsoft.Bot.Builder.Dialogs/DialogContainer.cs +++ b/libraries/Microsoft.Bot.Builder.Dialogs/DialogContainer.cs @@ -97,6 +97,17 @@ public virtual Dialog FindDialog(string dialogId) return this.Dialogs.Find(dialogId); } + /// + /// Finds a child dialog that was previously added to the container. Uses DialogContext as fallback to gather the dialog. + /// + /// The ID of the dialog to lookup. + /// The dialog context fallback where to find the dialog. + /// The Dialog if found; otherwise null. + public virtual Dialog FindDialog(string dialogId, DialogContext dc = null) + { + return Dialogs.Find(dialogId) ?? dc?.Dialogs?.Find(dialogId); + } + /// /// Called when an event has been raised, using `DialogContext.emitEvent()`, by either the current dialog or a dialog that the current dialog started. /// diff --git a/libraries/Microsoft.Bot.Builder.Dialogs/DialogContext.cs b/libraries/Microsoft.Bot.Builder.Dialogs/DialogContext.cs index cb8d49f0ca..0332955d1c 100644 --- a/libraries/Microsoft.Bot.Builder.Dialogs/DialogContext.cs +++ b/libraries/Microsoft.Bot.Builder.Dialogs/DialogContext.cs @@ -578,7 +578,19 @@ public Dialog FindDialog(string dialogId) if (this.Parent != null) { - return this.Parent.FindDialog(dialogId); + var dialog = Parent.FindDialog(dialogId); + + if (dialog != null) + { + return dialog; + } + + var parentDialog = Parent.ActiveDialog?.Id != null ? Parent.FindDialog(Parent.ActiveDialog.Id) : null; + if (parentDialog is DialogContainer) + { + dialog = (parentDialog as DialogContainer).FindDialog(dialogId, this); + return dialog; + } } return null; diff --git a/tests/Microsoft.Bot.Builder.Dialogs.Adaptive.Tests/AdaptiveDialogTests.cs b/tests/Microsoft.Bot.Builder.Dialogs.Adaptive.Tests/AdaptiveDialogTests.cs index b524784746..6cf975fe27 100644 --- a/tests/Microsoft.Bot.Builder.Dialogs.Adaptive.Tests/AdaptiveDialogTests.cs +++ b/tests/Microsoft.Bot.Builder.Dialogs.Adaptive.Tests/AdaptiveDialogTests.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Runtime.CompilerServices; using System.Linq; +using System.IO; using Microsoft.Bot.Builder.Dialogs.Adaptive.Actions; using Microsoft.Bot.Builder.Dialogs.Adaptive.Conditions; using Microsoft.Bot.Builder.Dialogs.Adaptive.Templates; @@ -22,6 +23,7 @@ using Microsoft.Bot.Builder.Dialogs.Adaptive.Testing.TestActions; using Microsoft.Bot.Builder.Dialogs.Adaptive.Generators; using Moq; +using Microsoft.Bot.Builder.Dialogs.Declarative.Resources; namespace Microsoft.Bot.Builder.Dialogs.Adaptive.Tests { @@ -1006,6 +1008,126 @@ public async Task AdaptiveDialog_ReplaceParentComplex_VerifyPostReplace() .StartTestAsync(); } + [Fact] + public async Task AdaptiveDialog_BeginDialog_With_ComponentDialog() + { + var storage = new MemoryStorage(); + var adapter = new TestAdapter() + .UseStorage(storage) + .UseBotState(new UserState(storage), new ConversationState(storage)); + + var rootDialog = new AdaptiveDialog("root") + { + AutoEndDialog = false, + Triggers = new List() + { + new OnBeginDialog() + { + Actions = new List() + { + new BeginDialog(nameof(ForeachItemsDialog)) + } + } + }, + }; + + var dialogManager = new DialogManager(rootDialog) + .UseResourceExplorer(_resourceExplorerFixture.ResourceExplorer); + dialogManager.Dialogs.Add(new ForeachItemsDialog(1)); + + await new TestFlow((TestAdapter)adapter, async (turnContext, cancellationToken) => + { + await dialogManager.OnTurnAsync(turnContext, cancellationToken); + }) + .SendConversationUpdate() + .Send("hi") + .AssertReply("Send me some text.") + .StartTestAsync(); + } + + [Fact] + public async Task AdaptiveDialog_LoadDialogFromProperty_With_BotRestart() + { + var storage = new MemoryStorage(); + var adapter = new TestAdapter() + .UseStorage(storage) + .UseBotState(new UserState(storage), new ConversationState(storage)); + + var rootDialog = new AdaptiveDialog("root") + { + AutoEndDialog = false, + Recognizer = new RegexRecognizer + { + Intents = new List + { + new IntentPattern + { + Intent = "Start", + Pattern = "start" + } + } + }, + Triggers = new List() + { + new OnIntent() + { + Intent = "Start", + Actions = new List() + { + new SetProperty + { + Property = "turn.dialogToStart", + Value = "AskNameDialog" + }, + new BeginDialog("=turn.dialogToStart") + } + }, + new OnDialogEvent(DialogEvents.VersionChanged) + { + Actions = new List() + { + new SetProperty + { + Property = "user.name", + Value = $"John Doe ({DialogEvents.VersionChanged})" + }, + } + } + }, + }; + + var resourceExplorer = new ResourceExplorer(); + var folderPath = Path.Combine(TestUtils.GetProjectPath(), "Tests", "ActionTests"); + resourceExplorer = resourceExplorer.AddFolder(folderPath, monitorChanges: false); + + var dialogManager = new DialogManager(rootDialog) + .UseResourceExplorer(resourceExplorer); + + await new TestFlow((TestAdapter)adapter, async (turnContext, cancellationToken) => + { + await dialogManager.OnTurnAsync(turnContext, cancellationToken); + }) + .Send("start") + .AssertReply("Hello, what is your name?") + .StartTestAsync(); + + // Simulate bot restart, maintaining storage information. + adapter = new TestAdapter() + .UseStorage(storage) + .UseBotState(new UserState(storage), new ConversationState(storage)); + + dialogManager = new DialogManager(rootDialog) + .UseResourceExplorer(resourceExplorer); + + await new TestFlow((TestAdapter)adapter, async (turnContext, cancellationToken) => + { + await dialogManager.OnTurnAsync(turnContext, cancellationToken); + }) + .Send("John Doe") + .AssertReply($"Hello John Doe ({DialogEvents.VersionChanged}), nice to meet you!") + .StartTestAsync(); + } + private static AdaptiveDialog CreateDialog(string custom) { return new AdaptiveDialog() diff --git a/tests/Microsoft.Bot.Builder.Dialogs.Adaptive.Tests/Tests/AdaptiveDialogTests/AdaptiveDialog_LoadDialogFromProperty.test.dialog b/tests/Microsoft.Bot.Builder.Dialogs.Adaptive.Tests/Tests/AdaptiveDialogTests/AdaptiveDialog_LoadDialogFromProperty.test.dialog index b8ea1361b7..03968a2f2c 100644 --- a/tests/Microsoft.Bot.Builder.Dialogs.Adaptive.Tests/Tests/AdaptiveDialogTests/AdaptiveDialog_LoadDialogFromProperty.test.dialog +++ b/tests/Microsoft.Bot.Builder.Dialogs.Adaptive.Tests/Tests/AdaptiveDialogTests/AdaptiveDialog_LoadDialogFromProperty.test.dialog @@ -5,9 +5,23 @@ "$kind": "Microsoft.AdaptiveDialog", "id": "outer", "autoEndDialog": false, + "recognizer": { + "$kind": "Microsoft.RegexRecognizer", + "intents": [ + { + "intent": "TellJokeDialog", + "pattern": "joke" + }, + { + "intent": "UnknownDialog", + "pattern": "unknown" + } + ] + }, "triggers": [ { - "$kind": "Microsoft.OnBeginDialog", + "$kind": "Microsoft.OnIntent", + "intent": "TellJokeDialog", "actions": [ { "$kind": "Microsoft.SetProperty", @@ -19,17 +33,40 @@ "dialog": "=turn.dialogToStart" } ] + }, + { + "$kind": "Microsoft.OnIntent", + "intent": "UnknownDialog", + "actions": [ + { + "$kind": "Microsoft.SetProperty", + "value": "UnknownDialog", + "property": "turn.dialogToStart" + }, + { + "$kind": "Microsoft.BeginDialog", + "dialog": "=turn.dialogToStart" + } + ] } ] }, "script": [ { "$kind": "Microsoft.Test.UserSays", - "text": "hi" + "text": "joke" }, { "$kind": "Microsoft.Test.AssertReply", "text": "Why did the chicken cross the road?" + }, + { + "$kind": "Microsoft.Test.UserSays", + "text": "unknown" + }, + { + "$kind": "Microsoft.Test.AssertReply", + "text": "Object reference not set to an instance of an object." } ] } \ No newline at end of file