C# 在bot框架中使用自适应对话框时,类对象bot状态是否可以保存在代码中?

C# 在bot框架中使用自适应对话框时,类对象bot状态是否可以保存在代码中?,c#,.net,botframework,C#,.net,Botframework,我们在Bot框架中遇到了一个问题,当第一个步骤之一是执行访问器.GetAsync()或.SetAsync()的CodeAction时,访问属性的后续对话框将因错误而崩溃: [OnTurnError]未处理的错误:无法将“Newtonsoft.Json.Linq.JValue”类型的对象转换为“System.String”类型。 完整堆栈如下所示: System.ArgumentException: Object of type 'Newtonsoft.Json.Linq.JValue' can

我们在Bot框架中遇到了一个问题,当第一个步骤之一是执行
访问器.GetAsync()
.SetAsync()
CodeAction
时,访问属性的后续对话框将因错误而崩溃:

[OnTurnError]未处理的错误:无法将“Newtonsoft.Json.Linq.JValue”类型的对象转换为“System.String”类型。

完整堆栈如下所示:


System.ArgumentException: Object of type 'Newtonsoft.Json.Linq.JValue' cannot be converted to type 'System.String'.
   at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)
   at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
   at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
   at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
   at System.Reflection.PropertyInfo.SetValue(Object obj, Object value)
   at Microsoft.Bot.Builder.Dialogs.ObjectPath.SetObjectSegment(Object obj, Object segment, Object value, Boolean json)
   at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid5[T0,T1,T2,T3,T4](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
   at Microsoft.Bot.Builder.Dialogs.ObjectPath.SetPathValue(Object obj, String path, Object value, Boolean json)
   at Microsoft.Bot.Builder.Dialogs.Memory.DialogStateManager.SetValue(String path, Object value)
   at Microsoft.Bot.Builder.Dialogs.Adaptive.Input.InputDialog.ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.Dialogs.DialogContext.ContinueDialogAsync(CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.Dialogs.Adaptive.AdaptiveDialog.ContinueActionsAsync(DialogContext dc, Object options, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.Dialogs.Adaptive.AdaptiveDialog.ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.Dialogs.DialogContext.ContinueDialogAsync(CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.Dialogs.DialogManager.HandleBotOnTurnAsync(DialogContext dc, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.Dialogs.DialogManager.OnTurnAsync(ITurnContext context, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.Dialogs.DialogManager.OnTurnAsync(ITurnContext context, CancellationToken cancellationToken)
   at MyBot.Bot.MyBotBot`1.OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken) in H:\Work\MyBot\backendadaptivebot\api\Bots\MyBotBot.cs:line 50
   at Microsoft.Bot.Builder.RegisterClassMiddleware`1.OnTurnAsync(ITurnContext turnContext, NextDelegate nextTurn, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.RegisterClassMiddleware`1.OnTurnAsync(ITurnContext turnContext, NextDelegate nextTurn, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.RegisterClassMiddleware`1.OnTurnAsync(ITurnContext turnContext, NextDelegate nextTurn, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.BotFrameworkAdapter.TenantIdWorkaroundForTeamsMiddleware.OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.MiddlewareSet.ReceiveActivityWithStatusAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.BotAdapter.RunPipelineAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken)
RootDialog
类构造函数中,我们有:

            participantAccessor = _userState.CreateProperty<Profile>("profile");

            string[] paths = { ".", "Dialogs", $"RootDialog.lg" };
            string fullPath = Path.Combine(paths);

            // These steps are executed when this Adaptive Dialog begins
            Triggers = new List<OnCondition>()
                {
                    // Add a rule to welcome user
                    new OnConversationUpdateActivity()
                    {
                        Actions = WelcomeUserSteps()
                    },

                    // Respond to user on message activity
                    new OnUnknownIntent()
                    {
                        Actions = GetUserDetails()
                    },
                };
此TextInput收到用户输入后,崩溃发生。 但是,如果删除了
CodeAction
,则bot不会崩溃

在这个
PopulateProfile
方法中,我们只需访问在构造函数中创建的属性,这是一个基本的配置文件类(只包含简单的属性:字符串和整数)

然后DialogContext.State中的对象类型似乎是JSON对象,并且bot按预期运行

这是否意味着Bot框架中的自适应对话框不支持通过代码将属性写入POCO类型

因此,必须通过简单的直接值(例如,
user.profile.completed
)或通过SetProperty操作来完成

(我在任何地方都看不到该文件)

编辑:下面是简单的
配置文件
类:

    public class Profile
    {
        public string Id { get; set; }
        public string AssociatedAsset { get; set; }
        public string FullName { get; set; }
        public string PreferredName { get; set; }
        public string MobileNumber { get; set; }
        public string Email { get; set; }
        public int Complete { get; set; } = 0;
    }

你试图将两种基本上不相容的做事方式结合起来。状态属性访问器是Bot Builder v4中访问状态的原始方式,但是自适应对话框是一个全新的系统,它们有自己的访问状态的方式

这就是问题所在。即使尝试将字符串分配给
此.value
,也会立即将该字符串转换为
JToken
。因此,即使将
JToken
作为字符串检索,当分配给
user.profile.MobileNumber
时,它仍将转换回
JToken
。如果
user.profile
已使用类型信息序列化,则它将被反序列化为该类型,这意味着在
JToken
被分配给其
MobileNumber
属性之前,它将被转换为
profile
对象。您可以在中提出这一错误,但请注意,您试图做的事情与自适应对话框的设计背道而驰

如果您想使用自适应对话框,您应该对此保持一致,并以自适应对话框的方式执行所有操作。代码操作很容易出错,因此应该谨慎使用。如果使用代码操作可以使用不同的自适应对话框操作,那么应该使用不同的操作,因为它将包含内置的自适应对话框功能。例如,如果您想设置一个属性,那么应该像您看到的那样使用
SetProperty

如果确实要在代码操作中设置属性,则不应使用状态属性访问器,而应使用自适应对话框方式。自适应对话框使用
SetValue
设置其属性,因此您也应该这样做。这将确保以自适应对话框可以轻松使用的方式格式化数据

private static async Task<DialogTurnResult> PopulateProfile(DialogContext dc, object options)
{
    //var profile = await UserState.CreateProperty<Profile>("profile").GetAsync(dc.Context, () => new Profile());
    //profile.Complete = 0;

    dc.State.SetValue("user.profile.Complete", 0);

    return await dc.EndDialogAsync(options);
}
私有静态异步任务PopulateProfile(DialogContext dc,对象选项)
{
//var profile=await UserState.CreateProperty(“profile”).GetAsync(dc.Context,()=>newprofile());
//profile.Complete=0;
dc.State.SetValue(“user.profile.Complete”,0);
return wait dc.EndDialogAsync(选项);
}

您试图将两种基本上不兼容的做事方式结合起来。状态属性访问器是Bot Builder v4中访问状态的原始方式,但是自适应对话框是一个全新的系统,它们有自己的访问状态的方式

这就是问题所在。即使尝试将字符串分配给
此.value
,也会立即将该字符串转换为
JToken
。因此,即使将
JToken
作为字符串检索,当分配给
user.profile.MobileNumber
时,它仍将转换回
JToken
。如果
user.profile
已使用类型信息序列化,则它将被反序列化为该类型,这意味着在
JToken
被分配给其
MobileNumber
属性之前,它将被转换为
profile
对象。您可以在中提出这一错误,但请注意,您试图做的事情与自适应对话框的设计背道而驰

如果您想使用自适应对话框,您应该对此保持一致,并以自适应对话框的方式执行所有操作。代码操作很容易出错,因此应该谨慎使用。如果使用代码操作可以使用不同的自适应对话框操作,那么应该使用不同的操作,因为它将包含内置的自适应对话框功能。例如,如果您想设置一个属性,那么应该像您看到的那样使用
SetProperty

如果确实要在代码操作中设置属性,则不应使用状态属性访问器,而应使用自适应对话框方式。自适应对话框使用
SetValue
设置其属性,因此您也应该这样做。这将确保以自适应对话框可以轻松使用的方式格式化数据

private static async Task<DialogTurnResult> PopulateProfile(DialogContext dc, object options)
{
    //var profile = await UserState.CreateProperty<Profile>("profile").GetAsync(dc.Context, () => new Profile());
    //profile.Complete = 0;

    dc.State.SetValue("user.profile.Complete", 0);

    return await dc.EndDialogAsync(options);
}
私有静态异步任务PopulateProfile(DialogContext dc,对象选项)
{
//var profile=await UserState.CreateProperty(“profile”).GetAsync(dc.Context,()=>newprofile());
//profile.Complete=0;
dc.State.SetValue(“user.profile.Complete”,0);
return wait dc.EndDialogAsync(选项);
}

我在您的问题中看到了这三个不同的属性:
profile.Complete
profile.Completed
profile.Completed
。我们能看看这个
配置文件
类是什么样子吗?还有,你为什么要这么做
    public class Profile
    {
        public string Id { get; set; }
        public string AssociatedAsset { get; set; }
        public string FullName { get; set; }
        public string PreferredName { get; set; }
        public string MobileNumber { get; set; }
        public string Email { get; set; }
        public int Complete { get; set; } = 0;
    }
private static async Task<DialogTurnResult> PopulateProfile(DialogContext dc, object options)
{
    //var profile = await UserState.CreateProperty<Profile>("profile").GetAsync(dc.Context, () => new Profile());
    //profile.Complete = 0;

    dc.State.SetValue("user.profile.Complete", 0);

    return await dc.EndDialogAsync(options);
}