第二次使用同一张卡时自适应卡id冲突



我有一个对话框(bot框架对话框),它的工作伟大的第一次用户进入它。对话框显示用户与自适应卡包含输入。它的id属性设置为"substitution"。当用户第二次进入相同的对话框时,ChoiseSet被正确显示,但在动作上。提交,我得到id "substitution"的id冲突。

我的问题类似于这个用户的问题:https://github.com/microsoft/AdaptiveCards/issues/3225#issuecomment-710626684。但是他对这个问题的解决办法对我的情况没有效果。我总是得到相同的错误。

我知道id应该是唯一的但我不应该被强制动态设置相同的ChoiceSet卡片的id

My adaptive card:

{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.3",
"body": [
{
"type": "TextBlock",
"text": "<to-be-set-with-code>"
},
{
"type": "Input.ChoiceSet",
"id": "substitution",
"style": "compact",
"isMultiSelect": false,
"value": "1",
"choices": []
}
],
"actions": [
{
"type": "Action.Submit",
"title": "OK",
"data": {
"msteams": {
"type": "messageBack",
"text": "back"
}
}
}
]
}

我的代码(内部对话步骤):

private async Task<DialogTurnResult> SubstitutionStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var adaptiveCardJson = await File.ReadAllTextAsync(Cards.TextWithChoiceSetCard, cancellationToken);
var card = AdaptiveCard.FromJson(adaptiveCardJson).Card;
var textBlock = card.Body[0] as AdaptiveTextBlock;
textBlock.Text = "Who will be your substitution?";
// get current teams user info
var userStateAccessors = UserState.CreateProperty<UserProfile>(nameof(UserProfile));
var userProfile = await userStateAccessors.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);
// generate choices
var adaptiveChoices = new List<AdaptiveChoice>();
foreach (var user in ApiWrapper.RetrieveInstanceUsers())
{
// do not include current user and bot user
if (user.UserName == ClientData.Username || string.Equals(user.Email, userProfile.UserPrincipalName, StringComparison.CurrentCultureIgnoreCase)) continue;
adaptiveChoices.Add(new AdaptiveChoice
{
Title = $"{user.UserName} ({user.Email})",
Value = user.ToJson()
});
}
var choiceSet = card.Body[1] as AdaptiveChoiceSetInput;
choiceSet.Choices = adaptiveChoices;

var attachment = new Attachment
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = card
};
var reply = stepContext.Context.Activity.CreateReply();
reply.Attachments = new List<Attachment> {attachment};
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions {Prompt = reply}, cancellationToken);
}

我找到了一个解决方法来修复异常:
AdaptiveCards: Collision detected for id 'cardFieldId'

假设我们有这些WaterfallSteps:

  • InitialStepAsync
  • CardStepAsync
  • FinalStepAsync

假设我们有这样一张卡片:

{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.3",
"body": 
[
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": 2,
"items": [
{
"type": "TextBlock",
"wrap": true,
"text": "Select an assistance."
},
{
"type": "Input.ChoiceSet",
"choices": [],
"placeholder": "---",
"id": "assistanceId"
}
]
}
]
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "ActionSet",
"actions": [
{
"type": "Action.Submit",
"title": "CANCEL",
"id": "bnCancel"
}
]
}
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "ActionSet",
"actions": [
{
"type": "Action.Submit",
"title": "CONFIRM",
"style": "positive",
"id": "bnConfirm"
}
]
}
]
}
]
}
]
}

步骤源代码:

private async Task<DialogTurnResult> InizialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var message = "Insert YES or NO.";
var promptMessage = MessageFactory.Text(message, message, InputHints.ExpectingInput);
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
}
private async Task<DialogTurnResult> CardStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var actionTypes = (List<ActionTypeFullItem>)stepContext.Options;
var txt = (string)stepContext.Result;
if (txt.Equals("YES", StringComparison.OrdinalIgnoreCase))
{
// If user say 'YES', show card.
// Retrieve JSON of our card from EmbeddedResource
var jCard = await Utils.GetCardJsonAsync("assistanceType");
// Workaround BUG: https://stackoverflow.com/questions/69358336/adaptive-card-id-collision-when-using-same-card-for-second-time
// Replace 'FIELDID' with 'FIELDID_RANDOMGUID'.
var chooseFieldId = $"assistanceId_{Guid.NewGuid()}";
var bnCancelFieldId = $"bnCancel_{Guid.NewGuid()}";
var bnConfirmFieldId = $"bnConfirm_{Guid.NewGuid()}";
jCard = jCard.Replace("assistanceId", chooseFieldId);
jCard = jCard.Replace("bnCancel", bnCancelFieldId);
jCard = jCard.Replace("bnConfirm", bnConfirmFieldId);
// Create the Adaptive Card from JSON
var card = AdaptiveCard.FromJson(jCard).Card;
// Popolate our choose field (extensions method created by me)
var chooses = new List<AdaptiveChoice>();
foreach (var actionType in actionTypes)
{
chooses.Add(new AdaptiveChoice
{
Title = actionType.Title,
Value = actionType.Id.ToString()
});
}
card.SetChoiseField(chooseFieldId, chooses);
// Popolate cancel button submit data (extensions method created by me)
// We need 'assistanceFieldId' to retrieve the value of choose filed in the next step.
card.AddSubmitData(bnCancelFieldId, new Dictionary<string, object>
{
{ "confirm", false },
{ "assistanceFieldId", chooseFieldId }
});
// Popolate confirm button submit data (extensions method created by me)
// We need 'assistanceFieldId' to retrieve the value of choose filed in the next step.
card.AddSubmitData(bnConfirmFieldId, new Dictionary<string, object>
{
{ "confirm", true },
{ "assistanceFieldId", chooseFieldId }
});
// Prompt card
return await stepContext.PromptCardAsync(card, cancellationToken);
}
return await stepContext.NextAsync(new { confirm = false }, cancellationToken);
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var actionTypes = (List<ActionTypeFullItem>)stepContext.Options;
var selectedActionType = default(ActionTypeFullItem);
// Example JSON result
// { "confirm":true, "assistanceFieldId": "assistanceId_d68fbc77-d96e-4d1c-b3eb-4d4c31ccf2af", "assistanceId_d68fbc77-d96e-4d1c-b3eb-4d4c31ccf2af":"19" }
var jObject = Newtonsoft.Json.Linq.JObject.Parse((string)stepContext.Result);
if (jObject.ContainsKey("confirm") && !(bool)jObject["confirm"])
{
// User has clicked the 'cancel' button -> go back to the first step.
return await stepContext.ReplaceDialogAsync(InitialDialogId, actionTypes, cancellationToken);
}
var assistanceFieldId = (string)jObject["assistanceFieldId"];
if (selectedActionType == null)
{
selectedActionType = actionTypes.SingleOrDefault(a => a.Id.ToString().Equals((string)jObject[assistanceFieldId], StringComparison.OrdinalIgnoreCase));
}
return await stepContext.EndDialogAsync(selectedActionType, cancellationToken);
}

这个想法很简单,而不是总是为每个字段保持相同的ID,我们添加一个随机的guid,总是有一个不同的ID。

最新更新