3

I'm using ms botbuilder v 4 I'm using webcontrol, webchat.js, latest, react Case is pretty trivial: I want to show list of possible values in dropdown, values will be dynamic (comes from API, i need Titles and Values (Ids) there. Then when user selects some item and clicks OK i want to get value (Id) and work further with that. As i got it for now only way to show dropdown is using adaptive cards, in v3 there was an option to use adaptive cards in prompts and it also planned for next version: https://github.com/Microsoft/botbuilder-dotnet/issues/1170 But for now only woraround for that is exaplained here: https://github.com/Microsoft/botbuilder-dotnet/issues/614 , with just list of string everything's working fine, but if i want to store keyvalue pairs (for IDs) i'm not able to do that cos Choices in PromptOptions only accepts list of string (will show below). So only workaround i'm using now is to store whole collection of values and after getting the result go and find it's id. Is there more convinient solution for that? Here's the code:

var choicesInputs = _teams.Select(s => new AdaptiveChoice { Title = s.Value, Value = s.Value}).ToList();

var card = new AdaptiveCard
{
    Version = new AdaptiveSchemaVersion(1, 0),
    Body =
    {
        new AdaptiveTextBlock("Select a team to assign your ticket"),
        new AdaptiveChoiceSetInput
        {
            Choices = choicesInputs,
            Id = "setId",
            Style = AdaptiveChoiceInputStyle.Compact,
            IsMultiSelect = false
        }
    },
    Actions = new List<AdaptiveAction>
    {
        new AdaptiveSubmitAction
        {
            Title = "Ok",
            Type = "Action.Submit"
        }
    }
};

signInPhoneState.Teams = _teams;

return await stepcontext.PromptAsync(
    "SelectGroupCardDialog",
    new PromptOptions
    {
            Choices = ChoiceFactory.ToChoices(_teams.Select(pair => pair.Value).ToList()),
        Prompt = (Activity) MessageFactory.Attachment(new Attachment
        {
            ContentType = AdaptiveCard.ContentType,
            Content = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(card))
        })
    },
    cancellationtoken);

// . . .

var selectedTeamId = signInPhoneState.Teams.FirstOrDefault(pair => pair.Value == sel).Key;

