Reputation: 151
I'm using Microsoft Bot Framework
with directLine
channel. My Bot is a part of company's customer portal from where I fetch some user information and store it in BotState
using stateClient
as shown below
public ActionResult Index()
{
var userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
GetTokenViaBootStrap().Wait();
var botCred = new MicrosoftAppCredentials(
ConfigurationManager.AppSettings["MicrosoftAppId"],
ConfigurationManager.AppSettings["MicrosoftAppPassword"]);
var stateClient = new StateClient(botCred);
BotState botState = new BotState(stateClient);
BotData botData = new BotData(eTag: "*");
botData.SetProperty<string>("UserName", result.UserInfo.GivenName + " " + result.UserInfo.FamilyName);
botData.SetProperty<string>("Email", result.UserInfo.DisplayableId);
botData.SetProperty<string>("GraphAccessToken", UserAccessToken);
botData.SetProperty<string>("TokenExpiryTime", result.ExpiresOn.ToString());
stateClient.BotState.SetUserDataAsync("directline", userId, botData).Wait();
var UserData = new UserInformationModel
{
UserId = userId,
UserName = result.UserInfo.GivenName + " " + result.UserInfo.FamilyName
};
return View(UserData);
}
As its a directLine
channel, I'm connecting my bot using secret in javascript as shown below:
BotChat.App({
bot: { id: 'my_bot_id', name: 'my_bot_id' },
resize: 'detect',
sendTyping: true, // defaults to false. set to true to send 'typing' activities to bot (and other users) when user is typing
user: { id: UserData.UserId},
directLine: {
secret: "my_bot_secret"
}
}, document.getElementById('my_bot_id'));
I'm accessing user information data in Node js Bot captured in MVC site as shown below:
function sessionUserCapture(session) {
switch (session.message.address.channelId) {
case 'skypeforbusiness':
// some code
break;
case 'directline':
userName= session.userData.UserName;
userEmail= session.userData.Email;
//some code
break;
case 'slack':
// some code
}
}
I referred Microsoft's Save state data from Manage state data for above code and then I used userData
available in the session
to access this data in my Node.JS
Bot.
As the StateClient is Deprecated, I referred this to replace stateclient
with Azure Table storage
. However, I'm not able to understand how can I store the above data in the Table Storage
.
Can anyone suggest any article which I can refer to solve this issue?
My Bot is in NodeJs
and the I'm using directLine
channel in a C# MVC application
.
Upvotes: 2
Views: 3616
Reputation: 8292
One option is to use the Microsoft Azure Storage Client Library for .NET, as explained in the answer here: How to retrieve Saved Conversation Data in Azure (Tablelogger) Just make sure to follow the exact same PartitionKey strategy as is followed by the TableBotDataStore class, and serialize the data field correctly.
-- Edit: I tested this out, and it does in fact work as expected.
public class WebChatController : Controller
{
public ActionResult Index()
{
var connectionString = ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString;
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("BotStore");
string userId = Guid.NewGuid().ToString();
TableQuery<BotDataRow> query = new TableQuery<BotDataRow>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, userId));
var dataRow = table.ExecuteQuery(query).FirstOrDefault();
if(dataRow != null)
{
dataRow.Data = Newtonsoft.Json.JsonConvert.SerializeObject(new
{
UserName = "This user's name",
Email = "[email protected]",
GraphAccessToken = "token",
TokenExpiryTime = DateTime.Now.AddHours(1)
});
dataRow.Timestamp = DateTimeOffset.UtcNow;
table.Execute(TableOperation.Replace(dataRow));
}
else
{
var row = new BotDataRow(userId, "userData");
row.Data = Newtonsoft.Json.JsonConvert.SerializeObject(new
{
UserName = "This user's name",
Email = "[email protected]",
GraphAccessToken = "token",
TokenExpiryTime = DateTime.Now.AddHours(1)
});
row.Timestamp = DateTimeOffset.UtcNow;
table.Execute(TableOperation.Insert(row));
}
var vm = new WebChatModel();
vm.UserId = userId;
return View(vm);
}
public class BotDataRow : TableEntity
{
public BotDataRow(string partitionKey, string rowKey)
{
this.PartitionKey = partitionKey;
this.RowKey = rowKey;
}
public BotDataRow() { }
public bool IsCompressed { get; set; }
public string Data { get; set; }
}
}
In the node bot:
'use strict';
const builder = require('botbuilder');
const restify = require('restify');
var azure = require('botbuilder-azure');
var tableName = 'BotStore';
var azureTableClient = new azure.AzureTableClient(tableName,'accountname','accountkey');
var tableStorage = new azure.AzureBotStorage({ gzipData: false }, azureTableClient);
const connector = new builder.ChatConnector({
appId: process.env.MicrosoftAppId,
appPassword: process.env.MicrosoftAppPassword
});
const server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3979, () => {
console.log(`${server.name} listening to ${server.url}`);
});
server.post('/api/messages', connector.listen());
var bot = new builder.UniversalBot(connector)
.set('storage', tableStorage);;
bot.dialog('/',
[
function (session){
var data = session.userData;
}
]);
Upvotes: 4
Reputation: 468
Have you configured your bot to connect to Azure Table Storage in Global.asax.cs, instead of the deprecated default state?
I have written a blog post with full details on how to move your bot's state to Azure Table Storage which you should review
here
protected void Application_Start()
{
Conversation.UpdateContainer(
builder =>
{
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
// Using Azure Table for storage
var store = new TableBotDataStore(ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
});
GlobalConfiguration.Configure(WebApiConfig.Register);
}
Upvotes: -1
Reputation: 3426
The code you are using is using the deprecated default state and will not work. In order to accomplish what you would like it depends on where you are in your code. The deciding factor is if you have access to the context
object or not.
For example if you are in the MessagesController
you will not have access to the context object and your code might look like this:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
var message = activity as IMessageActivity;
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var botDataStore = scope.Resolve<IBotDataStore<BotData>>();
var key = Address.FromActivity(message);
var userData = await botDataStore.LoadAsync(key, BotStoreType.BotUserData, CancellationToken.None);
userData.SetProperty("key 1", "value1");
userData.SetProperty("key 2", "value2");
await botDataStore.SaveAsync(key, BotStoreType.BotUserData, userData, CancellationToken.None);
await botDataStore.FlushAsync(key, CancellationToken.None);
}
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
}
then to get the data:
userData.GetProperty<string>("key 1");
The other situation would be if you did have access to the context object like in a Dialog
for example, your code might look like this:
context.UserData.SetValue("key 1", "value1");
context.UserData.SetValue("key 2", "value2");
then to get the data:
context.UserData.GetValue<string>("key 1");
Upvotes: 2