In this article, I am going to talk about User State Management with Azure Bot Framework SDK V4.
Let's understand, what is State?
State is like an activity. It may not run on the same process. Bot is inherently stateless. Bot Framework SDK allows you to use state management and storage objects to manage and persist the state. State Manager provides the abstraction layer and follows the same paradigms as a modern web application.
Why we need state?
Maintaining state allows your bot to have more meaningful conversations by remembering certain things about a user or conversation. For example, if you've talked to a user previously, you can save previous information about them, so that you don't have to ask for it again.
What we are going to explore in this article
Upon receiving user input, this sample checks the stored conversation state to see if this user has previously been prompted to provide their name. If not, the user's name is requested and that input is stored within the user state. If so, the name stored within the user state is used to converse with the user and their input data, along with the time received and input channel Id, is returned back to the user.
Let’s get started with a solution.
Step 1 - Create Bot using VS2017 (Bot framework SDK V4 template)
- Launch Visual Studio 2017, create a new project, and select Bot Framework.
- Select the Echo Bot (Bot Framework V4) template.
- Select the location and give the bot name.
- Click OK to create the project.
Step 2 - Create User Profile and Conversation Data Class
Define the classes which can maintain the user information and conversation data
- User Profile class for the user information.
- Conversation Data class to gather user information.
- public class UserProfile
- {
- public string Name { get; set; }
- }
Add ConversationData.cs
- public class ConversationData
- {
- // The time-stamp of the most recent incoming message.
- public string Timestamp { get; set; }
- // The ID of the user's channel.
- public string ChannelId { get; set; }
- // Track whether we have already asked the user's name
- public bool PromptedUserForName { get; set; } = false;
- }
Step 3 - Create User and Conversation State Object
Register Memory Storage that is used to create User and Conversation objects. The user and conversation state objects are created at Startup and dependency injected into the bot constructor. Other services for a bot that are registered are - a credential provider, an adapter, and the bot implementation.
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
- // Create the credential provider to be used with the Bot Framework Adapter.
- services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
- // Create the Bot Framework Adapter.
- services.AddSingleton<IBotFrameworkHttpAdapter, BotFrameworkHttpAdapter>();
- // Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
- services.AddTransient<IBot, EchoBot>();
- // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
- services.AddSingleton<IStorage, MemoryStorage>();
- // Create the User state.
- services.AddSingleton<UserState>();
- // Create the Conversation state.
- services.AddSingleton<ConversationState>();
- }
- private BotState _conversationState;
- private BotState _userState;
- public EchoBot(ConversationState conversationState, UserState userState)
- {
- _conversationState = conversationState;
- _userState = userState;
- }
Step 4 – Add State Property
Create Property method that provides a handle to the
BotState
object. Each state property accessor allows you to get or set the value of the associated state property. Accessor loads the property from storage and get it from the state cache.
Get Async to get the properly scoped key associated with the state property,
- var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
- var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());
- var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
- var userProfile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile());
Step 5 – Get State from Bot
- userProfile.Name is empty and conversationData.PromptedUserForName is true, we retrieve the user name provided and store this within user state.
- If userProfile.Name is empty and conversationData.PromptedUserForName is false, we ask for the user's name.
- If userProfile.Name was previously stored, we retrieve message time and channel Id from the user input, echo all data back to the user, and store the retrieved data within conversation state.
Change in EchoBot.cs add OnMessage Activity Async
- if (string.IsNullOrEmpty(userProfile.Name))
- {
- // First time around this is set to false, so we will prompt user for name.
- if (conversationData.PromptedUserForName)
- {
- // Set the name to what the user provided.
- userProfile.Name = turnContext.Activity.Text?.Trim();
- // Acknowledge that we got their name.
- await turnContext.SendActivityAsync($"Thanks {userProfile.Name}. To see conversation data, type anything.");
- // Reset the flag to allow the bot to go though the cycle again.
- conversationData.PromptedUserForName = false;
- }
- else
- {
- // Prompt the user for their name.
- await turnContext.SendActivityAsync($"What is your name?");
- // Set the flag to true, so we don't prompt in the next turn.
- conversationData.PromptedUserForName = true;
- }
- }
- else
- {
- // Add message details to the conversation data.
- // Convert saved Timestamp to local DateTimeOffset, then to string for display.
- var messageTimeOffset = (DateTimeOffset)turnContext.Activity.Timestamp;
- var localMessageTime = messageTimeOffset.ToLocalTime();
- conversationData.Timestamp = localMessageTime.ToString();
- conversationData.ChannelId = turnContext.Activity.ChannelId.ToString();
- // Display state data.
- await turnContext.SendActivityAsync($"{userProfile.Name} sent: {turnContext.Activity.Text}");
- await turnContext.SendActivityAsync($"Message received at: {conversationData.Timestamp}");
- await turnContext.SendActivityAsync($"Message received from: {conversationData.ChannelId}");
- }
Step 6 – To save the changes async
Add function to save the changes in EchoBot.cs
- public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
- {
- await base.OnTurnAsync(turnContext, cancellationToken);
- // Save any state changes that might have occured during the turn.
- await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
- await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
- }
Step 7 – Test your Bot Locally
Press F5 to Visual Studio Solution and Launch with Bot Emulator V4,
The previous article can be found from these links.
I hope you have enjoyed and learned something new in this article. Thanks for reading and stay tuned for the next article.