A chatbot provides an electronic exchange between a user and a computer. For example, each exchange might consist of a question from the user and an answer from the computer or a question from the computer and an answer from the user.

As with most interactions that take place across the Internet, a chatbot is, by default, stateless - meaning it remembers nothing from one exchange to the next.

Often, we would like to remember answers from one question to the next or even from one conversation to the next. If the bot asks a person's name, that name could be used to respond to a question asked later in the same conversation; or even in a different conversation.

This is why we implement State in our bots.

There are two key issues when managing state:

  • What is the scope of the data?
  • Where is stateful information stored?

In this article, we will focus on the first question and store all state data in memory.

What is the scope of the data?

The first question we ask ourselves is: When we save the data, when and where will it be seen?

We have 3 options, corresponding to 3 properties of the context (IDialogContext Interface)

  • ConversationData. Data stored here is available to anyone within the current conversation.
  • PrivateConversationData. Data stored here is available to the current user within the current conversation.
  • UserData. Data stored here is available to the current user in any conversation. This means he/she can come back the next day and the bot will remember his/her data.

Each of the 3 options above is a property of the IDialogContext interface. The context object, which is passed into the MessageReceivedAsync, implements this interface, so we can read and write to the appropriate storage by reading and writing to objects stored in context.

Below is a simple example that reads to and writes from the three different storage options.

private async Task MessageReceivedAsync(IDialogContext context, IAwaitable result)
{
	var message = await result;
	var channelId = message.ChannelId;
	var conversationId = message.Conversation.Id;
	var userId = message.From.Id;

	var currentTime = DateTime.Now;

	DateTime firstLoginTimeThisConversationAnyUser = DateTime.MinValue;

	context.ConversationData.TryGetValue("FirstLoginTime", out firstLoginTimeThisConversationAnyUser);
	if (firstLoginTimeThisConversationAnyUser == System.DateTime.MinValue)
	{
		firstLoginTimeThisConversationAnyUser = System.DateTime.Now;
		context.ConversationData.SetValue("FirstLoginTime", firstLoginTimeThisConversationAnyUser);
	}

	DateTime firstLoginTimeEverForCurrentUser = DateTime.MinValue;
	context.UserData.TryGetValue("FirstLoginTime", out firstLoginTimeEverForCurrentUser);
	if (firstLoginTimeEverForCurrentUser == System.DateTime.MinValue)
	{
		firstLoginTimeEverForCurrentUser = currentTime;
		context.UserData.SetValue("FirstLoginTime", firstLoginTimeEverForCurrentUser);
	}

	DateTime firstLoginTimeCurrentConversationCurrentUser = DateTime.MinValue;
	context.PrivateConversationData.TryGetValue("FirstLoginTime", out firstLoginTimeCurrentConversationCurrentUser);
	if (firstLoginTimeCurrentConversationCurrentUser == System.DateTime.MinValue)
	{
		firstLoginTimeCurrentConversationCurrentUser = currentTime;
		context.PrivateConversationData.SetValue("FirstLoginTime", firstLoginTimeCurrentConversationCurrentUser);
	}

	var output = $"User: {userId}\nConversation: {conversationId}\nChannel: {channelId}\n\n";
	output += $"A user logged into this converstaion at {firstLoginTimeThisConversationAnyUser}.\n";
	output += $"You first logged into any conversation at {firstLoginTimeEverForCurrentUser}.\n";
	output += $"You first logged into this conversation at {firstLoginTimeCurrentConversationCurrentUser}.";
	await context.PostAsync(output);

	context.Wait(MessageReceivedAsync);
}
  

  Listing 1

Notice that we are checking each time to see if any new data is returned from a name-value pair ("FirstLoginTime"). If nothing is stored in a value, it will return DateTime.MinValue, which signifies an empty date. In this case, we set the value to the current time.

You can test this with the Bot Framework Emulator (available here and described here).

You can download a Visual Studio Solution that uses the code in Listing 1 from the SavingStateDemo of the Bot-Framework-Demos on my GitHub page.

Launch your bot from Visual Studio and connect to it with the Emulator. Type any message into the textbox and press [ENTER], as shown in Fig. 1.

Fig01-BotEmulator1
Fig. 1

You should see information about the user, channel, and conversation; along with the value just stored for the when users (including you) logged into this (or another) conversation. Notice all 3 values are the same because this is your first conversation and you are the first user in this conversation.

Again, type any message in the textbox and press [ENTER]. The values have not changed because they were set as stateful data last time (Fig. 2)

Fig02-BotEmulator2
Fig. 2

You can start a new conversation by clicking the ENPOINT on the left, opening a new tab. Type something into this tab's textbox and press [ENTER] (Fig. 3)

Fig03-BotEmulatorNewConversation
Fig. 3

Notice that the user is the same, but the Conversation ID is different.

Therefore, the bot updated the ConversationData and the PrivateConversationData values, but not the UserData value.

In this article, I showed you how to save stateful data from a conversation using the Microsoft Bot Framework.