I can't figure out how to do the a very simple thing in MS Bot Framework: allow the user to break out of any conversation, leave the current dialogs and return to the main menu by typing "quit", "exit" or "start over".
Here's the way my main conversation is set up:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
try
{
if (activity.Type == ActivityTypes.Message)
{
UserActivityLogger.LogUserBehaviour(activity);
if (activity.Text.ToLower() == "start over")
{
//Do something here, but I don't have the IDialogContext here!
}
BotUtils.SendTyping(activity); //send "typing" indicator upon each message received
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
{
HandleSystemMessage(activity);
}
}
I know how to terminate a dialog with context.Done<DialogType>(this);
, but in this method, I do not have access to the IDialogContext object, so I cannot call .Done()
.
Is there any other way to terminate the whole dialog stack when the user types a certain message, other than adding a check for that in each step of all dialogs?
Posted bounty:
I need a way to terminate all IDialog
s without using the outrageous hack that I've posted here (which deletes all user data, which I need, e.g. user settings and preferences).
Basically, when the user types "quit" or "exit", I need to exit whatever IDialog
is currently in progress and return to the fresh state, as if the user has just initiated a conversation.
I need to be able to do this from MessageController.cs,
where I still do not have access to IDialogContext
. The only useful data I seem to have there is the Activity
object. I will be happy if someone points out to other ways to do that.
Another way to approach this is find some other way to check for the "exit" and "quit" keywords at some other place of the bot, rather than in the Post method.
But it shouldn't be a check that is done at every single step of the IDialog
, because that's too much code and not even always possible (when using PromptDialog
, I have no access to the text that the user typed).
Two possible ways that I didn't explore:
IDialog
s, start a new conversation
with the user (new ConversationId
) IDialogStack
object and do something with it to manage the dialog stack.The Microsoft docs are silent on this object so I have no idea how to get it. I do not use the Chain
object that allows .Switch()
anywhere in the bot, but if you think it can be rewritten to use it, it can be one of the ways to solve this too. However, I haven't found how to do branching between various types of dialogs (FormFlow
and the ordinary IDialog
) which in turn call their own child dialogs etc.
From my understanding of your question, what you want to achieve is to reset the dialog stack without completely destroy the bot state.
BotDataStore > BotData > DialogStack
Knowing FACTS from above, my solution will be
// in Global.asax.cs
var builder = new ContainerBuilder();
builder.RegisterModule(new DialogModule());
builder.RegisterModule(new ReflectionSurrogateModule());
builder.RegisterModule(new DialogModule_MakeRoot());
var config = GlobalConfiguration.Configuration;
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(config);
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
private static ILifetimeScope Container
{
get
{
var config = GlobalConfiguration.Configuration;
var resolver = (AutofacWebApiDependencyResolver)config.DependencyResolver;
return resolver.Container;
}
}
using (var scope = DialogModule.BeginLifetimeScope(Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(default(CancellationToken));
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
await botData.FlushAsync(default(CancellationToken));
}
Hope it helps.
Thanks to @ejadib to point out, Container is already being exposed in conversation class.
We can remove step 2 in the answer above, in the end the code will look like
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(default(CancellationToken));
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
await botData.FlushAsync(default(CancellationToken));
}