The Scenario
我有一个使用Bot Framework构建的机器人,带有一系列对话框 . 其中一个对话框为用户提供了通过向他们提供按钮来通过网页输入一些复杂数据的选项 . 单击按钮,然后将它们带到站点,填写数据,保存,然后返回到机器人 .
我希望我的机器人暂停对话,直到它从我的网页收到一个事件,告诉我用户已保存数据,然后继续询问用户问题 .
Before
我有一个版本实现,我会在用户点击按钮之前存储ConversationReference,然后当外部事件发生时,我会发送卡片和我想要显示的下一条消息(不是在对话框中)来自webhook,这很好但是它变得非常复杂/凌乱 - 我宁愿将整个应用程序保持在一个连续的对话框中 .
Idea 1: Use DirectLine API
我做了一些研究,很多人建议使用DirectLine API . 所以我实现了这个:
public async Task SendEventAsync(InternalEventMessage message, ConversationReference reference) {
var client = new DirectLineClient(!String.IsNullOrEmpty(_settings.DirectLineSecret) ? _settings.DirectLineSecret : null);
if (_settings.SiteUrl.Contains("localhost")) {
client.BaseUri = new Uri(_settings.DirectLineServiceUrl);
}
var eventMessage = Activity.CreateEventActivity();
//Wrong way round?!?
eventMessage.From = reference.Bot;
eventMessage.Type = ActivityTypes.Event;
eventMessage.Value = message;
var conversation = await client.Conversations.PostActivityAsync(reference.Conversation.Id, eventMessage as Activity);
}
这使用DirectLine客户端使用存储的ConversationReference向serviceUrl发送事件消息,基本上模仿用户(僵尸程序和用户似乎在SDK中是错误的方式) . 检查localhost是因为DirectLine库指向模拟器服务器而不是https://directline.botframework.com .
我在对话框中打电话给:
//method above shows input button and links to web page
context.Wait(WaitForAddressInput);
}
private async Task WaitForAddressInput(IDialogContext context, IAwaitable<IActivity> result) {
var message = await result;
switch (message.Type) {
case ActivityTypes.Message:
//TODO: Add response
break;
case ActivityTypes.Event:
var eventMessage = message as IEventActivity;
if (((JObject)eventMessage.Value).ToObject<InternalEventMessage>().Type == EventType.AddressInputComplete) {
_addressResult = (await _tableService.ReadOrderById(Order.OrderId)).Address;
await context.PostAsync($"Great}");
context.Done(_addressResult);
}
break;
}
}
在显示按钮后,等待来自用户的任何消息,如果我们的事件匹配,则继续对话 .
这在本地使用模拟器工作,但令人沮丧的是,它不存在 . 它无法识别通过网络聊天或Messenger创建的 Channels . 这在这里解释:Microsoft Bot Framework DirectLine Can't Access Conversations
出于安全原因,您无法使用DirectLine监视来自其他对话的邮件 .
所以我无法访问我没有使用DirectLine创建的 Channels .
Idea 2: BotConnector
所以我想我会尝试使用类似代码的BotConnector:
public async Task SendEventAsync(InternalEventMessage message, Microsoft.Bot.Connector.DirectLine.ConversationReference reference) {
var botAccount = new ChannelAccount(reference.User.Id, reference.User.Name);
var userAccount = new ChannelAccount(reference.Bot.Id, reference.Bot.Name);
MicrosoftAppCredentials.TrustServiceUrl(reference.ServiceUrl);
var connector = new ConnectorClient(new Uri(reference.ServiceUrl), new MicrosoftAppCredentials("xxxxxxxxxxxxxxxxxxxxxxxx", "xxxxxxxxxxxxxxxxxxxxxxxx"));
connector.Credentials.InitializeServiceClient();
var eventMessage = Activity.CreateMessageActivity();
eventMessage.Recipient = botAccount;
eventMessage.From = userAccount;
eventMessage.Type = ActivityTypes.Event;
eventMessage.Conversation = new ConversationAccount(id: reference.Conversation.Id);
eventMessage.ServiceUrl = reference.ServiceUrl;
eventMessage.Timestamp = DateTimeOffset.UtcNow;
eventMessage.LocalTimestamp = DateTime.Now;
eventMessage.ChannelId = reference.ChannelId;
var result = await connector.Conversations.SendToConversationAsync(eventMessage as Microsoft.Bot.Connector.Activity);
}
这不会崩溃,我可以看到事件出现在模拟器请求控制台但没有任何反应,它似乎被忽略了!
Idea 3: Try to imitate the bot service calling my bot
我还没有尝试过这个,因为我认为这可能是最耗时的,但我正在阅读有关服务身份验证的here,并想知道是否有可能模仿托管的僵尸服务发送消息并以此方式发送我的事件所需数据?
This seems like a fairly common scenario so I'm surprised I haven't come across a way to do this yet. If anyone has any other ideas on how I can send an event message to my bot from an external service then I'd love to hear it.
Update: 在Eric下面看我的答案,看看我做了什么 .
2 回答
想法1:
DirectLine 是一个 Channels ,而不是用于连接 Channels 的库 . (例如:您不会使用Facebook Messenger连接到Skype) DirectLineClient 对于创建通过Direct Line连接器服务连接到 DirectLine Channels 的客户端应用程序非常有用 .
想法2:
这种方法应该有效 . 实际上, BotAuth 库将此方法用于 CallbackController 中的MagicNumber登录流程:https://github.com/MicrosoftDX/botauth/blob/9a0a9f1b665f4aa95b6d60d09346dda90d8b314e/CSharp/BotAuth/Controllers/CallbackController.cs
对于您的方案,您应该能够构造 CardAction 类型 ActionTypes.OpenUrl ,其中包含在URL中编码 ConversationReference 的值 . 单击该按钮将调用显示页面的mvc控制器(将 ConversationReference 保存在cookie或其他内容中),当用户完成在页面上添加地址时,使用 ConversationReference 将事件发送到机器人(类似于BotAuth恢复的方式) CallbackController中的对话) .
想法3:
这将绕过连接器服务,并且不是受支持的方案 . 您共享的链接解释了身份验证在Bot Framework中的工作方式的详细信息,而不是如何绕过连接器服务 .
Eric的回答促使我使用BotAuth示例解决了这个问题,但是,为了完整起见,这是我使用Idea 2所做的 .
我在我的Bot Framework endpoints 上创建了一个CallbackController,然后使用以下代码将事件发送回等待对话框:
对话框等待此代码并继续:
这是我在Bot框架中遇到的最复杂的问题,我发现文档有点缺乏 . 希望这有助于某人!