Dispatch示例Adaptive Card提交无响应及luisResult键缺失问题
我完全明白你现在卡在哪了:Adaptive Card能正常渲染展示,但点击提交按钮后,要么Bot直接跳去默认分支,要么就抛出KeyNotFoundException说找不到luisResult键。这本质是因为Adaptive Card提交的结构化消息和普通文本消息的处理逻辑完全不同,Dispatch Recognizer不会自动对Value里的结构化数据做LUIS意图识别,导致后续代码找不到预期的luisResult键。
问题根源拆解
当用户点击Adaptive Card的提交按钮时,Bot收到的Activity里带的是Value字段(就是你调试看到的{"startdate": "2017-10-12", "enddate": "2017-10-12"}),这不是普通的文本输入。此时Dispatch的识别器不会主动调用LUIS做意图分析,所以recognizerResult.Properties里根本没有luisResult这个键,直接访问自然会触发异常。你之前把Value转成Text的尝试没用,因为Dispatch不会把这段序列化后的JSON当成用户输入去调用LUIS。
具体解决方案
我们需要把普通文本消息和Adaptive Card提交的结构化消息分开处理,同时给代码加一层安全校验:
1. 优先处理Adaptive Card的提交数据
在OnMessageActivityAsync方法开头,先判断Activity.Value是否存在,如果存在就直接处理结构化数据,不走Dispatch的意图识别流程:
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken) { // 先处理Adaptive Card提交的结构化数据 if (turnContext.Activity.Value != null) { // 反序列化提交的日期数据(需要先定义对应的模型类) var leaveDates = JsonConvert.DeserializeObject<LeaveDatesModel>(turnContext.Activity.Value.ToString()); // 直接处理业务逻辑:比如回复用户提交的日期,或者触发后续请假流程 await turnContext.SendActivityAsync( MessageFactory.Text($"已收到你的请假日期:开始日 {leaveDates.startdate},结束日 {leaveDates.enddate}"), cancellationToken); // 如果需要继续关联LUIS的某个意图,可以手动调用对应的处理方法 // 比如这里可以直接触发请假申请的后续步骤 return; } // 处理普通文本消息,走Dispatch的意图识别流程 var recognizerResult = await _botServices.Dispatch.RecognizeAsync(turnContext, cancellationToken); var topIntent = recognizerResult.GetTopScoringIntent(); await DispatchToTopIntentAsync(turnContext, topIntent.intent, recognizerResult, cancellationToken); } // 定义用来反序列化Adaptive Card提交数据的模型类 public class LeaveDatesModel { public string startdate { get; set; } public string enddate { get; set; } }
2. 安全访问recognizerResult.Properties里的luisResult
修改DispatchToTopIntentAsync方法,不要直接硬访问recognizerResult.Properties["luisResult"],而是用TryGetValue先判断键是否存在,避免抛出异常:
private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken) { switch (intent) { case "l_mts-bot-809f": // 先校验luisResult是否存在 if (recognizerResult.Properties.TryGetValue("luisResult", out var luisResultObj) && luisResultObj is LuisResult luisResult) { await ProcessHomeAutomationAsync(turnContext, luisResult, cancellationToken); } else { await turnContext.SendActivityAsync(MessageFactory.Text("抱歉,无法识别你的请求,请重试。"), cancellationToken); } break; case "q_mts-bot": await ProcessSampleQnAAsync(turnContext, cancellationToken); break; default: // 默认分支同样先做安全校验 if (recognizerResult.Properties.TryGetValue("luisResult", out var defaultLuisResultObj) && defaultLuisResultObj is LuisResult defaultLuisResult) { await ProcessHomeAutomationAsync(turnContext, defaultLuisResult, cancellationToken); } else { await turnContext.SendActivityAsync(MessageFactory.Text("抱歉,我没理解你的意思。"), cancellationToken); } break; } }
3. 清理无效逻辑
你之前在ProcessHomeAutomationAsync的LeaveSelfApplication分支里判断turnContext.Activity.Value的代码其实是无效的——因为用户第一次触发LeaveSelfApplication时,Bot发送Adaptive Card,此时Activity.Value是null;只有当用户提交Card时,消息才会被我们在OnMessageActivityAsync里优先处理,所以这段代码可以删掉,避免逻辑混乱。
总结
通过这些调整,我们实现了:
- 直接处理Adaptive Card提交的结构化数据,不需要依赖Dispatch的LUIS识别
- 给代码加了安全校验,彻底避免KeyNotFoundException
- 区分不同类型的消息,让整体逻辑更清晰
内容的提问来源于stack exchange,提问作者Nikhil Bansal

