// TODO: Incorporate value preservation into other updates made to Adaptive Cards public async Task PreserveValuesAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) { BotAssert.ContextNotNull(turnContext); if (turnContext.GetIncomingActionData() is JObject data) { var matchResult = await GetDataMatchAsync(turnContext, cancellationToken).ConfigureAwait(false); if (matchResult.SavedActivity != null && matchResult.SavedAttachment?.ContentType.EqualsCI(ContentTypes.AdaptiveCard) == true) { var changed = false; // The content must be non-null or else the attachment couldn't have been a match matchResult.SavedAttachment.Content = matchResult.SavedAttachment.Content.ToJObjectAndBack( card => { // Iterate through all inputs in the card foreach (var input in AdaptiveCardUtil.GetAdaptiveInputs(card)) { var id = AdaptiveCardUtil.GetAdaptiveInputId(input); var inputValue = data.GetValue(id); input.SetValue(AdaptiveProperties.Value, inputValue); changed = true; } }); if (changed) { // The changes to the attachment will already be reflected in the activity await UpdateActivityAsync(turnContext, matchResult.SavedActivity, cancellationToken).ConfigureAwait(false); } } } }
private async Task <DataMatchResult> GetDataMatchAsync( ITurnContext turnContext, CancellationToken cancellationToken = default) { var result = new DataMatchResult(); if (!(turnContext.GetIncomingActionData() is JObject incomingData)) { return(result); } var state = await GetStateAsync(turnContext, cancellationToken).ConfigureAwait(false); var couldBeFromAdaptiveCard = turnContext.Activity.Value.ToJObject() is JObject; // Iterate over all saved activities that contain any of the action data ID's from the incoming action data foreach (var savedActivity in state.SavedActivities .Where(activity => activity?.Attachments?.Any() == true)) { foreach (var savedAttachment in savedActivity.Attachments.WhereNotNull()) { if (savedAttachment.ContentType.EqualsCI(ContentTypes.AdaptiveCard)) { if (couldBeFromAdaptiveCard && savedAttachment.Content.ToJObject() is JObject savedAdaptiveCard) { // For Adaptive Cards we need to check the inputs var inputsMatch = true; var dataWithoutInputValues = incomingData.DeepClone() as JObject; // First, determine what matching submit action data is expected to look like // by taking the incoming data and removing the values associated with // the inputs found in the Adaptive Card foreach (var inputId in AdaptiveCardUtil.GetAdaptiveInputs(savedAdaptiveCard) .Select(AdaptiveCardUtil.GetAdaptiveInputId)) { // If the Adaptive Card is poorly designed, // the same input ID might show up multiple times. // Therefore we're checking if the original incoming data // contained the ID, because the inputs might still // match even if this input was already removed. if (incomingData.ContainsKey(inputId)) { // Removing a property that doesn't exist // will not throw an exception dataWithoutInputValues.Remove(inputId); } else { inputsMatch = false; break; } } // Second, if all the input ID's found in the card were present in the incoming data // then check each submit action in the card to see if its data matches the incoming data if (inputsMatch) { CardTree.Recurse( savedAdaptiveCard, (JObject savedSubmitAction) => { var submitActionData = savedSubmitAction.GetValue( AdaptiveProperties.Data) ?? new JObject(); if (JToken.DeepEquals(submitActionData, dataWithoutInputValues)) { result.Add(savedActivity, savedAttachment, savedSubmitAction); } }, TreeNodeType.AdaptiveCard, TreeNodeType.SubmitAction); } } } else { // For Bot Framework cards that are not Adaptive Cards CardTree.Recurse( savedAttachment, (CardAction savedCardAction) => { var savedData = savedCardAction.Value.ToJObject(true); // This will not throw an exception if the saved action data is null if (JToken.DeepEquals(savedData, incomingData)) { result.Add(savedActivity, savedAttachment, savedCardAction); } }, TreeNodeType.Attachment, TreeNodeType.CardAction); } } } return(result); }