title: Capture values submitted by Adaptive Card in waterfall dialog
body: <p>I have followed the advice of <a href="https://stackoverflow.com/questions/56004289/botframework-how-to-capture-extract-the-values-submitted-through-adaptive-car/56010355#56010355">this question</a>, the comment on <a href="https://github.com/Microsoft/botbuilder-dotnet/issues/614#issuecomment-443946026" rel="nofollow noreferrer">this issue</a> and also <a href="https://stackoverflow.com/a/56226891/5209435">this answer</a>.</p>
  
  <p>Inside my Waterfall Dialog:</p>
  
  <ul>
  <li>Displaying the adaptive card</li>
  <li>Sending a text prompt right after displaying an adaptive card</li>
  </ul>
  
  <p>Inside my main bot class:</p>
  
  <ul>
  <li>Setting the <code>Text</code> property of the <code>Activity</code> to that of the extracted value from the <code>Value</code> property of the activity if the activity is a message that contains postback data.</li>
  </ul>
  
  <p>I have tried using a <code>AdaptiveTextInput</code> or <code>AdaptiveDateInput</code> as the control to submit the value but it doesn't make a difference. I feel like it's going to be something really silly..</p>
  
  <p>I am currently using a mix of <code>Hero</code> and <code>Adaptive</code> cards in my waterfall, the <code>Hero</code> cards are working as they should.</p>
  
  <hr>
  
  <p><strong>Edit</strong></p>
  
  <p>I have added // !Relevant- comments to my code to the important parts, the rest is left for context.</p>
  
  <p>So my question is: what is preventing my submit on the adaptive card from flowing through correctly - is it a problem in how I am displaying in the waterfall, a problem with how the action is constructed in the card, or how I am handling the action in the main bot class?</p>
  
  <hr>
  
  <p>Building my cards in <code>AdaptiveCardService</code>:</p>
  
  <pre><code>public List&lt;Activity&gt; BuildCardActivitiesFromDecisionFlow(BotDecisionFlow botDecisionFlow)
  {
      List&lt;Activity&gt; cardActivities = new List&lt;Activity&gt;();
  
      foreach (Step step in botDecisionFlow.FormSchema.Steps)
      {
          Control control = step.Details.Control;
  
          cardActivities.Add(CreateCardActivity(step, control));
      }
  
      return cardActivities;
  }
  
  private Activity CreateCardActivity(Step step, Control control)
  {
      Activity cardActivity = (Activity)Activity.CreateMessageActivity();
  
      if (control.Type == ControlTypeEnum.RadioButton)
      {
          HeroCard heroCard = BuildHeroCard(step, control.DataType);
          Attachment attachment = heroCard.ToAttachment();
  
          cardActivity.Attachments.Add(attachment);
      }
      else if (control.Type == ControlTypeEnum.DatePicker)
      {
          AdaptiveCard adaptiveCard = BuildAdaptiveCard(step, control.DataType);
  
          Attachment attachment = new Attachment
          {
              ContentType = AdaptiveCard.ContentType,
              // Trick to get Adapative Cards to work with prompts as per https://github.com/Microsoft/botbuilder-dotnet/issues/614#issuecomment-443549810
              Content = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(adaptiveCard))
          };
  
          cardActivity.Attachments.Add(attachment);
      }
      else
      {
          throw new NotImplementedException($"The {nameof(control)} with value {control} is not yet supported.");
      }
  
      return cardActivity;
  }
  
  private HeroCard BuildHeroCard(Step step, DataTypeEnum dataType)
  {
      string question = step.Details.Question;
  
      HeroCard heroCard = new HeroCard
      {
          Text = question,
          // PostBack is required to get buttons to work with prompts, also the value needs to be a string for the
          // event to fire properly, as per https://stackoverflow.com/a/56297792/5209435
          Buttons = step.Details.EnumValueToDisplayTextMappings.Select(e =&gt; new CardAction(ActionTypes.PostBack, e.Value, null, e.Value, e.Value, JsonConvert.SerializeObject(new DialogValueDto(step.Name, e.Key, dataType)), null)).ToList()
      };
  
      return heroCard;
  }
  
  private AdaptiveCard BuildAdaptiveCard(Step step, DataTypeEnum dataType)
  {
      const string ISO8601Format = "yyyy-MM-dd";
      string question = step.Details.Question;
  
      DateTime today = DateTime.Today;
      string todayAsIso = today.ToString(ISO8601Format);
  
      AdaptiveCard adaptiveCard = new AdaptiveCard("1.0")
      {
          Body =
          {
              new AdaptiveContainer
              {
                  Items =
                  {
                      new AdaptiveTextBlock
                      {
                          Text = question,
                          Wrap = true
                      },
                      new AdaptiveDateInput
                      {
                          Id = "UserInput",
                          Value = todayAsIso,
                          Min = today.AddDays(-7).ToString(ISO8601Format),
                          Max = todayAsIso,
                          Placeholder = todayAsIso
                      }
                  }
              }
          },
          Actions = new List&lt;AdaptiveAction&gt;
          {
              // !Relevant-Start
              new AdaptiveSubmitAction
              {
                  Data = new DialogValueDto(step.Name, dataType),
                  Title = "Confirm",
                  Type = "Action.Submit"
              }
              // !Relevant-End
          }
      };
  
      return adaptiveCard;
  }
  </code></pre>
  
  <p>Inside my waterfall class:</p>
  
  <pre><code>private readonly IUmbracoApiWrapper _umbracoApiWrapper;
      private readonly IUmbracoResponseConverterService _umbracoResponseConverterService;
      private readonly IAdaptiveCardService _adaptiveCardService;
  
      private IStatePropertyAccessor&lt;DynamicWaterfallState&gt; _accessor;
      private DynamicWaterfallState _state;
  
      public DynamicWaterfallDialog(
          IUmbracoApiWrapper umbracoApiWrapper,
          IUmbracoResponseConverterService umbracoResponseConverterService,
          IAdaptiveCardService adaptiveCardService,
          UserState userState)
          : base(nameof(DynamicWaterfallDialog))
      {
          _accessor = userState.CreateProperty&lt;DynamicWaterfallState&gt;(nameof(DynamicWaterfallState));
          _umbracoApiWrapper = umbracoApiWrapper;
          _umbracoResponseConverterService = umbracoResponseConverterService;
          _adaptiveCardService = adaptiveCardService;
  
          InitialDialogId = nameof(WaterfallDialog);
  
          // !Relevant-Start
          var waterfallSteps = new WaterfallStep[]
          {
              // TODO: Rename this DisplayCardAsync
              UserInputStepAsync,
              // TODO: Rename this ProcessCardAsync
              LoopStepAsync,
          };
  
          AddDialog(new TextPrompt(nameof(TextPrompt)));
          AddDialog(new WaterfallDialog(InitialDialogId, waterfallSteps));
          // !Relevant-End
      }
  
      // TODO: Does it make more sense for the collection of dialogs to be passed in? It depends on how this dialog is going to be called, 
      // maybe just passing in the ID is fine rather than having code sprinkled around to fetch the dialog collections.
      public async Task&lt;DialogTurnResult&gt; UserInputStepAsync(WaterfallStepContext sc, CancellationToken cancellationToken)
      {
          // Get passed in options, need to serialise the object before we deserialise because calling .ToString on the object is unreliable
          string tempData = JsonConvert.SerializeObject(sc.Options);
          DynamicWaterfallDialogDto dynamicWaterfallDialogDto = JsonConvert.DeserializeObject&lt;DynamicWaterfallDialogDto&gt;(tempData);
  
          // Read out data from the state
          _state = await _accessor.GetAsync(sc.Context, () =&gt; new DynamicWaterfallState());
  
          List&lt;Activity&gt; activityCards = _state.ActivityDialogs ?? new List&lt;Activity&gt;();
          int dialogPosition = _state.DialogPosition;
          bool flowFinished = _state.FlowFinished;
          bool apiDataFetched = _state.ApiDataFetched;
  
          if (DynamicWaterfallDialogDtoExtensions.IsDynamicWaterfallDialogDtoValid(dynamicWaterfallDialogDto) &amp;&amp; !apiDataFetched)
          {
              // Fetch from API
              JObject decision = await _umbracoApiWrapper.GetDecisionById(18350);
  
              UmbracoDecisionResponseDto umbracoResponseDto = JsonConvert.DeserializeObject&lt;UmbracoDecisionResponseDto&gt;(decision.ToString());
  
              BotDecisionFlow botDecisionFlow = new BotDecisionFlow(_umbracoResponseConverterService, umbracoResponseDto);
  
              activityCards = _adaptiveCardService.BuildCardActivitiesFromDecisionFlow(botDecisionFlow);
  
              _state.ApiDataFetched = true;
              _state.ActivityDialogs = activityCards;
  
              await _accessor.SetAsync(sc.Context, _state, cancellationToken);
          }
  
          var cardToShow = activityCards.ElementAt(dialogPosition);
  
          _state.FlowFinished = _state.DialogPosition == activityCards.Count - 1;
          _state.DialogPosition++;
  
          await _accessor.SetAsync(sc.Context, _state, cancellationToken);
  
          // TODO we need to determine the control type to figure out the prompt type?
  
          // !Relevant-Start
          await sc.Context.SendActivityAsync(cardToShow);
          return await sc.PromptAsync(nameof(TextPrompt), new PromptOptions() { Prompt = MessageFactory.Text("") });
          //return await sc.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = new Activity { Type = ActivityTypes.Message } });
          // !Relevant-End
      }
  
      public async Task&lt;DialogTurnResult&gt; LoopStepAsync(WaterfallStepContext sc, CancellationToken cancellationToken)
      {
          object result = sc.Result;
          DialogValueDto userInput = JsonConvert.DeserializeObject&lt;DialogValueDto&gt;(sc.Result.ToString());
  
          await sc.Context.SendActivityAsync($"You selected: {userInput.UserInput}");
  
          _state = await _accessor.GetAsync(sc.Context, () =&gt; new DynamicWaterfallState());
  
          bool flowFinished = _state.FlowFinished;
  
          // TODO: Do we want to do state manipulation in here?
  
          if (!flowFinished)
          {
              // TODO: Do we want to pass in custom options here?
              return await sc.ReplaceDialogAsync(nameof(DynamicWaterfallDialog), sc.Options, cancellationToken);
          }
          else
          {
              // TODO: We probably want to pass the state in here instead of null if we want to show outcomes etc
              return await sc.EndDialogAsync(null, cancellationToken);
          }
      }
  }
  </code></pre>
  
  <p>Inside my main bot class:</p>
  
  <pre><code>public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken)
  {
      // Client notifying this bot took to long to respond (timed out)
      if (turnContext.Activity.Code == EndOfConversationCodes.BotTimedOut)
      {
          _telemetryClient.TrackTrace($"Timeout in {turnContext.Activity.ChannelId} channel: Bot took too long to respond.", Severity.Information, null);
          return;
      }
  
      var dc = await _dialogs.CreateContextAsync(turnContext);
  
      // !Relevant-Start
      if (turnContext.Activity.Type == ActivityTypes.Message)
      {
          // Ensure that message is a postBack (like a submission from Adaptive Cards)
          if (dc.Context.Activity.GetType().GetProperty("ChannelData") != null)
          {
              var channelData = JObject.Parse(dc.Context.Activity.ChannelData.ToString());
  
              // TODO: Add check for type, we should only handle adaptive cards here
              if (channelData.ContainsKey("postBack"))
              {
                  var postbackActivity = dc.Context.Activity;
  
                  string text = JsonConvert.DeserializeObject&lt;DialogValueDto&gt;(postbackActivity.Value.ToString())?.UserInput;
  
  
                  // Convert the user's Adaptive Card input into the input of a Text Prompt
                  // Must be sent as a string
                  postbackActivity.Text = text;
                  await dc.Context.SendActivityAsync(postbackActivity);
              }
          }
      }
      // !Relevant-End
  
      if (dc.ActiveDialog != null)
      {
          var result = await dc.ContinueDialogAsync();
      }
      else
      {
          await dc.BeginDialogAsync(typeof(T).Name);
      }
  }
  </code></pre>
  
  <p>My DialogValueDto incase you need it:</p>
  
  <pre><code>public string StepName { get; set; }
  public string UserInput { get; set; }
  public DataTypeEnum DataType { get; set; }
  
  /// &lt;summary&gt;
  /// For JSON deserialization
  /// &lt;/summary&gt;
  public DialogValueDto()
  {
  }
  
  /// &lt;summary&gt;
  /// For use with DateTime deserialization.
  /// The control id is set to "UserInput"
  /// so this property will be set automatically
  /// &lt;/summary&gt;
  public DialogValueDto(string stepName, DataTypeEnum dataType)
  {
      StepName = stepName;
      DataType = dataType;
  }
  
  /// &lt;summary&gt;
  /// This is the constructor that should be used most
  /// of the time
  /// &lt;/summary&gt;
  public DialogValueDto(string stepName, string userInput, DataTypeEnum dataType)
  {
      StepName = stepName;
      UserInput = userInput;
      DataType = dataType;
  }
  </code></pre>
  
  <p>Interestingly enough my <code>OnEventAsync</code> function of my MainDialog (the one which is wired up in <code>Startup.cs</code> via <code>services.AddTransient&lt;IBot, DialogBot&lt;MainDialog&gt;&gt;();</code>) gets fired when I set the text property of the activity.</p>