简体   繁体   English

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

[英]Adaptive card id collision when using same card for second time

I have a dialog (bot framework dialog) which works great the first time user enters it.我有一个对话框(机器人框架对话框),它在用户第一次输入时效果很好。 Dialog displays user with an adaptive card containing Input.ChoiceSet which has a id property set to "substitution".对话框向用户显示包含 Input.ChoiceSet 的自适应卡片,该卡片的 id 属性设置为“替换”。 When user enters the same dialog for the second time ChoiseSet gets displayed properly but on Action.Submit I get an id collision for id "substitution".当用户第二次进入同一个对话框时,ChoiseSet 正确显示,但在 Action.Submit 上,我收到了 id“替换”的 id 冲突。

My issue is similar to issue of this user: https://github.com/microsoft/AdaptiveCards/issues/3225#issuecomment-710626684 .我的问题类似于该用户的问题: https : //github.com/microsoft/AdaptiveCards/issues/3225#issuecomment-710626684 But his solution to the problem has no effect for my case.但是他对问题的解决方案对我的情况没有影响。 I keep getting the same error.我不断收到同样的错误。

I get that id should be unique but I shouldn't be forced to dynamically set id of the same ChoiceSet card.我知道 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"
        }
      }
    }
  ]
}

My code (inside dialog step):我的代码(在对话步骤内):

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);
}

I found a workaround to fix the exception:我找到了解决异常的解决方法:
AdaptiveCards: Collision detected for id 'cardFieldId' . AdaptiveCards:检测到 ID 'cardFieldId' 的冲突

Let's say we have these WaterfallSteps:假设我们有这些 WaterfallSteps:

  • InitialStepAsync初始步骤异步
  • CardStepAsync卡步异步
  • FinalStepAsync最后一步异步

And let's say that we have this card:假设我们有这张卡:

{
    "$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"
                      }
                    ]
                  }
                ]
              }
            ]
        }
    ]
}

The source code of steps:步骤源码:

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);
}

The idea is very simple, instead of always keeping the same ID for each field, we add a random guid to always have a different ID.这个想法很简单,我们不是总是为每个字段保持相同的 ID,而是添加一个随机的 guid 以始终具有不同的 ID。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM