Wrapcode

Manage user and conversation data with BotDataStore in Bot Framework

| 3 min read

I have been using Bot State management APIs for quite a long now. While the default service proved to be perfectly good for "my bot's usage". It indeed was slow and data privacy was always a concern for others. Default state service is used to store and retrieve user or conversational data within conversation context. Bot Framework is continuously being evolved over the period of past few months. To improve the performance, Microsoft has decided to deprecate State Management APIs. Few reasons why Microsoft recommended bot developers to implement their own state storage mechanisms -

  • Improved latency – you won’t be relying on the connection of the default connector service
  • Direct control over your bot’s conversation state and data.

Configure custom Bot Framework state adapter

You can either use in-memory data storage or configure custom data storage like Cosmos DB and Azure Storage. There is a really good article on Bot Framework documentation page that shows how we can configure custom data storages. You can follow this link to configure state storage adapters (^1 https://docs.microsoft.com/en-us/bot-framework/dotnet/bot-builder-dotnet-state).

> It is recommended to use Azure Extensions (Cosmos DB, Azure Table Storage) as storage adapter in production.

Manage user state in bot

You can set or retrieve custom user or conversation state using IBotDataStore. I assume you are using Autofac to register IBotDataStore module as given in the documentation (refer link ^1). To revisit, this is how we registered the IBotDataStore module in Global.asax.cs -

//In Global.asax.cs
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
var store = new TableBotDataStore(ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
view raw Global.asax.cs hosted with ❤ by GitHub

You can register for Cosmos DB or your custom data store like the snippet above.

Resolving DataStore is easy. Bot Builder's DialogModule creates a scope for a required message in conversation.

//In your dialog
Activity message = result as Activity;
//Create scope with respect to activity
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
//Resolve scope for IBotDataStore<BotData>
IBotDataStore<BotData> stateStore = scope.Resolve<IBotDataStore<BotData>>();
}
view raw AnyDialog.cs hosted with ❤ by GitHub

You can make use of this state store to retrieve user data. You need Address as a key to retrieve user specific bot data. Address is the key that uniquely identifies a bot's conversation with a user on a channel. As you can access activity message in Dialogs, it is easy to retrieve Address key.

Address key = Address.FromActivity(message);
view raw AnyDialog.cs hosted with ❤ by GitHub

Once you have an address key. You can use it to retrieve user data (BotData).

//In your dialog
Activity message = result as Activity;
//Create scope with respect to activity
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
//Resolve scope for IBotDataStore<BotData>
IBotDataStore<BotData> stateStore = scope.Resolve<IBotDataStore<BotData>>();
/* Retrieve user address. Address key holds information about Bot, Channel, User and conversation */
Address key = Address.FromActivity(message);
//Load user data with the help of key
BotData userData = await stateStore.LoadAsync(key, BotStoreType.BotUserData, CancellationToken.None);
}
view raw AnyDialog.cs hosted with ❤ by GitHub

This BotData object is responsible to manage information about user and channel. We can Get, Set and Remove properties using BotData methods. Below snippet shows simple Get, Set and Remove actions for BotData.

//In your dialog
Activity message = result as Activity;
//Create scope with respect to activity
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
//Resolve scope for IBotDataStore<BotData>
IBotDataStore<BotData> stateStore = scope.Resolve<IBotDataStore<BotData>>();
/* Retrieve user address. Address key holds information about Bot, Channel, User and conversation */
Address key = Address.FromActivity(message);
//Load user data with the help of key
BotData userData = await stateStore.LoadAsync(key, BotStoreType.BotUserData, CancellationToken.None);
//Get value by key
string getUserLanguagePreference = userData.GetProperty<string>("language_preference");
//Set value of type string
userData.SetProperty<string>("language_preference", "en_US");
//Remove value
userData.RemoveProperty("language_preference");
}
view raw AnyDialog.cs hosted with ❤ by GitHub

You can information in any serializable type.

 

In my experience of working with bots. I usually store user preferences (Language etc.) and profile information with the help of IBotDataStore. Your usage can be different. Let me know if you have any better way to achieve this.

 

Cheers, Rahul