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();
});
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.