Quick side question (but related in terms i'm using it for workaround): What is the easiest way to persist some variable though dialog? If i remember correectly In v3 it was as simple as marking a value as public and marking dialog as serializable and that's it, now as i get it you need to create special accessor for each dialog, dublicate property there and manage the state of it, is it correct? Thanks

Kyle Delaney
  • 11,616
  • 6
  • 39
  • 66
Andrey Stepanov
  • 311
  • 2
  • 11
  • So `_teams` is a dictionary where the keys are team ID's and the values are team names? What you're doing seems fine, but why not just use the keys instead of the values as the `AdaptiveChoice.Value` properties? Then when the user submits a choice you'd get the team ID in the activity's value, right? – Kyle Delaney Jan 09 '19 at 21:59
  • Are you still working on this? – Kyle Delaney Jan 21 '19 at 23:38
  • @KyleDelaney hi sorry for the delay. You meant to send Id as Value? smth like this: var choicesInputs = _teams.Select(s => new AdaptiveChoice { Title = s.Value, Value = JsonConvert.SerializeObject(s)}).ToList(); ... return await stepcontext.PromptAsync( "SelectGroupCardDialog", new PromptOptions { Choices = ToChoices(_teams.Select(pair => pair.Key.ToString(), – Andrey Stepanov Jan 23 '19 at 12:24
  • But in this case in response i get ONLY id and not able to get name of team, i need both. I've tried to add Synonyms, but not working, still getting Id in synonyms. Only workaround i found while we can show one value and send another then we can serialize KeyValuePair and deserialize it: var choicesInputs = _teams.Select(s => new AdaptiveChoice { Title = s.Value, Value = JsonConvert.SerializeObject(s)}).ToList(); .... return await stepcontext.PromptAsync( "SelectGroupCardDialog", – Andrey Stepanov Jan 23 '19 at 12:25
  • new PromptOptions { Choices = ToChoices(_teams.Select(pair => JsonConvert.SerializeObject(pair)).ToList()), .... public async Task ShowTicketInfo(WaterfallStepContext sc, CancellationToken cancellationToken) { var sel = ((FoundChoice)sc.Result).Value; var selectedTeam = JsonConvert.DeserializeObject>(sel); Can you confirm that in next release when prompts with adaptive cards will be added we will have regular Key, Value response? – Andrey Stepanov Jan 23 '19 at 12:26
  • Is `_teams` a dictionary or not? – Kyle Delaney Jan 23 '19 at 21:54
  • it is a dictionary – Andrey Stepanov Jan 27 '19 at 14:56
  • Let me see if I have all the information here. You have a dictionary named `_teams` that uses a team ID as each pair's key and a team name as its value. You have a choice set in an adaptive card that allows users to select a team, and you're using a team name as the value of each choice. Once you've retrieved the team name from the user's selection, you're searching `_teams` for the team's ID because you need both the ID and the name. Is that correct? – Kyle Delaney Jan 28 '19 at 18:48
  • If that is correct, you need to understand that with the way dictionaries work, it is much easier and faster to access a value in a dictionary using a key than it is to access a key in a dictionary using a value. [The whole point of dictionaries is to access values using keys.](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2) So if you were to make your adaptive choices like this `new AdaptiveChoice { Title = s.Value, Value = s.Key }` then when you retrieve the team ID from the user's selection you are also effectively retrieving the team name. – Kyle Delaney Jan 28 '19 at 18:54
  • All you have to do is `var selectedTeamName = signInPhoneState.Teams[sel];` or maybe `signInPhoneState.Teams.TryGetValue(sel, out string selectedTeamName);` if you want to account for situations when `sel` isn't found in the dictionary. – Kyle Delaney Jan 28 '19 at 18:59
  • Curiously you seem to have stumbled upon a way to include both the team ID and the team name in the adaptive choice's value, even though it's unnecessary. When you wrote `new AdaptiveChoice { Title = s.Value, Value = JsonConvert.SerializeObject(s)}` you seemed to think that was a way of including just the team ID in the user's response but really you're serializing the whole `KeyValuePair` which includes both the team ID and the team name. While my recommendation is still to just retrieve the team name from the dictionary using the team ID, it's possible to put them both in the message. – Kyle Delaney Jan 28 '19 at 19:13
  • Would you like me to put that as an answer? – Kyle Delaney Jan 31 '19 at 22:12
  • @KyleDelaney i think there's some misunderstanding here. i want to have both id and name after chosing and have that without storing extra variables, but there's no all-in-one solution for dropdowns for now, so we need to use combo of AdaptiveCard and PromptAsync. AdaptiveCard one and it's AdaptiveChoice accepts both Title and Value and we can put whatever we need in value to map our answer later, but Choices in PromptOptions only supports array of strings, we can't put key;value here, that's why i was serializing it. Yeah if we still store whole dictionary we can search it by both id and name – Andrey Stepanov Feb 03 '19 at 12:37
  • I saw that after the prompt you were extracting the ID from the team name using `signInPhoneState.Teams` which I assumed was the same dictionary stored in `_teams`. So I figured you'd still have access to that dictionary after the prompt, meaning there would be no problem with just using the team ID as the choice's value and then getting the name from the dictionary afterwards. So you're saying you don't want to do that because you don't want to need to access the dictionary on the turn after the prompt? – Kyle Delaney Feb 05 '19 at 17:47
  • And you don't want to serialize both the team ID and the team name as JSON in the adaptive choice value because you don't want to have to use those serializations as the prompt's choices? – Kyle Delaney Feb 05 '19 at 17:49
  • 1
    all you mentioned is correct, all this aproaches worked when we're storing all values in external dictionary, but this is huge workaround, isn't it? To have standart key,value for dropdowns , choises, lists etc. – Andrey Stepanov Feb 08 '19 at 12:49
  • I don't think it's a huge workaround, and I don't think it's all that standard to want to include a key/value pair as the single value selected in a dropdown. When you do want to include multiple values as a single value, JSON or some other form of serialization is a pretty standard way to do it. – Kyle Delaney Feb 09 '19 at 00:21
  • In every UX framework i was working with last decade passing some Id while showing text without it - out of the box. Winforms , WPF, Uwp etc, also in web frameworks. Actually even botbuilder supports that, HeroCard Button you can have not just Title, but also Value and then get it. If you think prepare synthetic var with all the list, serialize that and store sowhere (in our case in userdata in cosmodb) just to later map answer is not workaround - i would like to hear what is consider workaround ;) But please tell me that will be fixed with upcoming update and new Prompts with AdaptiveCards? – Andrey Stepanov Feb 10 '19 at 15:01
  • The behavior you're describing (having a choice's display text be different from the value associated with it) is exactly what we're already doing with Adaptive Card. It's unclear how you think it could be any better/easier. If you want the choice's title to be automatically appended to its value in the submit action's payload, that's very different from the behavior we see with hero cards etc. – Kyle Delaney Feb 13 '19 at 21:24
  • as we already discussed, it is working well in AdaptiveCards, but you can't use it in prompts for example in waterfall out of the box, for that you need to use it AdaptiveCard in conjuntion with PromptAsync, all that discussed here https://github.com/Microsoft/botbuilder-dotnet/issues/614 and also feature request already made , was planned for next release, sad news that it was moved to 4.4 from 4.3 – Andrey Stepanov Feb 14 '19 at 10:18
  • Are you talking about this feature request? https://github.com/Microsoft/botbuilder-dotnet/issues/1170 – Kyle Delaney Feb 14 '19 at 22:38
  • @KyleDelaney yes – Andrey Stepanov Feb 18 '19 at 13:50
  • That feature has actually already been implemented in [this pull request](https://github.com/Microsoft/botbuilder-dotnet/pull/1339), so you'll see it in 4.3. However, it uses hero cards and not Adaptive Cards. – Kyle Delaney Feb 19 '19 at 01:02
  • yeah but i believe the feature was about adaptive cards cos hero cards are limited in features (for example no dropdowns)? Btw what's the main plans for adaptive cards, i've heard ms want to charge that to use of pure html? – Andrey Stepanov Feb 26 '19 at 17:23
  • Adaptive Cards are open source so you can see what the plans are on the [repo](https://github.com/Microsoft/AdaptiveCards/issues?q=is%3Aissue+is%3Aopen+label%3AArea-Explorations). I don't think HTML is "in the cards" so to speak ;) – Kyle Delaney Feb 27 '19 at 17:51

1 Answers1

0

You have a dictionary with team ID's as keys and team names as values. You are using the team names as the values for an adaptive choice set that's being used in a prompt, and in the turn after the prompt you're extracting the team ID from the dictionary using the team name. You want a more convenient option.

Option 1: If you're okay with your current setup of keeping the dictionary available

When accessing the data in a dictionary, it is more efficient to access a value using a key than the other way around. That is what dictionaries are for, after all. So instead of using the team names as values in your choice set, you could use team ID's.

var choicesInputs = _teams.Select(s => new AdaptiveChoice { Title = s.Value, Value = s.Key }).ToList();

// . . .

signInPhoneState.Teams.TryGetValue(sel, out string selectedTeamName);

This would mean that if the dictionary is being drawn from some external source that's subject to change, the team name would be as up-to-date as possible.

Option 2: If you don't want to depend on the dictionary for the next turn

You could store both the team ID and the team name in the choice's value.

var choicesInputs = _teams.Select(s => new AdaptiveChoice { Title = s.Value, Value = JsonConvert.SerializeObject(s) }).ToList();

// . . .

var pair = JsonConvert.DeserializeObject<KeyValuePair<string, string>>(sel);
var selectedTeamId = pair.Key;
var selectedTeamName = pair.Value;

This would mean if the underlying data changes between the first turn of the prompt and the second, the choice would still be valid.

Kyle Delaney
  • 11,616
  • 6
  • 39
  • 66