如何将用户上下文详情从Bot控制器传递至FormDialog(.NET环境)
嘿,我来帮你搞定这个问题!在.NET Bot Framework环境下,要把控制器里的用户上下文(比如Activity中的用户信息、对话数据)传递到FormDialog,核心思路是借助Bot的状态管理机制(UserState/ConversationState)暂存数据,再在FormDialog里读取使用。下面是具体的实现步骤和代码示例:
步骤1:先配置状态存储(如果还没做)
首先得确保你的Bot已经配置了状态存储,Azure Bot Service默认支持Azure Blob或Cosmos DB作为持久化存储,本地开发可以先用MemoryStorage过渡。在Startup.cs里注册状态服务:
public void ConfigureServices(IServiceCollection services) { services.AddBot<YourBotClass>(options => { // 配置状态存储(生产环境建议替换为AzureBlobStorage或CosmosDbStorage) IStorage dataStore = new MemoryStorage(); var conversationState = new ConversationState(dataStore); var userState = new UserState(dataStore); options.State.Add(conversationState); options.State.Add(userState); }); }
步骤2:在控制器中存储用户上下文
在你的Post控制器里,把Activity里的关键上下文信息(比如用户ID、自定义字段、对话ID等)存入ConversationState或UserState:
private readonly IServiceProvider _serviceProvider; // 通过构造函数注入服务提供者 public YourBotController(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity) { if (activity != null && activity.GetActivityType() == ActivityTypes.Message) { // 获取状态服务实例 var conversationState = _serviceProvider.GetService<ConversationState>(); var userState = _serviceProvider.GetService<UserState>(); // 创建状态属性容器,用于存储上下文 var conversationProps = conversationState.CreateProperty<ConversationContext>("ConversationContext"); var userProps = userState.CreateProperty<UserContext>("UserContext"); // 读取或初始化上下文对象,存入需要的信息 var conversationContext = await conversationProps.GetAsync(activity.Context, () => new ConversationContext()); conversationContext.UserId = activity.From.Id; conversationContext.SourceChannel = activity.ChannelId; conversationContext.LastUserMessage = activity.Text; // 保存状态变更 await conversationState.SaveChangesAsync(activity.Context, false); // 启动对话链,传递状态服务到RootDialog await Conversation.SendAsync(activity, () => new RootDialog(conversationState, userState)); } else { HandleSystemMessage(activity); } return new HttpResponseMessage(HttpStatusCode.Accepted); } // 自定义上下文模型类,按需添加字段 public class ConversationContext { public string UserId { get; set; } public string SourceChannel { get; set; } public string LastUserMessage { get; set; } } public class UserContext { // 用于存储跨会话的用户持久化信息,比如偏好、历史记录等 }
步骤3:在RootDialog中接收状态并传递给FormDialog
创建RootDialog,在构造函数中接收状态服务,然后在对话启动时读取上下文,再初始化FormDialog:
public class RootDialog : IDialog<object> { private readonly ConversationState _conversationState; private readonly UserState _userState; public RootDialog(ConversationState conversationState, UserState userState) { _conversationState = conversationState; _userState = userState; } public async Task StartAsync(IDialogContext context) { // 读取之前存储的会话上下文 var conversationProps = _conversationState.CreateProperty<ConversationContext>("ConversationContext"); var conversationContext = await conversationProps.GetAsync(context.Context, () => new ConversationContext()); // 初始化FormDialog时,把上下文传递给FormBuilder var formDialog = FormDialog.FromForm(() => RequestOrder.BuildEnquiryForm(conversationContext), FormOptions.PromptInStart); context.Call(formDialog, ResumeAfterFormDialog); } private async Task ResumeAfterFormDialog(IDialogContext context, IAwaitable<RequestOrder> result) { // 处理FormDialog返回的表单结果 var requestOrder = await result; await context.PostAsync($"已收到你的咨询:【类型】{requestOrder.EnquiryType},【详情】{requestOrder.EnquiryDetails}"); context.Wait(MessageReceivedAsync); } private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result) { var message = await result; await context.PostAsync("你可以继续发起新的咨询,或者输入「结束」终止对话"); context.Wait(MessageReceivedAsync); } }
步骤4:在FormBuilder中使用上下文数据
修改你的RequestOrder.BuildEnquiryForm方法,接收上下文参数,然后在表单字段中动态使用这些数据(比如设置默认值、个性化提示语):
public class RequestOrder { public string EnquiryType { get; set; } public string EnquiryDetails { get; set; } // 接收上下文参数,构建个性化表单 public static IForm<RequestOrder> BuildEnquiryForm(ConversationContext conversationContext) { return new FormBuilder<RequestOrder>() .Field(nameof(EnquiryType), prompt: $"你好 {conversationContext.UserId}(来自{conversationContext.SourceChannel}渠道),请选择咨询类型:", defaultValue: "技术支持") // 可以从上下文读取默认值 .Field(nameof(EnquiryDetails), prompt: "请详细描述你的问题:", validate: async (state, value) => { // 也可以用上下文做自定义校验逻辑 var details = value as string; if (string.IsNullOrEmpty(details)) { return new ValidateResult { IsValid = false, Feedback = "请输入有效的咨询详情哦" }; } return new ValidateResult { IsValid = true }; }) .Build(); } }
临时传递的替代方案(无需持久化)
如果只是临时传递上下文,不需要跨会话保存,可以直接用DialogContext的PrivateConversationData存储数据:
在控制器里:
await Conversation.SendAsync(activity, () => { return Chain.From(() => { var currentContext = DialogContext.CurrentContext; // 把临时数据存入会话私有存储 currentContext.PrivateConversationData.SetValue("UserId", activity.From.Id); currentContext.PrivateConversationData.SetValue("LastMessage", activity.Text); return FormDialog.FromForm(RequestOrder.BuildTemporaryForm); }); });
在FormBuilder里读取:
public static IForm<RequestOrder> BuildTemporaryForm() { return new FormBuilder<RequestOrder>() .Field(nameof(RequestOrder.EnquiryType), async (state) => { var currentContext = DialogContext.CurrentContext; string userId = currentContext.PrivateConversationData.GetValue<string>("UserId"); return new ValidateResult { Prompt = $"你好 {userId},请选择咨询类型:" }; }) .Field(nameof(RequestOrder.EnquiryDetails)) .Build(); }
注意:PrivateConversationData仅在当前会话有效,会话结束后数据会丢失,适合临时传递场景;而UserState/ConversationState是持久化存储,适合跨会话保留用户信息。
内容的提问来源于stack exchange,提问作者user7138342

