2

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.

Gaurav Dhavale
  • 151
  • 1
  • 14
  • Please clarify if this is correct: You have a bot built in node.js that stores state in azure table storage. You want to retrieve this state from within an mvc site. – Eric Dahlvang Jan 19 '18 at 21:05
  • I have my chatbot on the customer portal. I'm collecting user's identification information from login session in MVC site. I want to send this user information to the bot in node js from MVC site to process it further to get the response to the question user asked on directLine channel. I want to access this information in node js bot. – Gaurav Dhavale Jan 19 '18 at 21:26
  • @Eric, I have updated the question for better clarity. – Gaurav Dhavale Jan 19 '18 at 21:39

3 Answers3

4

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 = "whatever@email.com",
                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 = "whatever@email.com",
                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;
    }
]);

enter image description here

Eric Dahlvang
  • 8,252
  • 4
  • 29
  • 50
  • I went through this solution. Its used to store conversation history which I believe is captured on the **Bot side** and it's retrieved with the customer name. Without message activity or conversation id, I won't be able to uniquely store user data from the Client side on Azure Table storage and then retrieve it on the Bot side. – Gaurav Dhavale Feb 13 '18 at 17:23
  • @GauravDhavale I added some code to my answer, and tested it out. This does work. Activity and conversation are not required in order to store userdata. – Eric Dahlvang Feb 14 '18 at 01:08
  • Hi @Eric, Thank you for this code. I'm able to store the user information in the Azure Table Storage. I got 404 first, So I manually created the BotStore table. To retrieve a data in my Node JS Bot, I used [this](https://learn.microsoft.com/en-us/azure/cosmos-db/table-storage-how-to-use-nodejs#retrieve-an-entity-by-key) link. However, Can I use [botbuilder-azure](https://learn.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-state-azure-table-storage) example for retrieving user information? The example on this link only shows storing data in table storage. – Gaurav Dhavale Feb 14 '18 at 18:04
  • Also, Can you please guide me on how did you retrieve the data on the Bot side in the session parameter? Is it different than the way I'm approaching to get the user information on the Bot side?Thanks :) – Gaurav Dhavale Feb 14 '18 at 18:08
  • I've provided the code for the node bot as well (It is using Bot Builder Azure, and bot builder sdk loads session.userData) – Eric Dahlvang Feb 14 '18 at 18:54
  • 1
    Ahh... I was testing over Emulator which has a default user id. This ID is different from the userId stored in partition key on the client side because of which I was not getting the data stored in Azure Table storage in session.userData. Finally, I'm able to store & retrieve Bot Data in Azure Table storage with Directline channel. Thanks a lot, @Eric. – Gaurav Dhavale Feb 14 '18 at 20:27
2

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");
D4RKCIDE
  • 3,439
  • 1
  • 18
  • 34
  • Hello Jason, I have updated my code section. I'm able to communicate over the bot with just javascript code. I have just used the Index view to capture user information. So I was not able to find the context. – Gaurav Dhavale Jan 19 '18 at 18:44
-1

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);
    }
desflan
  • 468
  • 3
  • 13
  • Hey @desflan, Yes, I have configured the bot to connect to Azure Table Storage as shown in [ths](https://blog.botframework.com/2017/07/18/saving-state-azure-extensions/) example. I will go through your example and will see if that helps. Thank you. :) – Gaurav Dhavale Feb 08 '18 at 15:27
  • I want to store the user information in the state on the channel side and not on the bot side. I want to simply access this data on the bot side. Your example shows activity as an Input to the Post function. Since I'm using Direct Line channel in the javascript, I don't have this activity as shown in my example code Index() method. Can you please help me this scenario, how can I replace the state client? – Gaurav Dhavale Feb 09 '18 at 02:32