Manage conversation and user state

Note

This topic is pre-release documentation for V4 SDK content and is subject to change. You can find V3 SDK (stable) content here.

For your bot to save conversation and user state, first initialize state manager middleware, and then use the conversation and user state properties. For more information on how that state is used, see State and storage.

Initialize state manager middleware

In the SDK, you need to initialize the bot adapter to use state manager middleware before you can use the conversation or user property stores. Conversation state is used for conversation properties, and user state is used for user properties. (User state properties can be accessed across multiple conversations.) The state manager middleware provides an abstraction that lets you access properties using a simple key-value or object store, independent of the type of underlying storage. The state manager takes care of writing data to storage and managing concurrency, whether the underlying storage type is in-memory, file storage, or Azure Table Storage.

To see how ConversationState is initialized, see Startup.cs in the Microsoft.Bot.Samples.EchoBot-AspNetCore sample.

The libraries required for this code:

using Microsoft.Bot.Builder.BotFramework;
using Microsoft.Bot.Builder.Core.Extensions;
using Microsoft.Bot.Builder.Integration.AspNet.Core;

Initializing ConversationState:

services.AddBot<EchoBot>(options =>
{
    options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);

    IStorage dataStore = new MemoryStorage();
    options.Middleware.Add(new ConversationState<EchoState>(dataStore));
});

In the line options.Middleware.Add(new ConversationState<EchoState>(dataStore));, ConversationState is the conversation state manager object, which is added to the bot as middleware. The EchoState type parameter is the type representing how you want conversation state info to be stored. A bot can use any class type for conversation or user state data.

The implementation of EchoState is in EchoBot.cs:

public class EchoState
{
    public int TurnNumber { get; set; }
}

Note

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. See File storage and Azure table storage later in this article to set up other underlying storage mediums for conversation state and user state.

Configuring state manager middleware

When initializing the state middleware, an optional state settings parameter allows you to change the default behavior of how properties are saved. The default settings are:

  • Persist properties beyond the lifetime of the turn context.
  • If more than one instance of the bot writes to a property, allow the last instance of the bot to overwrite the previous one.

Use conversation and user state properties

Once the state manager middleware has been configured, you can get the conversation state and user state properties from the context object.

You can see how this works using the Microsoft.Bot.Samples.EchoBot sample in the Bot Builder SDK.

In the OnTurn handler, context.GetConversationState gets the conversation state to access the data you defined, and you can modify the state by using the properties (in this case, incrementing TurnNumber).

public async Task OnTurn(ITurnContext context)
{
    // This bot is only handling Messages
    if (context.Activity.Type == ActivityTypes.Message)
    {
        // Get the conversation state from the turn context
        var state = context.GetConversationState<EchoState>();

        // Bump the turn count. 
        state.TurnCount++;

        // Echo back to the user whatever they typed.
        await context.SendActivity($"Turn {state.TurnCount}: You sent '{context.Activity.Text}'");
    }
}

Using conversation state to direct conversation flow

In designing a conversation flow, it is useful to define a state flag to direct the conversation flow. The flag can be a simple Boolean type or a type that includes the name of the current topic. The flag can help you track where in a conversation you are. For example, a Boolean type flag can tell you whether you are in a conversation or not. While a topic name property can tell you which conversation you are currently in.

The following example uses a Boolean have asked name property to flag when the bot has asked the user for their name. When the next message is received, the bot checks the property. If the property is set to true, the bot knows the user was just asked for their name, and interprets the incoming message as a name to save as a user property.

public class ConversationInfo
{
    public bool haveAskedNameFlag { get; set; }
    public bool haveAskedNumberFlag { get; set; }
}

public class UserInfo
{
    public string name { get; set; }
    public string telephoneNumber { get; set; }
    public bool done { get; set; }
}

public async Task OnTurn(ITurnContext context)
{
    // Get state objects. Default objects are created if they don't already exist.
    var convo = ConversationState<ConversationInfo>.Get(context);
    var user = UserState<UserInfo>.Get(context);

    if (context.Activity.Type is ActivityTypes.Message)
    {
        if (string.IsNullOrEmpty(user.name) && !convo.haveAskedNameFlag)
        {
            // Ask for the name.
            await context.SendActivity("Hello. What's your name?");

            // Set flag to show we've asked for the name. We save this out so the
            // context object for the next turn of the conversation can check haveAskedName
            convo.haveAskedNameFlag = true;
        }
        else if (!convo.haveAskedNumberFlag)
        {
            // Save the name.
            var name = context.Activity.AsMessageActivity().Text;
            user.name = name;
            convo.haveAskedNameFlag = false; // Reset flag

            // Ask for the phone number. You might want a flag to track this, too.
            await context.SendActivity($"Hello, {name}. What's your telephone number?");
            convo.haveAskedNumberFlag = true;
        }
        else if (convo.haveAskedNumberFlag)
        {
            // save the telephone number
            var telephonenumber = context.Activity.AsMessageActivity().Text;

            user.telephoneNumber = telephonenumber;
            convo.haveAskedNumberFlag = false; // Reset flag
            await context.SendActivity($"Got it. I'll call you later.");
        }
    }
}

