Search code examples
c#botframeworkstatekey-valueadaptive-cards

botbuilder v 4, dynamic adaptive card with dropdown and capturing values on prompt


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


Solution

  • 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.