Adaptive Dialogs: Using a ChoiceInput inside a Foreach creates an infinite loop
See original GitHub issueVersion
4.14.1
Describe the bug
When creating an AdaptiveDialog which has a Foreach action containing a ChoiceInput, we have found that the Foreach never gets beyond the second item in the array and loops infinitely
To Reproduce
Example dialog:
var searchStart = new AdaptiveDialog("searchStart") { Generator = new TemplateEngineLanguageGenerator() };
var choices = new ChoiceSet();
choices.Add(new Choice("Red"));
choices.Add(new Choice("Green"));
choices.Add(new Choice("Blue"));
searchStart.Triggers.Add(new OnBeginDialog
{
Actions =
{
new CodeAction(async (dc,obj) =>
{
dc.State.SetValue("dialog.myArray", new string[]{"one", "two", "three"});
return await dc.EndDialogAsync();
}),
new Foreach()
{
ItemsProperty="dialog.myArray",
Actions = new List<Dialog>
{
new CodeAction(async (dc, obj) =>
{
var loopIndex = dc.State.GetValue<int>("$foreach.index");
await dc.Context.SendActivityAsync($"current index: {loopIndex}");
return await dc.EndDialogAsync();
}),
new ChoiceInput
{
Id = "mychoice",
DefaultValue = "${turn.Activity.Text}",
Property = "dialog.myColour",
Prompt = new ActivityTemplate("What is your favourite colour?"),
Choices = choices,
AlwaysPrompt = true,
},
new CodeAction(async (dc, obj) =>
{
var yourColour = dc.State.GetValue<object>("dialog.myColour");
await dc.Context.SendActivityAsync("You said:" + yourColour.ToString());
return await dc.EndDialogAsync();
})
}
},
new SendActivity("All done")
}
});
Result:
Additional context
We have been looking at the code and this appears to be becuase the Foreach class uses a local private ‘index’ int field to store the position in the array. This confused us as it was already being stored in state in ‘dialog.foreach.index’. What appears to be happening is that the Foreach is being re-instantiated for some reason and so index is being reset to 0 every time.
One option could be that you remove the local private index property and reuse ‘dialog.foreach.index’. We’d suggest changing NextItemAsync to be
protected virtual async Task<DialogTurnResult> NextItemAsync(DialogContext dc, CancellationToken cancellationToken = default)
{
// Get list information
var result = dc.State.GetValue<object>(this.ItemsProperty.GetValue(dc.State));
var list = ConvertToList(result);
var index = dc.State.GetValue<int>( Index.GetValue(dc.State), () => -1);
// Next item
if (list != null && ++index < list.Count)
{
// Persist index and value
dc.State.SetValue(Value.GetValue(dc.State), list[index][IterationValue]);
dc.State.SetValue(Index.GetValue(dc.State), list[index][IterationKey]);
// Start loop
return await this.BeginActionAsync(dc, 0, cancellationToken).ConfigureAwait(false);
}
else
{
// End of list has been reached, or the list is null
return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
}
}
and put this in BeginDialogAsync
dc.State.RemoveValue(Index.GetValue(dc.State));
Unless someone can provide another solution to us?
Thanks in advanced
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (1 by maintainers)
Top GitHub Comments
hi @j-melville Sorry that I could not reproduce the issue, but this problem does exist, while the action is broken and would be restored from some other “store” or “cache”, the value of the private value would lose its previous value. And, your fix solution is right! I just get a pr to fix this.
Additional thing, your adaptive card could be simplified to this:
The result:
Hope it helpful. Thanks!
Ahh that sounds like a good idea, thanks.