如何优化MS Bot向Azure Cosmos DB存储对话文本的实现方案?
嘿,很高兴看到你已经成功把MS Bot和Azure Cosmos DB打通了!目前你手动在每个PostAsync方法里调用context.ConversationData.SetValue("message", messageText);来存储对话文本的方式,虽然能实现需求,但确实有点重复冗余。这里有几个更优雅的优化方案,帮你摆脱重复代码:
中间件可以在消息到达Bot业务逻辑的前后自动处理,刚好可以用来统一记录所有对话消息,不用在每个PostAsync里重复写存储代码。
首先创建一个消息日志中间件类:
public class MessageLoggingMiddleware : IMiddleware { public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default) { // 只处理用户发送的消息类型 if (turnContext.Activity.Type == ActivityTypes.Message) { var messageText = turnContext.Activity.Text; // 如果你需要保存单条最新消息,用下面的代码 await turnContext.ConversationData.SetValueAsync("message", messageText, cancellationToken); // 如果需要保存完整的消息历史(不覆盖旧消息),可以改用列表存储 // var messageHistory = await turnContext.ConversationData.GetValueOrDefaultAsync<List<string>>("messageHistory", new List<string>(), cancellationToken); // messageHistory.Add(messageText); // await turnContext.ConversationData.SetValueAsync("messageHistory", messageHistory, cancellationToken); } // 继续执行后续的Bot业务逻辑 await next(cancellationToken); } }
然后在你的Global.asax.cs里注册这个中间件:
// 假设你已经实例化了自己的Bot类 var yourBot = new YourCustomBot(); // 添加中间件到Bot的管道中 yourBot.Use(new MessageLoggingMiddleware());
这样所有用户发送的消息都会被自动拦截并存储到Cosmos DB里,完全不用在每个PostAsync里重复写存储逻辑。
如果你想直接从底层存储层面做扩展,可以继承默认的DocumentDbBotDataStore,重写SaveAsync方法,在保存Bot数据的时候自动把对话消息加进去:
public class CustomDocumentDbBotDataStore : DocumentDbBotDataStore { public CustomDocumentDbBotDataStore(Uri serviceEndpoint, string authKey) : base(serviceEndpoint, authKey) { } public override async Task SaveAsync(IAddress key, BotData data, CancellationToken cancellationToken = default) { // 只处理对话范围内的消息 if (data.BotDataScope == BotDataScope.Conversation && data.Activity != null && data.Activity.Type == ActivityTypes.Message) { var messageText = data.Activity.Text; // 保存最新单条消息 data.SetValue("message", messageText); // 或者保存消息历史列表 // var messageHistory = data.GetValueOrDefault<List<string>>("messageHistory", new List<string>()); // messageHistory.Add(messageText); // data.SetValue("messageHistory", messageHistory); } // 调用父类方法完成最终的Cosmos DB存储 await base.SaveAsync(key, data, cancellationToken); } }
然后在Global.asax.cs里替换原来的存储实例:
var uri = new Uri(ConfigurationManager.AppSettings["DocumentDbUrl"]); var key = ConfigurationManager.AppSettings["DocumentDbKey"]; // 用自定义的存储类替换默认的DocumentDbBotDataStore var store = new CustomDocumentDbBotDataStore(uri, key); Conversation.UpdateContainer(builder => builder.Register(c => store).As<IBotDataStore<BotData>>().SingleInstance());
这个方案更贴近底层存储逻辑,所有Bot数据保存操作都会自动带上消息文本,无需额外调用SetValue。
如果你使用的是较新的Bot Framework v4版本,更推荐使用框架原生的ConversationState来管理对话数据,配合自定义状态类来存储消息历史:
首先定义一个自定义的对话状态类:
public class ConversationMessageHistory { // 用来存储所有对话消息的列表 public List<string> Messages { get; set; } = new List<string>(); }
然后在你的Bot类中注入ConversationState,并在消息处理逻辑中自动保存消息:
public class YourCustomBot : ActivityHandler { private readonly ConversationState _conversationState; private readonly IStatePropertyAccessor<ConversationMessageHistory> _messageHistoryAccessor; // 通过构造函数注入ConversationState public YourCustomBot(ConversationState conversationState) { _conversationState = conversationState; // 创建状态属性访问器 _messageHistoryAccessor = _conversationState.CreateProperty<ConversationMessageHistory>("MessageHistory"); } protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken) { // 获取当前对话的消息历史 var messageHistory = await _messageHistoryAccessor.GetAsync(turnContext, () => new ConversationMessageHistory(), cancellationToken); // 添加最新的用户消息 messageHistory.Messages.Add(turnContext.Activity.Text); // 保存状态到Cosmos DB await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); // 这里写你的Bot业务逻辑 await turnContext.SendActivityAsync($"收到你的消息:{turnContext.Activity.Text}", cancellationToken: cancellationToken); } }
这个方案完全贴合Bot Framework v4的设计模式,状态管理更规范,也能自动完成持久化操作。
根据你当前的实现场景,我更推荐方案1或者方案2;如果是使用v4版本的Bot Framework,方案3会是更合适的选择。这些方案都能帮你省去重复代码,让消息存储逻辑更集中、更易维护。
内容的提问来源于stack exchange,提问作者Adam

