Manage state data

Note

This topic applies to SDK v3 release. You can find the documentation for the latest version of the SDK v4 here.

The Bot Builder Framework enables your bot to store and retrieve state data that is associated with a user, a conversation, or a specific user within the context of a specific conversation. State data can be used for many purposes, such as determining where the prior conversation left off or simply greeting a returning user by name. If you store a user's preferences, you can use that information to customize the conversation the next time you chat. For example, you might alert the user to a news article about a topic that interests them, or alert a user when an appointment becomes available.

For testing and prototyping purposes, you can use the Bot Builder Framework's in-memory data storage. For production bots, you can implement your own storage adapter or use one of Azure Extensions. The Azure Extensions allow you to store your bot's state data in either Table Storage, CosmosDB, or SQL. This article will show you how to use the in-memory storage adapter to store your bot's state data.

Important

The Bot Framework State Service API is not recommended for production environments, and may be deprecated in a future release. It is recommended that you update your bot code to use the in-memory storage adapter for testing purposes or use one of the Azure Extensions for production bots.

In-memory data storage

In-memory data storage is intended for testing only. This storage is volatile and temporary. The data is cleared each time the bot is restarted. To use the in-memory storage for testing purposes, you will need to:

Install the following NuGet packages:

  • Microsoft.Bot.Builder.Azure
  • Autofac.WebApi2

In the Application_Start method, create a new instance of the in-memory storage, and register the new data store:

// Global.asax file

var store = new InMemoryDataStore();

Conversation.UpdateContainer(
           builder =>
           {
               builder.Register(c => store)
                         .Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
                         .AsSelf()
                         .SingleInstance();

               builder.Register(c => new CachingBotDataStore(store,
                          CachingBotDataStoreConsistencyPolicy
                          .ETagBasedConsistency))
                          .As<IBotDataStore<BotData>>()
                          .AsSelf()
                          .InstancePerLifetimeScope();


           });
GlobalConfiguration.Configure(WebApiConfig.Register);

You can use this method to set your own custom data storage or use any of the Azure Extensions.

Manage custom data storage

For performance and security reasons in the production environment, you may implement your own data storage or consider implementing one of the following data storage options:

  1. Manage state data with Cosmos DB

  2. Manage state data with Table storage

With either of these Azure Extensions options, the mechanism for setting and persisting data via the Bot Framework SDK for .NET remains the same as the in-memory data storage.

Bot state methods

This table lists the methods that you can use to manage state data.

Method Scoped to Objective
GetUserData User Get state data that has previously been saved for the user on the specified channel
GetConversationData Conversation Get state data that has previously been saved for the conversation on the specified channel
GetPrivateConversationData User and Conversation Get state data that has previously been saved for the user within the conversation on the specified channel
SetUserData User Save state data for the user on the specified channel
SetConversationData Conversation Save state data for the conversation on the specified channel.

Note: Because the DeleteStateForUser method does not delete data that has been stored using the SetConversationData method, you must NOT use this method to store a user's personally identifiable information (PII).
SetPrivateConversationData User and Conversation Save state data for the user within the conversation on the specified channel
DeleteStateForUser User Delete state data for the user that has previously been stored by using either the SetUserData method or the SetPrivateConversationData method.

Note: Your bot should call this method when it receives an activity of type deleteUserData or an activity of type contactRelationUpdate that indicates the bot has been removed from the user's contact list.

If your bot saves state data by using one of the "Set...Data" methods, future messages that your bot receives in the same context will contain that data, which your bot can access by using the corresponding "Get...Data" method.

Useful properties for managing state data

Each Activity object contains properties that you will use to manage state data.

Property Description Use case
From Uniquely identifies a user on a channel Storing and retrieving state data that is associated with a user
Conversation Uniquely identifies a conversation Storing and retrieving state data that is associated with a conversation
From and Conversation Uniquely identifies a user and conversation Storing and retrieving state data that is associated with a specific user within the context of a specific conversation

Note

You may use these property values as keys even if you opt to store state data in your own database, rather than using the Bot Framework state data store.

Create a state client

The StateClient object enables you to manage state data using the Bot Builder SDK for .NET. If you have access to a message that belongs to the same context in which you want to manage state data, you can create a state client by calling the GetStateClient method on the Activity object.

StateClient stateClient = activity.GetStateClient();

If you do not have access to a message that belongs to the same context in which you want to manage state data, you can create a state client by simply creating a new instance of the StateClient class. In this example, microsoftAppId and microsoftAppPassword are the Bot Framework authentication credentials that you acquire for your bot during the bot creation process.

Note

To find your bot's AppID and AppPassword, see MicrosoftAppID and MicrosoftAppPassword.

StateClient stateClient = new StateClient(new MicrosoftAppCredentials(microsoftAppId, microsoftAppPassword));

Note

The default state client is stored in a central service. For some channels, you may want to use a state API that is hosted within the channel itself, so that state data can be stored in a compliant store that the channel supplies.

Get state data

Each of the "Get...Data" methods returns a BotData object that contains the state data for the specified user and/or conversation. To get a specific property value from a BotData object, call the GetProperty method.

The following code example shows how to get a typed property from user data.

BotData userData = await stateClient.BotState.GetUserDataAsync(activity.ChannelId, activity.From.Id);
var sentGreeting = userData.GetProperty<bool>("SentGreeting");

The following code example shows how to get a property from a complex type within user data.

MyCustomType myUserData = new MyCustomType();
BotData botData = await botState.GetUserDataAsync(activity.ChannelId, activity.From.Id);
myUserData = botData.GetProperty<MyCustomType>("UserData");

If no state data exists for the user and/or conversation that is specified for a "Get...Data" method call, the BotData object that is returned will contain these property values:

  • BotData.Data = null
  • BotData.ETag = "*"

Save state data

To save state data, first get the BotData object by calling the appropriate "Get...Data" method, then update it by calling the SetProperty method for each property you want to update, and save it by calling the appropriate "Set...Data" method.

Note

You may store up to 32 kilobytes of data for each user on a channel, each conversation on a channel, and each user within the context of a conversation on a channel.

The following code example shows how to save a typed property in user data.

BotData userData = await stateClient.BotState.GetUserDataAsync(activity.ChannelId, activity.From.Id);
userData.SetProperty<bool>("SentGreeting", true);
await stateClient.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);

The following code example shows how to save a property in a complex type within user data.

BotData userData = await stateClient.BotState.GetUserDataAsync(activity.ChannelId, activity.From.Id);
userData.SetProperty<MyCustomType>("UserData", myUserData);
await stateClient.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);

Handle concurrency issues

Your bot may receive an error response with HTTP status code 412 Precondition Failed when it attempts to save state data, if another instance of the bot has changed the data. You can design your bot to account for this scenario, as shown in the following code example.

var builder = new ContainerBuilder();
builder
    .Register(c => new CachingBotDataStore(c.Resolve<ConnectorStore>(), CachingBotDataStoreConsistencyPolicy.LastWriteWins))
    .As<IBotDataStore<BotData>>()
    .AsSelf()
    .InstancePerLifetimeScope();
builder.Update(Conversation.Container);

Additional resources