To set up user state so that it can be returned by UserState<UserInfo>.Get(context), you add user state middleware. For example, in Startup.cs of the ASP .NET Core EchoBot, changing the code in ConfigureServices.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddBot<EchoBot>(options =>
    {
        options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);
        
        IStorage dataStore = new MemoryStorage();
        options.Middleware.Add(new ConversationState<ConversationInfo>(dataStore));
        options.Middleware.Add(new UserState<UserInfo>(dataStore));
    });
}

An alternative is to use the waterfall model of a dialog. The dialog keeps track of the conversation state for you so you do not need to create flags to track your state. For more information, see Manage simple conversation with Dialogs.

File storage

The memory storage provider uses in-memory storage that gets disposed when the bot is restarted. It is good for testing purposes only. If you want to persist data but do not want to hook your bot up to a database, you can use the file storage provider. While this provider is also intented for testing purposes, it persists state data to a file so that you can inspect it. The data is written out to file using JSON format.

Go to Startup.cs in the Microsoft.Bot.Samples.EchoBot-AspNetCore sample, and edit the code in the ConfigureServices method.

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddBot<EchoBot>(options =>
    {
        options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);

        // Using file storage instead of in-memory storage.
        IStorage dataStore = new FileStorage(System.IO.Path.GetTempPath());
        options.Middleware.Add(new ConversationState<EchoState>(dataStore));
    });
}

Run the code, and let the echobot echo back your input a few times.

Then go to the directory specified by System.IO.Path.GetTempPath(). You should see a file with a name starting with "conversation". Open it and look at the JSON. The file contains something like the following:

{
  "$type": "Microsoft.Bot.Samples.Echo.EchoState, Microsoft.Bot.Samples.EchoBot",
  "TurnNumber": "3",
  "eTag": "ecfe2a23566b4b52b2fe697cffc59385"
}

The $type specifies the type of the data structure you're using in your bot to store conversation state. The TurnNumber field corresponds to the TurnNumber property in the EchoState class. The eTag field is inherited from IStoreItem and is a unique value that automatically gets updated each time your bot updates conversation state. The eTag field enables your bot to enable optimistic concurrency.

Azure table storage

You can also use Azure Table storage as your storage medium.

In the Microsoft.Bot.Samples.EchoBot-AspNetCore sample, add a reference to the Microsoft.Bot.Builder.Azure NuGet package.

Then, go to Startup.cs, add a using Microsoft.Bot.Builder.Azure; statement, and edit the code in the ConfigureServices method.

services.AddBot<EchoBot>(options =>
{
    options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);
    // The parameters are the connection string and table name.
    // "UseDevelopmentStorage=true" is the connection string to use if you are using the Azure Storage Emulator.
    // Replace it with your own connection string if you're not using the emulator
    options.Middleware.Add(new ConversationState<EchoState>(new AzureTableStorage("UseDevelopmentStorage=true","conversationstatetable")));
    // you could also specify the cloud storage account instead of the connection string
    /* options.Middleware.Add(new ConversationState<EchoState>(
        new AzureTableStorage(WindowsAzure.Storage.CloudStorageAccount.DevelopmentStorageAccount, "conversationstatetable"))); */
    options.EnableProactiveMessages = true;
});

UseDevelopmentStorage=true is the connection string you can use with the Azure Storage Emulator. Replace it with your own connection string if you're not using the emulator.

If the table with the name you specify in the constructor to AzureTableStorage doesn't exist, it is created.

To look at the conversation state data that is saved, run the sample and then open the table using Azure Storage explorer.

echobot conversation state data in Azure Storage Explorer

The Partition Key is a uniquely-generated key specific to the current conversation. If you restart the bot or start a new conversation, the new conversation will get its own row with its own partition key. The EchoState data structure is serialized to JSON and saved in the Json column of the Azure Table.

{
    "$type":"Microsoft.Bot.Samples.Echo.AspNetCore.EchoState, Microsoft.Bot.Samples.EchoBot-AspNetCore",
    "TurnNumber":2,
    "LastMessage":"second message",
    "eTag":"*"
}

The $type and eTag fields are added by the BotBuilder SDK. For more about eTags, see Managing optimistic concurrency

Next steps

Now that you know how to use state to help you read and write bot data to storage, lets take a look at how you can read and write directly to storage.

Additional resources

For more background on storage, see Storage in the Bot Builder SDK