不能序列化IDialogContext;需要从MS Bot框架内的对话对象中获取用户信息

本文关键字:对话 对象 信息 用户 获取 框架 Bot IDialogContext 序列化 MS 不能 | 更新日期: 2023-09-27 18:01:48

我正在使用MS bot框架,并试图使bot发送预定的消息。为此,我使用Hangfire框架。

我试图使用这个代码调度,其中上下文是从我的对话框传递的IDialogContext对象,SendScheduledMessageToUser方法刚刚调用context. postasync()发送消息给用户:

            BackgroundJob.Schedule(() => CurrencyDialog.SendScheduledMessageToUser(context), new DateTimeOffset(when));

问题是上下文结果为空。我在调用它的控制台中看到一个序列化异常。我假设你不能序列化上下文对象,因为它内部的循环引用。

因此,为了向用户发送预定消息,我最好的想法是获取用户信息(ID,会话ID,频道,服务URL等),然后将这些简单的数据传递给预定方法,以便它可以向用户发送消息。然而,令人难以置信的是,似乎没有办法从IDialog的实现内部获取用户数据。IDialogContext有数据,但它被标记为私有或内部,所以我不能得到它。当对话框第一次启动时,我不能将Activity对象传递给对话框,因为没有构造函数。

关于从IDialog的实现中获取用户信息,或者获取一些可以序列化的数据,以便向用户发送预定的消息,有什么想法吗?

不能序列化IDialogContext;需要从MS Bot框架内的对话对象中获取用户信息

还有另一种获取用户数据的方法。在你的Controllers Post方法中这样做-

case ActivityTypes.Message:
    StateClient stateClient = activity.GetStateClient();        
    BotData userData = await stateClient.BotState.GetUserDataAsync(activity.ChannelId, activity.From.Id);
    userData.SetProperty<string>("name", activity.From.Name);
    //other properties you want to set.
    await stateClient.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);

然后在你的intent方法中,像这样访问它们-

[LuisIntent("Greetings")]
public async Task Greetings(IDialogContext context, LuisResult result)
{
var username = context.UserData.Get<string>("name");
await context.PostAsync($"hi {username}");
    context.Wait(MessageReceived);
}

可以将原始活动传递给后台作业

我的例子是使用石英。. NET,但应该类似于Hangfire

public class ReminderJob : IJob
{
    public void Execute(IJobExecutionContext context)
    {
        var dataMap = context.Trigger.JobDataMap;
        var originalActivity = dataMap["originalActivity"] as Activity;
        var message = dataMap["reply"] as string;
        if (originalActivity == null) return;
        var connector = new ConnectorClient(new Uri(originalActivity.ServiceUrl));
        var reply = originalActivity.CreateReply(message);
        connector.Conversations.ReplyToActivity(reply);
    }
}
public class JobScheduler
{
    public static void StartReminderJob(ITrigger trigger)
    {
        var scheduler = StdSchedulerFactory.GetDefaultScheduler();
        scheduler.Start();
        var job = JobBuilder.Create<ReminderJob>().Build();
        scheduler.ScheduleJob(job, trigger);
    }
}

我是这样使用的

// how to use
var jobDetail = TriggerBuilder
            .Create()
            .StartAt(new DateTimeOffset(result.Start.Value.ToUniversalTime()))
            .Build();
jobDetail.JobDataMap["originalActivity"] = originalMessage;
jobDetail.JobDataMap["reply"] = $"test";
JobScheduler.StartReminderJob(jobDetail);

好的,所以我最终使用的解决方案非常非常蹩脚,但是有效。

由于Hangfire在序列化IDialogContext或Activity时抛出异常,我在Dialog对象和Hangfire调用的方法的参数中保存了一些字符串参数,然后在必要时重新创建活动。

在我的对话框类中:

public class CurrencyDialog : IDialog<object>
{
    public string serviceUrl;
    public string from;
    public string recipient;
    public string conversation;
    public string channelId;
    public string activityId;

当对话框开始时,保存对象中的字符串:

    public async Task StartAsync(IDialogContext context)
    {
        //await context.PostAsync("What rate would you like to check today?");
        context.Wait(BeginCurrencyDialog);
    }
    public async Task BeginCurrencyDialog(IDialogContext context, IAwaitable<IMessageActivity> argument)
    {
        //this needs to be saved because neither Activity nor IDialogContext are serializable, but Hangfire needs it
        IMessageActivity activity = await argument;
        serviceUrl = activity.ServiceUrl;
        from = activity.From.Id;
        recipient = activity.Recipient.Id;
        conversation = activity.Conversation.Id;
        channelId = activity.ChannelId;
        activityId = activity.Id;

当你想要调度作业时,将这些传递给你在Hangfire中使用的方法:

最后,在Hangfire调用的方法中,当时间到来时,使用这个方法来重新创建活动,然后您可以使用它来发送答案:

    //creates an activity that can be used to send rich messages in response; cannot use original activity because it is not serializable
    public Activity CreateActivity()
    {
        if (activityId == null) { return null; }
        ChannelAccount fromAccount = new ChannelAccount(id: from);
        ChannelAccount recipientAccount = new ChannelAccount(id: recipient);
        ConversationAccount conversationAccount = new ConversationAccount(id: conversation);
        return new Activity()
        {
            Type = ActivityTypes.Message,
            ServiceUrl = serviceUrl,
            From = fromAccount,
            Recipient = recipientAccount,
            Conversation = conversationAccount,
            ChannelId = channelId,
            Id = activityId
        };
    }

之后,您可以使用activity.CreateReply()来创建回复和上下文。PostAsync将其发送给用户。