State Management with Azure Bot Framework SDKV4

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
  1. User Profile class for the user information.
  2. Conversation Data class to gather user information.
  1. public class UserProfile  
  2. {  
  3.    public string Name { getset; }  
  4. }  


Add ConversationData.cs
  1. public class ConversationData  
  2.    {  
  3.        // The time-stamp of the most recent incoming message.  
  4.        public string Timestamp { get; set; }  
  5.   
  6.        // The ID of the user's channel.  
  7.        public string ChannelId { get; set; }  
  8.   
  9.        // Track whether we have already asked the user's name  
  10.        public bool PromptedUserForName { get; set; } = false;  
  11.    }  


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.
  1. public void ConfigureServices(IServiceCollection services)  
  2.        {  
  3.            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);  
  4.            // Create the credential provider to be used with the Bot Framework Adapter.  
  5.            services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();  
  6.           // Create the Bot Framework Adapter.  
  7.            services.AddSingleton<IBotFrameworkHttpAdapter, BotFrameworkHttpAdapter>();  
  8.            // Create the bot as a transient. In this case the ASP Controller is expecting an IBot.  
  9.            services.AddTransient<IBot, EchoBot>();  
  10.            // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)  
  11.            services.AddSingleton<IStorage, MemoryStorage>();  
  12.            // Create the User state.  
  13.            services.AddSingleton<UserState>();             
  14.            // Create the Conversation state.  
  15.            services.AddSingleton<ConversationState>();  
  16.        }  

  1. private BotState _conversationState;  
  2.         private BotState _userState;  
  3.   
  4.         public EchoBot(ConversationState conversationState, UserState userState)  
  5.         {  
  6.             _conversationState = conversationState;  
  7.             _userState = userState;  
  8.         } 


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,
  1. var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));  
  2. var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());  
  3.   
  4. var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));  
  5. var userProfile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile());  
Step 5 – Get State from Bot
  1. userProfile.Name is empty and conversationData.PromptedUserForName is true, we retrieve the user name provided and store this within user state.
  2. If userProfile.Name is empty and conversationData.PromptedUserForName is false, we ask for the user's name.
  3. 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
  1. if (string.IsNullOrEmpty(userProfile.Name))  
  2.            {  
  3.                // First time around this is set to false, so we will prompt user for name.  
  4.                if (conversationData.PromptedUserForName)  
  5.                {  
  6.                    // Set the name to what the user provided.  
  7.                    userProfile.Name = turnContext.Activity.Text?.Trim();  
  8.   
  9.                    // Acknowledge that we got their name.  
  10.                    await turnContext.SendActivityAsync($"Thanks {userProfile.Name}. To see conversation data, type anything.");  
  11.   
  12.                    // Reset the flag to allow the bot to go though the cycle again.  
  13.                    conversationData.PromptedUserForName = false;  
  14.                }  
  15.                else  
  16.                {  
  17.                    // Prompt the user for their name.  
  18.                    await turnContext.SendActivityAsync($"What is your name?");  
  19.   
  20.                    // Set the flag to true, so we don't prompt in the next turn.  
  21.                    conversationData.PromptedUserForName = true;  
  22.                }  
  23.            }  
  24.            else  
  25.            {  
  26.                // Add message details to the conversation data.  
  27.                // Convert saved Timestamp to local DateTimeOffset, then to string for display.  
  28.                var messageTimeOffset = (DateTimeOffset)turnContext.Activity.Timestamp;  
  29.                var localMessageTime = messageTimeOffset.ToLocalTime();  
  30.                conversationData.Timestamp = localMessageTime.ToString();  
  31.                conversationData.ChannelId = turnContext.Activity.ChannelId.ToString();  
  32.   
  33.                // Display state data.  
  34.                await turnContext.SendActivityAsync($"{userProfile.Name} sent: {turnContext.Activity.Text}");  
  35.                await turnContext.SendActivityAsync($"Message received at: {conversationData.Timestamp}");  
  36.                await turnContext.SendActivityAsync($"Message received from: {conversationData.ChannelId}");  
  37.            }  
Step 6 – To save the changes async
 
Add function to save the changes in EchoBot.cs
  1. public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))  
  2.         {  
  3.             await base.OnTurnAsync(turnContext, cancellationToken);  
  4.   
  5.             // Save any state changes that might have occured during the turn.  
  6.             await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);  
  7.             await _userState.SaveChangesAsync(turnContext, false, cancellationToken);  
  8.         }  
Step 7 – Test your Bot Locally
 
Press F5 to Visual Studio Solution and Launch with Bot Emulator V4,


I hope you have enjoyed and learned something new in this article. Thanks for reading and stay tuned for the next article.

Get rid with horizontal margin or spaces from Modern Community Site Pages

SharePoint Online Modern communication site pages support section layout, named Full-Width column. It helps to span the section without any horizontal margin or padding.
In this article, you will learn the following,


  1. Add Full-width Section.
  2. Add Full-width support to SPFx Control.
  3. Add SPFx Control to Full-width Section.
Users can choose from a number of different section layouts such as,
  • one columns 
  • two columns
  • three columns
  • one-third left column
  • one-third right column

Full-Width Section Requirement

When we build a regular SPFx control, it doesn’t expand beyond a certain width due to section layouts which don’t support or allow the full width.
So, let’s get started
Step 1 - Add Full-Width Section
  • Full-width section allows layouts to expand to the full width of the page.
  • Edit the page
  • Click the plus “+” icon parallel to the section layout and select the Full-width section.



Step 2 - Add Full-Width support to SPFx Control
SPFx control doesn’t add the full-width column property by default. But SharePoint Framework allows us to add full-width column attributes into the web part manifest file, i.e., GetEmployeeDataWebPart.menifest.json and adds supportsFullBleed property to true.
  1. {  
  2.   "$schema""https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",  
  3.   "id""c58ad54b-0b32-4087-8b9b-5b28ded5e375",  
  4.   "alias""GetEmployeeDataWebPart",  
  5.   "componentType""WebPart",  
  6.   
  7.   // The "*" signifies that the version should be taken from the package.json  
  8.   "version""*",  
  9.   "manifestVersion": 2,  
  10.   
  11.   // If true, the component can only be installed on sites where Custom Script is allowed.  
  12.   // Components that allow authors to embed arbitrary script code should set this to true.  
  13.   // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f  
  14.   "requiresCustomScript"false,  
  15.   "supportsFullBleed"true 
  16.   
  17.   "preconfiguredEntries": [{  
  18.     "groupId""5c03119e-3074-46fd-976b-c60198311f70",   
  19.     "group": { "default""Other" },  
  20.     "title": { "default""GetEmployeeData" },  
  21.     "description": { "default""GetEmployeeData description" },  
  22.     "officeFabricIconFontName""Page",  
  23.     "properties": {  
  24.       "listName""SPFxList"  
  25.     }  
  26.   }]  
  27. }  



Do the above one line setting. Build the bundle and deploy the solution.
Step 3 - Add SPFx Control to Full Width Section 
Browse the site and add a full-width section. The custom SPFx control will be available as per the below screenshot.





Click and add control. You can see that the custom control will take the full horizontal section and doesn’t allow margin or spaces.




NoteAs per today, the full-width column section doesn’t work with workbench and Modern Team Sites.
I hope you have enjoyed and learned something new in this article. Thanks for reading. Stay tuned for the next article.