在瀑布对话框中捕获 Adaptive Card 提交的值
Capture values submitted by Adaptive Card in waterfall dialog
我听从了 , the comment on this issue and also 的建议。
在我的瀑布对话框中:
- 显示自适应卡
- 显示自适应卡片后立即发送文本提示
在我的主机器人里面 class:
- 将
Activity
的 Text
属性 设置为从 activity 的 Value
属性 中提取的值,如果activity 是一条包含回发数据的消息。
我曾尝试使用 AdaptiveTextInput
或 AdaptiveDateInput
作为提交值的控件,但没有任何区别。我觉得这将是一件非常愚蠢的事情..
我目前在我的瀑布中混合使用 Hero
和 Adaptive
卡片,Hero
卡片正常工作。
编辑
我已将 // !Relevant- 注释添加到我的重要部分的代码中,其余留给上下文。
所以我的问题是:是什么阻止我在自适应卡片上提交的内容正确流过 - 是我在瀑布中的显示方式有问题,卡片中的动作构建方式有问题,还是我如何处理主机器人中的操作 class?
在 AdaptiveCardService
构建我的卡片:
public List<Activity> BuildCardActivitiesFromDecisionFlow(BotDecisionFlow botDecisionFlow)
{
List<Activity> cardActivities = new List<Activity>();
foreach (Step step in botDecisionFlow.FormSchema.Steps)
{
Control control = step.Details.Control;
cardActivities.Add(CreateCardActivity(step, control));
}
return cardActivities;
}
private Activity CreateCardActivity(Step step, Control control)
{
Activity cardActivity = (Activity)Activity.CreateMessageActivity();
if (control.Type == ControlTypeEnum.RadioButton)
{
HeroCard heroCard = BuildHeroCard(step, control.DataType);
Attachment attachment = heroCard.ToAttachment();
cardActivity.Attachments.Add(attachment);
}
else if (control.Type == ControlTypeEnum.DatePicker)
{
AdaptiveCard adaptiveCard = BuildAdaptiveCard(step, control.DataType);
Attachment attachment = new Attachment
{
ContentType = AdaptiveCard.ContentType,
// Trick to get Adapative Cards to work with prompts as per https://github.com/Microsoft/botbuilder-dotnet/issues/614#issuecomment-443549810
Content = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(adaptiveCard))
};
cardActivity.Attachments.Add(attachment);
}
else
{
throw new NotImplementedException($"The {nameof(control)} with value {control} is not yet supported.");
}
return cardActivity;
}
private HeroCard BuildHeroCard(Step step, DataTypeEnum dataType)
{
string question = step.Details.Question;
HeroCard heroCard = new HeroCard
{
Text = question,
// PostBack is required to get buttons to work with prompts, also the value needs to be a string for the
// event to fire properly, as per
Buttons = step.Details.EnumValueToDisplayTextMappings.Select(e => new CardAction(ActionTypes.PostBack, e.Value, null, e.Value, e.Value, JsonConvert.SerializeObject(new DialogValueDto(step.Name, e.Key, dataType)), null)).ToList()
};
return heroCard;
}
private AdaptiveCard BuildAdaptiveCard(Step step, DataTypeEnum dataType)
{
const string ISO8601Format = "yyyy-MM-dd";
string question = step.Details.Question;
DateTime today = DateTime.Today;
string todayAsIso = today.ToString(ISO8601Format);
AdaptiveCard adaptiveCard = new AdaptiveCard("1.0")
{
Body =
{
new AdaptiveContainer
{
Items =
{
new AdaptiveTextBlock
{
Text = question,
Wrap = true
},
new AdaptiveDateInput
{
Id = "UserInput",
Value = todayAsIso,
Min = today.AddDays(-7).ToString(ISO8601Format),
Max = todayAsIso,
Placeholder = todayAsIso
}
}
}
},
Actions = new List<AdaptiveAction>
{
// !Relevant-Start
new AdaptiveSubmitAction
{
Data = new DialogValueDto(step.Name, dataType),
Title = "Confirm",
Type = "Action.Submit"
}
// !Relevant-End
}
};
return adaptiveCard;
}
在我的瀑布里面 class:
private readonly IUmbracoApiWrapper _umbracoApiWrapper;
private readonly IUmbracoResponseConverterService _umbracoResponseConverterService;
private readonly IAdaptiveCardService _adaptiveCardService;
private IStatePropertyAccessor<DynamicWaterfallState> _accessor;
private DynamicWaterfallState _state;
public DynamicWaterfallDialog(
IUmbracoApiWrapper umbracoApiWrapper,
IUmbracoResponseConverterService umbracoResponseConverterService,
IAdaptiveCardService adaptiveCardService,
UserState userState)
: base(nameof(DynamicWaterfallDialog))
{
_accessor = userState.CreateProperty<DynamicWaterfallState>(nameof(DynamicWaterfallState));
_umbracoApiWrapper = umbracoApiWrapper;
_umbracoResponseConverterService = umbracoResponseConverterService;
_adaptiveCardService = adaptiveCardService;
InitialDialogId = nameof(WaterfallDialog);
// !Relevant-Start
var waterfallSteps = new WaterfallStep[]
{
// TODO: Rename this DisplayCardAsync
UserInputStepAsync,
// TODO: Rename this ProcessCardAsync
LoopStepAsync,
};
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(InitialDialogId, waterfallSteps));
// !Relevant-End
}
// TODO: Does it make more sense for the collection of dialogs to be passed in? It depends on how this dialog is going to be called,
// maybe just passing in the ID is fine rather than having code sprinkled around to fetch the dialog collections.
public async Task<DialogTurnResult> UserInputStepAsync(WaterfallStepContext sc, CancellationToken cancellationToken)
{
// Get passed in options, need to serialise the object before we deserialise because calling .ToString on the object is unreliable
string tempData = JsonConvert.SerializeObject(sc.Options);
DynamicWaterfallDialogDto dynamicWaterfallDialogDto = JsonConvert.DeserializeObject<DynamicWaterfallDialogDto>(tempData);
// Read out data from the state
_state = await _accessor.GetAsync(sc.Context, () => new DynamicWaterfallState());
List<Activity> activityCards = _state.ActivityDialogs ?? new List<Activity>();
int dialogPosition = _state.DialogPosition;
bool flowFinished = _state.FlowFinished;
bool apiDataFetched = _state.ApiDataFetched;
if (DynamicWaterfallDialogDtoExtensions.IsDynamicWaterfallDialogDtoValid(dynamicWaterfallDialogDto) && !apiDataFetched)
{
// Fetch from API
JObject decision = await _umbracoApiWrapper.GetDecisionById(18350);
UmbracoDecisionResponseDto umbracoResponseDto = JsonConvert.DeserializeObject<UmbracoDecisionResponseDto>(decision.ToString());
BotDecisionFlow botDecisionFlow = new BotDecisionFlow(_umbracoResponseConverterService, umbracoResponseDto);
activityCards = _adaptiveCardService.BuildCardActivitiesFromDecisionFlow(botDecisionFlow);
_state.ApiDataFetched = true;
_state.ActivityDialogs = activityCards;
await _accessor.SetAsync(sc.Context, _state, cancellationToken);
}
var cardToShow = activityCards.ElementAt(dialogPosition);
_state.FlowFinished = _state.DialogPosition == activityCards.Count - 1;
_state.DialogPosition++;
await _accessor.SetAsync(sc.Context, _state, cancellationToken);
// TODO we need to determine the control type to figure out the prompt type?
// !Relevant-Start
await sc.Context.SendActivityAsync(cardToShow);
return await sc.PromptAsync(nameof(TextPrompt), new PromptOptions() { Prompt = MessageFactory.Text("") });
//return await sc.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = new Activity { Type = ActivityTypes.Message } });
// !Relevant-End
}
public async Task<DialogTurnResult> LoopStepAsync(WaterfallStepContext sc, CancellationToken cancellationToken)
{
object result = sc.Result;
DialogValueDto userInput = JsonConvert.DeserializeObject<DialogValueDto>(sc.Result.ToString());
await sc.Context.SendActivityAsync($"You selected: {userInput.UserInput}");
_state = await _accessor.GetAsync(sc.Context, () => new DynamicWaterfallState());
bool flowFinished = _state.FlowFinished;
// TODO: Do we want to do state manipulation in here?
if (!flowFinished)
{
// TODO: Do we want to pass in custom options here?
return await sc.ReplaceDialogAsync(nameof(DynamicWaterfallDialog), sc.Options, cancellationToken);
}
else
{
// TODO: We probably want to pass the state in here instead of null if we want to show outcomes etc
return await sc.EndDialogAsync(null, cancellationToken);
}
}
}
在我的主机器人里面 class:
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
// Client notifying this bot took to long to respond (timed out)
if (turnContext.Activity.Code == EndOfConversationCodes.BotTimedOut)
{
_telemetryClient.TrackTrace($"Timeout in {turnContext.Activity.ChannelId} channel: Bot took too long to respond.", Severity.Information, null);
return;
}
var dc = await _dialogs.CreateContextAsync(turnContext);
// !Relevant-Start
if (turnContext.Activity.Type == ActivityTypes.Message)
{
// Ensure that message is a postBack (like a submission from Adaptive Cards)
if (dc.Context.Activity.GetType().GetProperty("ChannelData") != null)
{
var channelData = JObject.Parse(dc.Context.Activity.ChannelData.ToString());
// TODO: Add check for type, we should only handle adaptive cards here
if (channelData.ContainsKey("postBack"))
{
var postbackActivity = dc.Context.Activity;
string text = JsonConvert.DeserializeObject<DialogValueDto>(postbackActivity.Value.ToString())?.UserInput;
// Convert the user's Adaptive Card input into the input of a Text Prompt
// Must be sent as a string
postbackActivity.Text = text;
await dc.Context.SendActivityAsync(postbackActivity);
}
}
}
// !Relevant-End
if (dc.ActiveDialog != null)
{
var result = await dc.ContinueDialogAsync();
}
else
{
await dc.BeginDialogAsync(typeof(T).Name);
}
}
我的 DialogValueD 以备不时之需:
public string StepName { get; set; }
public string UserInput { get; set; }
public DataTypeEnum DataType { get; set; }
/// <summary>
/// For JSON deserialization
/// </summary>
public DialogValueDto()
{
}
/// <summary>
/// For use with DateTime deserialization.
/// The control id is set to "UserInput"
/// so this property will be set automatically
/// </summary>
public DialogValueDto(string stepName, DataTypeEnum dataType)
{
StepName = stepName;
DataType = dataType;
}
/// <summary>
/// This is the constructor that should be used most
/// of the time
/// </summary>
public DialogValueDto(string stepName, string userInput, DataTypeEnum dataType)
{
StepName = stepName;
UserInput = userInput;
DataType = dataType;
}
有趣的是,当我设置 属性 activity.
我的问题原来是 two-fold
1) 在我的 DialogBot
文件中的 OnTurnAsync
方法中,我有:
var postbackActivity = dc.Context.Activity;
string text = JsonConvert.DeserializeObject<DialogValueDto>(postbackActivity.Value.ToString())?.UserInput;
postbackActivity.Text = text;
await dc.Context.SendActivityAsync(postbackActivity);
我正在设置 postBackActivity
变量的 Text
属性 而不是直接在 dc.Context.Activity
上直接设置 Text
属性。因为我通过 SendActivityAsync
发送变量,所以它掩盖了这个错误,因为我得到了我想要传递给 MainDialog
class 中的 OnEventAsync
方法的值。
正确的方法是直接在上下文中设置它,而不是在它的副本上设置(DOH!)
dc.Context.Activity.Text = text
2) 在我的 MainDialog
class 中的 OnEventAsync
方法中,我有一个空块,它捕获了响应但什么也没做(它需要调用 await dc.ContinueDialogAsync()
).但是,这已经由虚拟助手模板中的现有代码块处理,我的空块防止被击中。
object value = dc.Context.Activity.Value;
if (condition)
{
// do nothing
}
else if (value.GetType() == typeof(JObject))
{
// code from the Virtual Assistant template to check the values passed through
var submit = JObject.Parse(value.ToString());
// more template code
// Template code
if (forward)
{
var result = await dc.ContinueDialogAsync();
if (result.Status == DialogTurnStatus.Complete)
{
await CompleteAsync(dc);
}
}
}
一旦我删除了空的 if
块,它就会进入所需的代码(前面的部分)。
更改列表:
DynamicWaterfallDialog:
public DynamicWaterfallDialog(
...
)
: base(nameof(DynamicWaterfallDialog))
{
...
InitialDialogId = nameof(WaterfallDialog);
var waterfallSteps = new WaterfallStep[]
{
UserInputStepAsync,
LoopStepAsync,
};
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(InitialDialogId, waterfallSteps));
}
DialogBot:
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
...
var dc = await _dialogs.CreateContextAsync(turnContext);
if (dc.Context.Activity.Type == ActivityTypes.Message)
{
// Ensure that message is a postBack (like a submission from Adaptive Cards)
if (dc.Context.Activity.GetType().GetProperty("ChannelData") != null)
{
JObject channelData = JObject.Parse(dc.Context.Activity.ChannelData.ToString());
Activity postbackActivity = dc.Context.Activity;
if (channelData.ContainsKey("postBack") && postbackActivity.Value != null)
{
DialogValueDto dialogValueDto = JsonConvert.DeserializeObject<DialogValueDto>(postbackActivity.Value.ToString());
// Only set the text property for adaptive cards because the value we want, and the value that the user submits comes through the
// on the Value property for adaptive cards, instead of the text property like everything else
if (DialogValueDtoExtensions.IsValidDialogValueDto(dialogValueDto) && dialogValueDto.CardType == CardTypeEnum.Adaptive)
{
// Convert the user's Adaptive Card input into the input of a Text Prompt, must be sent as a string
dc.Context.Activity.Text = JsonConvert.SerializeObject(dialogValueDto);
// We don't need to post the text as per because this is automatically handled inside the
// OnEventAsync method of MainDialog.cs
}
}
}
}
if (dc.ActiveDialog != null)
{
var result = await dc.ContinueDialogAsync();
}
else
{
await dc.BeginDialogAsync(typeof(T).Name);
}
}
MainDialog:
protected override async Task OnEventAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
{
object value = dc.Context.Activity.Value;
if (value.GetType() == typeof(JObject))
{
var submit = JObject.Parse(value.ToString());
if (value != null)
{
// Null propagation here is to handle things like dynamic adaptive cards that submit objects
string action = submit["action"]?.ToString();
...
}
var forward = true;
var ev = dc.Context.Activity.AsEventActivity();
// Null propagation here is to handle things like dynamic adaptive cards that may not convert into an EventActivity
if (!string.IsNullOrWhiteSpace(ev?.Name))
{
...
}
if (forward)
{
var result = await dc.ContinueDialogAsync();
if (result.Status == DialogTurnStatus.Complete)
{
await CompleteAsync(dc);
}
}
}
}
我想我希望在上下文中设置文本 属性 以自动触发我的 LoopStepAsync
(DynamicWaterfallDialog) 处理程序,而不是落入 OnEventAsync (MainDialog)。我知道我需要在某个地方打电话给 ContinueDialogAsync
并且应该更加怀疑我问题的最后一段:
Interestingly enough my OnEventAsync function of my MainDialog (the one which is wired up in Startup.cs via services.AddTransient>();) gets fired when I set the text property of the activity.
那么近,那么远。希望这有助于将来的其他人。
Link 我发现有帮助的是:
我听从了
在我的瀑布对话框中:
- 显示自适应卡
- 显示自适应卡片后立即发送文本提示
在我的主机器人里面 class:
- 将
Activity
的Text
属性 设置为从 activity 的Value
属性 中提取的值,如果activity 是一条包含回发数据的消息。
我曾尝试使用 AdaptiveTextInput
或 AdaptiveDateInput
作为提交值的控件,但没有任何区别。我觉得这将是一件非常愚蠢的事情..
我目前在我的瀑布中混合使用 Hero
和 Adaptive
卡片,Hero
卡片正常工作。
编辑
我已将 // !Relevant- 注释添加到我的重要部分的代码中,其余留给上下文。
所以我的问题是:是什么阻止我在自适应卡片上提交的内容正确流过 - 是我在瀑布中的显示方式有问题,卡片中的动作构建方式有问题,还是我如何处理主机器人中的操作 class?
在 AdaptiveCardService
构建我的卡片:
public List<Activity> BuildCardActivitiesFromDecisionFlow(BotDecisionFlow botDecisionFlow)
{
List<Activity> cardActivities = new List<Activity>();
foreach (Step step in botDecisionFlow.FormSchema.Steps)
{
Control control = step.Details.Control;
cardActivities.Add(CreateCardActivity(step, control));
}
return cardActivities;
}
private Activity CreateCardActivity(Step step, Control control)
{
Activity cardActivity = (Activity)Activity.CreateMessageActivity();
if (control.Type == ControlTypeEnum.RadioButton)
{
HeroCard heroCard = BuildHeroCard(step, control.DataType);
Attachment attachment = heroCard.ToAttachment();
cardActivity.Attachments.Add(attachment);
}
else if (control.Type == ControlTypeEnum.DatePicker)
{
AdaptiveCard adaptiveCard = BuildAdaptiveCard(step, control.DataType);
Attachment attachment = new Attachment
{
ContentType = AdaptiveCard.ContentType,
// Trick to get Adapative Cards to work with prompts as per https://github.com/Microsoft/botbuilder-dotnet/issues/614#issuecomment-443549810
Content = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(adaptiveCard))
};
cardActivity.Attachments.Add(attachment);
}
else
{
throw new NotImplementedException($"The {nameof(control)} with value {control} is not yet supported.");
}
return cardActivity;
}
private HeroCard BuildHeroCard(Step step, DataTypeEnum dataType)
{
string question = step.Details.Question;
HeroCard heroCard = new HeroCard
{
Text = question,
// PostBack is required to get buttons to work with prompts, also the value needs to be a string for the
// event to fire properly, as per
Buttons = step.Details.EnumValueToDisplayTextMappings.Select(e => new CardAction(ActionTypes.PostBack, e.Value, null, e.Value, e.Value, JsonConvert.SerializeObject(new DialogValueDto(step.Name, e.Key, dataType)), null)).ToList()
};
return heroCard;
}
private AdaptiveCard BuildAdaptiveCard(Step step, DataTypeEnum dataType)
{
const string ISO8601Format = "yyyy-MM-dd";
string question = step.Details.Question;
DateTime today = DateTime.Today;
string todayAsIso = today.ToString(ISO8601Format);
AdaptiveCard adaptiveCard = new AdaptiveCard("1.0")
{
Body =
{
new AdaptiveContainer
{
Items =
{
new AdaptiveTextBlock
{
Text = question,
Wrap = true
},
new AdaptiveDateInput
{
Id = "UserInput",
Value = todayAsIso,
Min = today.AddDays(-7).ToString(ISO8601Format),
Max = todayAsIso,
Placeholder = todayAsIso
}
}
}
},
Actions = new List<AdaptiveAction>
{
// !Relevant-Start
new AdaptiveSubmitAction
{
Data = new DialogValueDto(step.Name, dataType),
Title = "Confirm",
Type = "Action.Submit"
}
// !Relevant-End
}
};
return adaptiveCard;
}
在我的瀑布里面 class:
private readonly IUmbracoApiWrapper _umbracoApiWrapper;
private readonly IUmbracoResponseConverterService _umbracoResponseConverterService;
private readonly IAdaptiveCardService _adaptiveCardService;
private IStatePropertyAccessor<DynamicWaterfallState> _accessor;
private DynamicWaterfallState _state;
public DynamicWaterfallDialog(
IUmbracoApiWrapper umbracoApiWrapper,
IUmbracoResponseConverterService umbracoResponseConverterService,
IAdaptiveCardService adaptiveCardService,
UserState userState)
: base(nameof(DynamicWaterfallDialog))
{
_accessor = userState.CreateProperty<DynamicWaterfallState>(nameof(DynamicWaterfallState));
_umbracoApiWrapper = umbracoApiWrapper;
_umbracoResponseConverterService = umbracoResponseConverterService;
_adaptiveCardService = adaptiveCardService;
InitialDialogId = nameof(WaterfallDialog);
// !Relevant-Start
var waterfallSteps = new WaterfallStep[]
{
// TODO: Rename this DisplayCardAsync
UserInputStepAsync,
// TODO: Rename this ProcessCardAsync
LoopStepAsync,
};
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(InitialDialogId, waterfallSteps));
// !Relevant-End
}
// TODO: Does it make more sense for the collection of dialogs to be passed in? It depends on how this dialog is going to be called,
// maybe just passing in the ID is fine rather than having code sprinkled around to fetch the dialog collections.
public async Task<DialogTurnResult> UserInputStepAsync(WaterfallStepContext sc, CancellationToken cancellationToken)
{
// Get passed in options, need to serialise the object before we deserialise because calling .ToString on the object is unreliable
string tempData = JsonConvert.SerializeObject(sc.Options);
DynamicWaterfallDialogDto dynamicWaterfallDialogDto = JsonConvert.DeserializeObject<DynamicWaterfallDialogDto>(tempData);
// Read out data from the state
_state = await _accessor.GetAsync(sc.Context, () => new DynamicWaterfallState());
List<Activity> activityCards = _state.ActivityDialogs ?? new List<Activity>();
int dialogPosition = _state.DialogPosition;
bool flowFinished = _state.FlowFinished;
bool apiDataFetched = _state.ApiDataFetched;
if (DynamicWaterfallDialogDtoExtensions.IsDynamicWaterfallDialogDtoValid(dynamicWaterfallDialogDto) && !apiDataFetched)
{
// Fetch from API
JObject decision = await _umbracoApiWrapper.GetDecisionById(18350);
UmbracoDecisionResponseDto umbracoResponseDto = JsonConvert.DeserializeObject<UmbracoDecisionResponseDto>(decision.ToString());
BotDecisionFlow botDecisionFlow = new BotDecisionFlow(_umbracoResponseConverterService, umbracoResponseDto);
activityCards = _adaptiveCardService.BuildCardActivitiesFromDecisionFlow(botDecisionFlow);
_state.ApiDataFetched = true;
_state.ActivityDialogs = activityCards;
await _accessor.SetAsync(sc.Context, _state, cancellationToken);
}
var cardToShow = activityCards.ElementAt(dialogPosition);
_state.FlowFinished = _state.DialogPosition == activityCards.Count - 1;
_state.DialogPosition++;
await _accessor.SetAsync(sc.Context, _state, cancellationToken);
// TODO we need to determine the control type to figure out the prompt type?
// !Relevant-Start
await sc.Context.SendActivityAsync(cardToShow);
return await sc.PromptAsync(nameof(TextPrompt), new PromptOptions() { Prompt = MessageFactory.Text("") });
//return await sc.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = new Activity { Type = ActivityTypes.Message } });
// !Relevant-End
}
public async Task<DialogTurnResult> LoopStepAsync(WaterfallStepContext sc, CancellationToken cancellationToken)
{
object result = sc.Result;
DialogValueDto userInput = JsonConvert.DeserializeObject<DialogValueDto>(sc.Result.ToString());
await sc.Context.SendActivityAsync($"You selected: {userInput.UserInput}");
_state = await _accessor.GetAsync(sc.Context, () => new DynamicWaterfallState());
bool flowFinished = _state.FlowFinished;
// TODO: Do we want to do state manipulation in here?
if (!flowFinished)
{
// TODO: Do we want to pass in custom options here?
return await sc.ReplaceDialogAsync(nameof(DynamicWaterfallDialog), sc.Options, cancellationToken);
}
else
{
// TODO: We probably want to pass the state in here instead of null if we want to show outcomes etc
return await sc.EndDialogAsync(null, cancellationToken);
}
}
}
在我的主机器人里面 class:
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
// Client notifying this bot took to long to respond (timed out)
if (turnContext.Activity.Code == EndOfConversationCodes.BotTimedOut)
{
_telemetryClient.TrackTrace($"Timeout in {turnContext.Activity.ChannelId} channel: Bot took too long to respond.", Severity.Information, null);
return;
}
var dc = await _dialogs.CreateContextAsync(turnContext);
// !Relevant-Start
if (turnContext.Activity.Type == ActivityTypes.Message)
{
// Ensure that message is a postBack (like a submission from Adaptive Cards)
if (dc.Context.Activity.GetType().GetProperty("ChannelData") != null)
{
var channelData = JObject.Parse(dc.Context.Activity.ChannelData.ToString());
// TODO: Add check for type, we should only handle adaptive cards here
if (channelData.ContainsKey("postBack"))
{
var postbackActivity = dc.Context.Activity;
string text = JsonConvert.DeserializeObject<DialogValueDto>(postbackActivity.Value.ToString())?.UserInput;
// Convert the user's Adaptive Card input into the input of a Text Prompt
// Must be sent as a string
postbackActivity.Text = text;
await dc.Context.SendActivityAsync(postbackActivity);
}
}
}
// !Relevant-End
if (dc.ActiveDialog != null)
{
var result = await dc.ContinueDialogAsync();
}
else
{
await dc.BeginDialogAsync(typeof(T).Name);
}
}
我的 DialogValueD 以备不时之需:
public string StepName { get; set; }
public string UserInput { get; set; }
public DataTypeEnum DataType { get; set; }
/// <summary>
/// For JSON deserialization
/// </summary>
public DialogValueDto()
{
}
/// <summary>
/// For use with DateTime deserialization.
/// The control id is set to "UserInput"
/// so this property will be set automatically
/// </summary>
public DialogValueDto(string stepName, DataTypeEnum dataType)
{
StepName = stepName;
DataType = dataType;
}
/// <summary>
/// This is the constructor that should be used most
/// of the time
/// </summary>
public DialogValueDto(string stepName, string userInput, DataTypeEnum dataType)
{
StepName = stepName;
UserInput = userInput;
DataType = dataType;
}
有趣的是,当我设置 属性 activity.
我的问题原来是 two-fold
1) 在我的 DialogBot
文件中的 OnTurnAsync
方法中,我有:
var postbackActivity = dc.Context.Activity;
string text = JsonConvert.DeserializeObject<DialogValueDto>(postbackActivity.Value.ToString())?.UserInput;
postbackActivity.Text = text;
await dc.Context.SendActivityAsync(postbackActivity);
我正在设置 postBackActivity
变量的 Text
属性 而不是直接在 dc.Context.Activity
上直接设置 Text
属性。因为我通过 SendActivityAsync
发送变量,所以它掩盖了这个错误,因为我得到了我想要传递给 MainDialog
class 中的 OnEventAsync
方法的值。
正确的方法是直接在上下文中设置它,而不是在它的副本上设置(DOH!)
dc.Context.Activity.Text = text
2) 在我的 MainDialog
class 中的 OnEventAsync
方法中,我有一个空块,它捕获了响应但什么也没做(它需要调用 await dc.ContinueDialogAsync()
).但是,这已经由虚拟助手模板中的现有代码块处理,我的空块防止被击中。
object value = dc.Context.Activity.Value;
if (condition)
{
// do nothing
}
else if (value.GetType() == typeof(JObject))
{
// code from the Virtual Assistant template to check the values passed through
var submit = JObject.Parse(value.ToString());
// more template code
// Template code
if (forward)
{
var result = await dc.ContinueDialogAsync();
if (result.Status == DialogTurnStatus.Complete)
{
await CompleteAsync(dc);
}
}
}
一旦我删除了空的 if
块,它就会进入所需的代码(前面的部分)。
更改列表:
DynamicWaterfallDialog:
public DynamicWaterfallDialog(
...
)
: base(nameof(DynamicWaterfallDialog))
{
...
InitialDialogId = nameof(WaterfallDialog);
var waterfallSteps = new WaterfallStep[]
{
UserInputStepAsync,
LoopStepAsync,
};
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(InitialDialogId, waterfallSteps));
}
DialogBot:
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
...
var dc = await _dialogs.CreateContextAsync(turnContext);
if (dc.Context.Activity.Type == ActivityTypes.Message)
{
// Ensure that message is a postBack (like a submission from Adaptive Cards)
if (dc.Context.Activity.GetType().GetProperty("ChannelData") != null)
{
JObject channelData = JObject.Parse(dc.Context.Activity.ChannelData.ToString());
Activity postbackActivity = dc.Context.Activity;
if (channelData.ContainsKey("postBack") && postbackActivity.Value != null)
{
DialogValueDto dialogValueDto = JsonConvert.DeserializeObject<DialogValueDto>(postbackActivity.Value.ToString());
// Only set the text property for adaptive cards because the value we want, and the value that the user submits comes through the
// on the Value property for adaptive cards, instead of the text property like everything else
if (DialogValueDtoExtensions.IsValidDialogValueDto(dialogValueDto) && dialogValueDto.CardType == CardTypeEnum.Adaptive)
{
// Convert the user's Adaptive Card input into the input of a Text Prompt, must be sent as a string
dc.Context.Activity.Text = JsonConvert.SerializeObject(dialogValueDto);
// We don't need to post the text as per because this is automatically handled inside the
// OnEventAsync method of MainDialog.cs
}
}
}
}
if (dc.ActiveDialog != null)
{
var result = await dc.ContinueDialogAsync();
}
else
{
await dc.BeginDialogAsync(typeof(T).Name);
}
}
MainDialog:
protected override async Task OnEventAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
{
object value = dc.Context.Activity.Value;
if (value.GetType() == typeof(JObject))
{
var submit = JObject.Parse(value.ToString());
if (value != null)
{
// Null propagation here is to handle things like dynamic adaptive cards that submit objects
string action = submit["action"]?.ToString();
...
}
var forward = true;
var ev = dc.Context.Activity.AsEventActivity();
// Null propagation here is to handle things like dynamic adaptive cards that may not convert into an EventActivity
if (!string.IsNullOrWhiteSpace(ev?.Name))
{
...
}
if (forward)
{
var result = await dc.ContinueDialogAsync();
if (result.Status == DialogTurnStatus.Complete)
{
await CompleteAsync(dc);
}
}
}
}
我想我希望在上下文中设置文本 属性 以自动触发我的 LoopStepAsync
(DynamicWaterfallDialog) 处理程序,而不是落入 OnEventAsync (MainDialog)。我知道我需要在某个地方打电话给 ContinueDialogAsync
并且应该更加怀疑我问题的最后一段:
Interestingly enough my OnEventAsync function of my MainDialog (the one which is wired up in Startup.cs via services.AddTransient>();) gets fired when I set the text property of the activity.
那么近,那么远。希望这有助于将来的其他人。
Link 我发现有帮助的是: