如何在通道上主动重置用户的IDialogStack?

时间:2018-10-12 09:47:54

标签: c# botframework

在有当前用户的情况下,我(不建议)升级了我的机器人的RootDialog对话数据-特别是一个新的成员变量。

这意味着某些用户的序列化对话状态与当前版本的bot代码不匹配。

如果用户联系机器人,这很好-他们会在机器人上引发异常并重置堆栈的同时收到“机器人出现问题”消息。在那之后一切都会好起来的。

但是,我们希望主动向机器人发送消息,并且由于序列化不匹配(BadRequest异常),目前所有这些消息都失败了。

如何为IDialogContext之外的给定用户/通道解析IBotData和IDialogStack?我正在Azure数据库上使用SQL bot数据存储(SqlBotDataEntities)。

我想在管理员网站上的控制器方法中执行此操作,然后调用stack.Reset()。这可能吗?

1 个答案:

答案 0 :(得分:1)

尝试对 IBotData 进行 LoadAsync 操作会导致堆栈自动重置。 SqlBotDataEntities 表中有足够的信息来创建 Activity ,并将其用于范围。 (由于 SqlBotDataContext 是Azure扩展库的内部组件,因此您需要在代码中重复它。)

类似的东西:

public async Task<HttpResponseMessage> Post()
{
    using (var context = new SqlBotDataContext(ConfigurationManager.ConnectionStrings["BotDataContextConnectionString"].ConnectionString))
    {
        try
        {
            foreach(var botData in context.BotData)
            {
                if(botData.BotStoreType == BotStoreType.BotPrivateConversationData)
                {
                    var message = Activity.CreateMessageActivity();
                    message.ChannelId = botData.ChannelId;
                    message.Timestamp = botData.Timestamp;
                    message.From = new ChannelAccount(id: botData.UserId);
                    message.Conversation = new ConversationAccount(id: botData.ConversationId);
                    message.Recipient = new ChannelAccount(id: botData.BotId);
                    message.ServiceUrl = botData.ServiceUrl;

                    using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
                    {
                        var scopedData = scope.Resolve<IBotData>();
                        await scopedData.LoadAsync(default(CancellationToken));
                        //resetting the stack is not necessary, since .LoadAsync will fail silently, and reset it
                        //var stack = scope.Resolve<IDialogStack>();
                        //stack.Reset();
                        await scopedData.FlushAsync(default(CancellationToken));
                    }
                }
            }
        }
        catch (System.Data.SqlClient.SqlException err)
        {
            throw new HttpException((int)HttpStatusCode.InternalServerError, err.Message);
        }
    }

    var response = Request.CreateResponse(HttpStatusCode.OK);
    return response;
} 

https://github.com/Microsoft/BotBuilder-Azure/blob/master/CSharp/Library/Microsoft.Bot.Builder.Azure/SqlBotDataStore.cs#L163

复制以下内容
internal class SqlBotDataContext : System.Data.Entity.DbContext
{
    public SqlBotDataContext(string connectionString)
        : base(connectionString)
    {
        System.Data.Entity.Database.SetInitializer<SqlBotDataContext>(null);
    }

