In the previous botframework article I covered the different types of responses available for the botframework. This article is going to touch on the Dialog
and persisting information between subsequent messages.
So what’s a Dialog?
Dialogs can call child dialogs or send messages to a user. Dialogs are suspended when waiting for a message from the user to the bot. Dialogs are resumed when the bot receives a message from the user.
To create a Dialog, you must implement the IDialog<T>
interface and make your dialog class serializable, something like this:
[Serializable]
public class HelloDialog : IDialog<object>
{
protected int count = 1;
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync($"You're new!");
context.Wait(MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
await context.PostAsync($"I've seen you {count++} times now, {message.From.Name}");
context.Wait(MessageReceivedAsync);
}
}
What I’m trying to show here is how the dialog handles an initial message from a new user, versus the continuation of a conversation with that same user.
In order to wire the dialog up to the MessagesController
you need to reference Microsoft.Bot.Builder.Dialogs
and use the Conversation.SendAsync()
method:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
// needs a reference to Microsoft.Bot.Builder.Dialogs
await Conversation.SendAsync(activity, () => new HelloDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
With this implemented you should see something similar to the following conversation if you use the bot framework channel emulator:
The magic is in context.Wait
:
Suspend the current dialog until the user has sent a message to the bot
So what we’ve done here is implement IDialog<T>
’s StartAsync
method, then tell the bot to hang around, waiting for another message in the same conversation from the same user using context.Wait
.
Well, that sounds like stateful communication, right? Which sucks, since it inhibits scaling by requiring sticky sessions.
A-ha! But not quite, in this case; in something that hopefully doesn’t end up looking like viewstate
, we have DialogState
encoded in the context
’s PrivateConversationData
’s Bag
:
This will allow state to be carried around and the conversation continued without the user needing to be tied to a single instance of the bot. Nice.
Let’s play with that a bit:
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
int heresavalue = 1;
if (!context.PrivateConversationData.TryGetValue<int>("countthingy", out heresavalue)) { heresavalue = 1; }
var message = await argument;
await context.PostAsync($"I've seen you {count++} times now, {message.From.Name} and also {heresavalue}");
context.Wait(MessageReceivedAsync);
context.PrivateConversationData.SetValue<int>("countthingy", heresavalue * 2);
}
Now we’re setting a variable heresavalue
and doubling it for each message in the same conversation, just to show that the value is being persisted; after a few messages, it looks like this:
context.PrivateConversationData.SetValue<T>
and context.PrivateConversationData.TryGetValue<T>
mean we can push info into the databag and get it back again within the context of a one on one chat.
The same is also true for context.ConversationData
, where the conversation is a group (i.e., the bot and more than one other user), instead of one-to-one.
Skype
What’s quite cool is how your Skype name is pulled out from message.From.Name
when the bot is deployed to Skype
Summary
Hopefully this short article gave you an idea of how to implement a Dialog
and how you can persist state within a conversation.
Hello,
Is there anyway to maintain some objects at the MessageController class? I am using LUIS and I want to fetch customer details when they send me the first message and then after I would like to skip the fetching function because the customer is already instantiated. However, I would need to do it at the MessageController level to keep one copy among all my dialogs.