【问题标题】:How to store & retrieve Bot Data in Azure Table storage with directLine channel?如何使用 directLine 通道在 Azure 表存储中存储和检索 Bot 数据?
【发布时间】:2018-01-18 17:36:29
【问题描述】:

我正在使用Microsoft Bot FrameworkdirectLine 频道。我的机器人是公司客户门户的一部分,我从中获取一些用户信息并使用stateClient 将其存储在BotState 中,如下所示

 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);
        }

作为directLine 频道,我正在使用javascript 中的秘密连接我的机器人,如下所示:

  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'));

我正在访问MVC站点中捕获的Node js Bot中的用户信息数据,如下所示:

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
    }
}

我参考了微软的Save state data from Manage state data 获取上述代码,然后我使用session 中的userData 在我的Node.JS Bot 中访问这些数据。

由于 StateClient 已弃用,我建议 thisstateclient 替换为 Azure Table storage。但是,我无法理解如何将上述数据存储在Table Storage 中。

谁能推荐任何我可以参考的文章来解决这个问题?

我的机器人在 NodeJs 中,而我在 C# MVC application 中使用 directLine 频道。

【问题讨论】:

  • 请澄清这是否正确:您有一个内置在 node.js 中的机器人,它将状态存储在 azure 表存储中。您想从 mvc 站点中检索此状态。
  • 我在客户门户上有我的聊天机器人。我正在从 MVC 站点的登录会话中收集用户的标识信息。我想将此用户信息从 MVC 站点发送到节点 js 中的机器人,以进一步处理它以获得对用户在 directLine 频道上提出的问题的响应。我想在 node js bot 中访问这些信息。
  • @Eric,我已经更新了问题以便更清楚。

标签: c# node.js botframework azure-table-storage direct-line-botframework


【解决方案1】:

一种选择是使用 Microsoft Azure 存储客户端库 for .NET,如此处的答案所述:How to retrieve Saved Conversation Data in Azure (Tablelogger) 只需确保遵循与TableBotDataStore 类,并正确序列化数据字段。

-- 编辑: 我对此进行了测试,它确实按预期工作。

 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; }
    }
}

在节点机器人中:

'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;
    }
]);

【讨论】:

  • 我经历了这个解决方案。它用于存储我认为在 Bot 端 捕获的对话历史记录,并使用客户名称进行检索。如果没有消息活动或对话 ID,我将无法将来自客户端的用户数据唯一地存储在 Azure 表存储中,然后在 Bot 端检索它。
  • @GauravDhavale 我在答案中添加了一些代码,并对其进行了测试。这确实有效。存储用户数据不需要活动和对话。
  • 嗨@Eric,感谢您提供此代码。我能够将用户信息存储在 Azure 表存储中。我先得到 404,所以我手动创建了 BotStore 表。为了在我的 Node JS Bot 中检索数据,我使用了 this 链接。但是,我可以使用botbuilder-azure 示例来检索用户信息吗?此链接上的示例仅显示将数据存储在表存储中。
  • 另外,您能否指导我如何在会话参数中检索 Bot 端的数据?它与我在 Bot 端获取用户信息的方式不同吗?谢谢 :)
  • 啊...我正在测试具有默认用户 ID 的模拟器。此 ID 与存储在客户端分区键中的 userId 不同,因此我没有获取存储在 session.userData 中的 Azure 表存储中的数据。最后,我可以使用 Directline 通道在 Azure 表存储中存储和检索机器人数据。非常感谢,@Eric。
【解决方案2】:

您正在使用的代码正在使用 已弃用 默认状态,因此无法正常工作。为了完成您想要的,这取决于您在代码中的位置。决定因素是您是否可以访问context 对象。

例如,如果您在MessagesController 中,您将无法访问上下文对象,您的代码可能如下所示:

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());
                }
            } 

然后获取数据:

userData.GetProperty<string>("key 1");

另一种情况是,如果您确实可以访问上下文对象,例如 Dialog,您的代码可能如下所示:

        context.UserData.SetValue("key 1", "value1");
        context.UserData.SetValue("key 2", "value2");

然后获取数据:

context.UserData.GetValue<string>("key 1");

【讨论】:

  • 你好 Jason,我已经更新了我的代码部分。我可以只用 javascript 代码通过机器人进行通信。我刚刚使用索引视图来捕获用户信息。所以我找不到上下文。
【解决方案3】:

您是否已将机器人配置为连接到 Global.asax.cs 中的 Azure 表存储,而不是已弃用的默认状态?
我写了一篇博文,详细介绍了如何将机器人的状态移动到 Azure 表存储,你应该查看它 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,是的,我已将机器人配置为连接到 Azure 表存储,如 ths 示例中所示。我将通过你的例子,看看是否有帮助。谢谢你。 :)
  • 我想把用户信息保存在channel端的状态,而不是bot端。我想简单地在机器人端访问这些数据。您的示例将活动显示为 Post 函数的输入。由于我在 javascript 中使用 Direct Line 通道,因此我没有此活动,如示例代码 Index() 方法中所示。你能帮我解决一下这个场景,我该如何替换状态客户端?
猜你喜欢
  • 1970-01-01
  • 2013-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-29
  • 1970-01-01
  • 2016-04-02
  • 2019-04-23
相关资源
最近更新 更多