    /// <summary>
    /// Throw if the database or SqlBotDataEntities table have not been created.
    /// </summary>
    static internal void AssertDatabaseReady()
    {
        //var connectionString = Utils.GetAppSetting(AppSettingKeys.SqlServerConnectionString);
        var connectionString = ConfigurationManager.ConnectionStrings["BotDataContextConnectionString"].ConnectionString;
        using (var context = new SqlBotDataContext(connectionString))
        {
            if (!context.Database.Exists())
                throw new ArgumentException("The sql database defined in the connection has not been created. See https://github.com/Microsoft/BotBuilder-Azure/tree/master/CSharp");

            if (context.Database.SqlQuery<int>(@"IF EXISTS (SELECT * FROM sys.tables WHERE name = 'SqlBotDataEntities') 
                                                                SELECT 1
                                                            ELSE
                                                                SELECT 0").SingleOrDefault() != 1)
                throw new ArgumentException("The SqlBotDataEntities table has not been created in the database. See https://github.com/Microsoft/BotBuilder-Azure/tree/master/CSharp");
        }
    }

    public DbSet<SqlBotDataEntity> BotData { get; set; }
}
public enum BotStoreType
{
    BotConversationData = 0,
    BotPrivateConversationData = 1,
    BotUserData = 2
}
internal class SqlBotDataEntity : IAddress
{
    private static readonly JsonSerializerSettings serializationSettings = new JsonSerializerSettings()
    {
        Formatting = Formatting.None,
        NullValueHandling = NullValueHandling.Ignore
    };
    internal SqlBotDataEntity() { Timestamp = DateTimeOffset.UtcNow; }
    internal SqlBotDataEntity(BotStoreType botStoreType, string botId, string channelId, string conversationId, string userId, object data)
    {
        this.BotStoreType = botStoreType;
        this.BotId = botId;
        this.ChannelId = channelId;
        this.ConversationId = conversationId;
        this.UserId = userId;
        this.Data = Serialize(data);
        Timestamp = DateTimeOffset.UtcNow;
    }


    #region Fields

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    [Index("idxStoreChannelUser", 1)]
    [Index("idxStoreChannelConversation", 1)]
    [Index("idxStoreChannelConversationUser", 1)]
    public BotStoreType BotStoreType { get; set; }
    public string BotId { get; set; }
    [Index("idxStoreChannelConversation", 2)]
    [Index("idxStoreChannelUser", 2)]
    [Index("idxStoreChannelConversationUser", 2)]
    [MaxLength(200)]
    public string ChannelId { get; set; }
    [Index("idxStoreChannelConversation", 3)]
    [Index("idxStoreChannelConversationUser", 3)]
    [MaxLength(200)]
    public string ConversationId { get; set; }
    [Index("idxStoreChannelUser", 3)]
    [Index("idxStoreChannelConversationUser", 4)]
    [MaxLength(200)]
    public string UserId { get; set; }
    public byte[] Data { get; set; }
    public string ETag { get; set; }
    public string ServiceUrl { get; set; }
    [Required]
    public DateTimeOffset Timestamp { get; set; }

    #endregion Fields

    #region Methods

    private static byte[] Serialize(object data)
    {
        using (var cmpStream = new MemoryStream())
        using (var stream = new GZipStream(cmpStream, CompressionMode.Compress))
        using (var streamWriter = new StreamWriter(stream))
        {
            var serializedJSon = JsonConvert.SerializeObject(data, serializationSettings);
            streamWriter.Write(serializedJSon);
            streamWriter.Close();
            stream.Close();
            return cmpStream.ToArray();
        }
    }

    private static object Deserialize(byte[] bytes)
    {
        using (var stream = new MemoryStream(bytes))
        using (var gz = new GZipStream(stream, CompressionMode.Decompress))
        using (var streamReader = new StreamReader(gz))
        {
            return JsonConvert.DeserializeObject(streamReader.ReadToEnd());
        }
    }

    internal ObjectT GetData<ObjectT>()
    {
        return ((JObject)Deserialize(this.Data)).ToObject<ObjectT>();
    }

    internal object GetData()
    {
        return Deserialize(this.Data);
    }
    internal static async Task<SqlBotDataEntity> GetSqlBotDataEntity(IAddress key, BotStoreType botStoreType, SqlBotDataContext context)
    {
        SqlBotDataEntity entity = null;
        var query = context.BotData.OrderByDescending(d => d.Timestamp);
        switch (botStoreType)
        {
            case BotStoreType.BotConversationData:
                entity = await query.FirstOrDefaultAsync(d => d.BotStoreType == botStoreType
                                                && d.ChannelId == key.ChannelId
                                                && d.ConversationId == key.ConversationId);
                break;
            case BotStoreType.BotUserData:
                entity = await query.FirstOrDefaultAsync(d => d.BotStoreType == botStoreType
                                                && d.ChannelId == key.ChannelId
                                                && d.UserId == key.UserId);
                break;
            case BotStoreType.BotPrivateConversationData:
                entity = await query.FirstOrDefaultAsync(d => d.BotStoreType == botStoreType
                                                && d.ChannelId == key.ChannelId
                                                && d.ConversationId == key.ConversationId
                                                && d.UserId == key.UserId);
                break;
            default:
                throw new ArgumentException("Unsupported bot store type!");
        }

        return entity;
    }
    #endregion
}