Botframework V4: Question about Input forms cards

2019-06-24 03:47发布

Hello i have this input forms card. It is rendering properly but how can i get its results? And how can i make it so that the bot wait for the user to submit before proceding to the next step? Putting stepContext.NextAsync will automatically trigger the next step. But removing it will cause an error because it needs to return something.

   public InitialQuestions(string dialogId, IEnumerable<WaterfallStep> steps = null)
        : base(dialogId, steps)
    {
        AddStep(async (stepContext, cancellationToken) =>
        {
            var cardAttachment = CreateAdaptiveCardAttachment(_cards);
            var reply = stepContext.Context.Activity.CreateReply();
            reply.Attachments = new List<Attachment>() { cardAttachment };
            await stepContext.Context.SendActivityAsync(reply, cancellationToken);

            // how can i wait for user to click submit before going to next step?
            return await stepContext.NextAsync();

            // return await stepContext.PromptAsync(
            //   "textPrompt",
            //   new PromptOptions
            //   {
            //       Prompt = MessageFactory.Text(""),
            //   },
            //   cancellationToken: cancellationToken);

        });

        AddStep(async (stepContext, cancellationToken) =>
        {
            // next step
        });
    }

     private static Attachment CreateAdaptiveCardAttachment(string filePath)
    {
        var adaptiveCardJson = File.ReadAllText(filePath);
        var adaptiveCardAttachment = new Attachment()
        {
            ContentType = "application/vnd.microsoft.card.adaptive",
            Content = JsonConvert.DeserializeObject(adaptiveCardJson),
        };
        return adaptiveCardAttachment;
    }

This is the card

{
  "type": "AdaptiveCard",
  "body": [
    {
      "type": "TextBlock",
      "text": "What is your Occupation?"
    },
    {
      "type": "Input.Text",
      "id": "Occupation",
      "placeholder": "Occupation"
    },
    {
      "type": "TextBlock",
      "text": "Are you married? "
    },
    {
      "type": "Input.ChoiceSet",
      "id": "Married",
      "value": "true",
      "choices": [
        {
          "title": "Yes",
          "value": "true"
        },
        {
          "title": "No",
          "value": "false"
        }
      ],
      "style": "expanded"
    },
    {
      "type": "TextBlock",
      "text": "When is your birthday?"
    },
    {
      "type": "Input.Date",
      "id": "Birthday",
      "value": ""
    }
  ],
  "actions": [
    {
      "type": "Action.Submit",
      "title": "Submit",
      "data": {
        "id": "1234567890"
      }
    }
  ],
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "version": "1.0"
}

Thanks guys.

EDIT: for future reference of others this is the answer i found.

    AddStep(async (stepContext, cancellationToken) =>
    {
        var state = await (stepContext.Context.TurnState["BasicAccessors"] as BasicAccessors).BasicStateAccessor.GetAsync(stepContext.Context);

        var jsonString = (JObject)stepContext.Context.Activity.Value;
        BasicState results = JsonConvert.DeserializeObject<BasicState>(jsonString.ToString());
        state.Occupation = results.Occupation;
        state.Married = results.Married;
        state.Birthday = results.Birthday;

        return await stepContext.NextAsync();
    });

1条回答
【Aperson】
2楼-- · 2019-06-24 04:43

Let me answer your questions in reverse order:

And how can i make it so that the bot wait for the user to submit before proceding to the next step? Putting stepContext.NextAsync will automatically trigger the next step. But removing it will cause an error because it needs to return something.

Yes, it's true, you need to return something from your step, but as you point out you're not ready for it to move to the next step yet. The answer is that you want to use a prompt at this point! Now I see you have some code in here commented out to do this and maybe what's confusing is that, today, there is no specific prompt for working with cards. Instead you do want to use a general purpose TextPrompt and we'll set the activity on that to something other than just simple text.

With this in mind, you would keep your code above that is using CreateReply to build your Activity with card attachments, but, instead of sending that Activity yourself with SendActivityAsync you want to set it as the value of the Prompt property of the TextPrompt like so:

AddStep(async (stepContext, cancellationToken) =>
        {
            return await stepContext.PromptAsync(
               "myPrompt",
               new PromptOptions
               {
                   Prompt = new Activity 
                   {
                       Type = ActivityTypes.Message,
                       Attachments = new List<Attachment>() 
                       { 
                          CreateAdaptiveCardAttachment(_cards),
                       },
                   },
               },
               cancellationToken: cancellationToken);

        });

Ok, so that's one half of the problem. With that in mind now, let's circle back to the first part of your question:

Hello i have this input forms card. It is rendering properly but how can i get its results?

Well, your adaptive card is using the Submit action which means that you will receive an activity that contains the values of the form in the Values property of the Activity, however because we used a TextPrompt above the default validation behavior of the TextPrompt is going to be validating that some value was supplied for the Text portion of the Activity which there won't be in this case. So, to fix that, when you configure the TextPrompt you really want to provide your own PromptValidator<T> like so:

    Add(new TextPrompt("myPrompt", new PromptValidator<string>(async (pvc, ct) => true)));

This basically says the input is valid no matter what. You could make it richer if you wanted by actually checking the details of the value, but this should unblock you for now.

Now, back in your WaterfallDialog your next step is going to be receiving the Activity whose Value property will be a JObject which you can either use directly or you can call JObject::ToObject<T> to convert it into a specific class you've created that represents your form input:

AddStep(async (stepContext, cancellationToken) =>
        {
           // This will give you a JObject representation of the incoming values 
           var rawValues = (JObject)stepContext.Context.Activity.Values;

           // You can convert that to something more strongly typed like so
           // where MyFormValues is a class you've defined
           var myFormValues = rawValues.ToObject<MyFormValues>();
        });

I want to just close this answer out by saying that, in answering your question, I've recorded a bunch of feedback that I intend to send to the product team to improve this situation both in terms of API design and documentation because, clearly, this is not obvious or optimal.

查看更多
登录 后发表回答