How to create adaptive cards with situational text values? - c#

I am currently trying to create an adaptive card in a Waterfall Dialog for one of my bots that will display the name and search item (both strings) when rendered. Both of the values I want to use are stored in the Context.Activity.Value property of my dialog, so all I need to know is how to insert those values into my adaptive card at some point during its creation so that the "text" values of the text blocks can contain my values.
I have looked into using empty JSON objects in the adaptive card schema that I could fill somehow during the adaptive card's creation, but have not figured out how to insert said values. I'm a relative beginner with C# and Bot Framework, so I don't know what to try.
Below is the step in my Waterfall Dialog where the adaptive card is made:
private async Task<DialogTurnResult> AdaptiveCardTest(WaterfallStepContext stepContext,
CancellationToken cancellationToken)
{
var introCard = File.ReadAllText("./Content/AdaptiveCardTest.json");
var card = AdaptiveCard.FromJson(introCard).Card;
var attachment = new Attachment(AdaptiveCard.ContentType, content: card);
var response = MessageFactory.Attachment(attachment, ssml: card.Speak,
inputHint: InputHints.AcceptingInput);
await stepContext.Context.SendActivityAsync(response);
return await stepContext.NextAsync();
}
AdaptiveCardTest.json is the adaptive card's json file. At the moment it just has an image popup with some text which includes placeholders where I would like the user name and search item to go. The placeholder links are there because the actual links are ridiculously long.
{
"type": "AdaptiveCard",
"id": "NewUserGreeting",
"backgroundImage": "image_url_placeholder"
"body": [
{
"type": "Container",
"items": [
{
"type": "Image",
"url": "image_url_placeholder_2"",
"size": "Stretch"
}
]
},
{
"type": "Container",
"spacing": "None",
"backgroundImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAXCAIAAACAiijJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAqSURBVDhPY1RgL2SgDDBBaQrAqBEIMGoEAowagQCjRiDAqBEIQLERDAwAIisAxhgAwtEAAAAASUVORK5CYII=",
"items": [
{
"type": "TextBlock",
"id": "title",
"spacing": "Medium",
"size": "Large",
"weight": "Bolder",
"color": "Light",
"text": "Hi, I'm **your** Virtual Assistant",
"wrap": true
},
{
"type": "TextBlock",
"id": "body",
"size": "Medium",
"color": "Light",
"text": "The user {{Name}} would like to know more about {{SearchItem}}.",
"wrap": true
}
]
}
],
}
Any help would be greatly appreciated, thank you!

For your simple scenario I would go with #MikeP's suggestion. In the future if you want to do something more complex where a template won't suffice, then you can build the Adaptive Card dynamically using the .NET SDK once you have installed the AdaptiveCard NuGet package.
The documentation on the .NET SDK is pretty limited but the properties of the AdaptiveCard object usually align with their JSON counterparts.
An example is:
const string ISO8601Format = "yyyy-MM-dd";
string text = "dynamic-text-here;
DateTime today = DateTime.Today;
string todayAsIso = today.ToString(ISO8601Format);
// Create card
AdaptiveCard adaptiveCard = new AdaptiveCard("1.0")
{
Body =
{
new AdaptiveContainer
{
Items =
{
new AdaptiveTextBlock
{
Text = question,
Wrap = true
},
new AdaptiveDateInput
{
// This Id matches the property in DialogValueDto so it will automatically be set
Id = "UserInput",
Value = todayAsIso,
Min = today.AddDays(-7).ToString(ISO8601Format),
Max = todayAsIso,
Placeholder = todayAsIso
}
}
}
},
Actions = new List<AdaptiveAction>
{
new AdaptiveSubmitAction
{
// Data can be an object but this will require the value provided for the
// Content property to be serialised it to a string
// as per this answer https://stackoverflow.com/a/56297792/5209435
// See the attachment block below for how this is handled
Data = "your-submit-data",
Title = "Confirm",
Type = "Action.Submit"
}
}
};
// Create message attachment
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);
// Send the message
context.SendActivityAsync(cardActivity);
Because Items and Actions are collections, you could have conditional logic inside your code to build up these collections based on some condition at runtime then pass the build collection to Items or Actions which will allow you more flexibility than having a JSON template that you replace placeholder tokens at a known location.

This is something that I have done in the past using Handlebars which is a nice way of replacing tokens in your adaptive card JSON with properties from a model. Just make sure the tokens in your adaptive card JSON match the model properties
Have a look at their site for more detail but it is just a case of doing:
Handlebars.Compile(<Adaptive card template>);
Handlebars is available as a Nuget package you can add into your project.

Related

In Adaptive Card, how can I ToggleVisibility when user submit?

In Adaptive Card, it's easy to create a submit button:
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "TextBlock",
"text": "Present a form and submit it back to the originator"
},
{
"type": "Input.Text",
"id": "firstName",
"placeholder": "What is your first name?"
},
{
"type": "Input.Text",
"id": "lastName",
"placeholder": "What is your last name?"
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Action.Submit",
"data": {
"x": 13
}
}
]
}
Which look like this:
Is it possible to also use Toggle Visibility as defined here?
https://adaptivecardsci.z5.web.core.windows.net/pr/3261/explorer/Action.ToggleVisibility.html
So what I owuld like to achieve is when the user click the button, it will both submit the form and also make the form element invisible.
Emulator does not support message updates or deletions, so you will not be able to test this functionality in Emulator. However, you can still debug your bot locally on a channel like Teams using a tunneling service like ngrok: https://blog.botframework.com/2017/10/19/debug-channel-locally-using-ngrok/
You can find examples of how to update an acitivity in the following answers:
Can we add text field dynamically
Microsoft teams bot adaptive card carousel deleting a card
Simple example to store array data in card
You can see that it involves bot state. If you just want to delete the whole activity then your job may be easier because you won't need to save any information about the activities except for the activity ID. Your state accessor could look like this:
public IStatePropertyAccessor<Dictionary<string, string>> CardStateAccessor { get; internal set; }
And you can initialize it like this:
CardStateAccessor = _conversationState.CreateProperty<Dictionary<string, string>>("cardState");
Since your card is in JSON form, you may want to deserialize it before adding a unique card ID to the submit action:
var card = JObject.Parse(json);
var data = card.SelectToken("actions[0].data");
var cardId = Guid.NewGuid();
data[KEYCARDID] = cardId;
var cardActivity = MessageFactory.Attachment(new Attachment("application/vnd.microsoft.card.adaptive", content: card));
var response = await turnContext.SendActivityAsync(cardActivity, cancellationToken);
var dict = await CardStateAccessor.GetAsync(turnContext, () => new Dictionary<string, string>(), cancellationToken);
dict[cardId] = response.Id;
Then you can delete the activity like this:
var value = JObject.FromObject(turnContext.Activity.Value);
var cardId = Convert.ToString(value[KEYCARDID]);
var dict = await CardStateAccessor.GetAsync(turnContext, () => new Dictionary<string, string>(), cancellationToken);
if (dict.TryGetValue(cardId, out var activityId))
{
await turnContext.DeleteActivityAsync(activityId, cancellationToken);
dict.Remove(cardId);
}
If you would like this process to be made easier then you may voice your support for my cards library proposal: https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/issues/137

Simple example to store array data in card

I would like to understand how to read/write data with Adaptive cards. I can read the data from a submit action, and reply as text, but not sure how present the input data in the card. First place, I would like to add the shootValue to an array that I can carry trough the lifecycle of the card. Can somebody please let me know how to do this?
The goal of this question is to understand how to keep existing responses from the card.
Like in Battleship, I shoot "A1", type it in an input box, submit, I would like to see "A1" in the card. I add "A2", submit, then I would like to see "A1" and "A2" in the card that is sent to Teams. I understand that I need to rebuild the card from scratch at every shot, that means, I need to either carry on the shots somehow with each action.
Data card:
{
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "TextBlock",
"text": "Hello {name}"
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "stretch",
"id": "",
"items": [
{
"type": "Container",
"items": [
{
"type": "Input.Text",
"placeholder": "Voorbeeld: A1",
"id": "id_shoot",
"$data": "shoot"
}
]
}
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "Container",
"items": [
{
"type": "TextBlock",
"text": " {shoot}",
"horizontalAlignment": "Right",
"id": ""
}
],
"$data": "{shoots}",
"id": "shotcoords"
}
],
"$data": "{shots}"
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "Container",
"items": [
{
"type": "TextBlock",
"text": "{status}",
"id": ""
}
],
"$data": "{shoots}",
"id": "shotstatuses"
}
],
"id": ""
}
]
},
{
"type": "ActionSet",
"id": "",
"actions": [
{
"type": "Action.Submit",
"title": "Shoot",
"id": "",
"style": "positive",
"data": {}
}
]
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json"
}
Data
{
"name": "Test shot",
"shoots": [
{
"shoot": "a1",
"status": "hit"
},
{
"shoot": "a2",
"status": "hit"
}
]
}
There is no "simple" way to do this, but there is a way. The answer will be similar to this one.
First, you'll need a way of saving state for your card so you can update the card's activity. In C# you can declare a state property accessor like this:
public IStatePropertyAccessor<Dictionary<string, (string ActivityId, List<string> Shots)>> BattleshipStateAccessor { get; internal set; }
Then you can instantiate it like this
BattleshipStateAccessor = _conversationState.CreateProperty<Dictionary<string, (string, List<string>)>>("battleshipState");
You have a few decisions to make here. First, I'm choosing to make the state property a dictionary so I can keep track of multiple cards and only update the specific card that was clicked. If you don't care about that then you don't need a dictionary and you don't need to worry about "card ID's," but saving at least one activity ID is necessary so that you'll be able to update the card. As for saving the "shots," you have a few choices here. You could save that state on the client side by updating the submit action's data with each shot that's been made, but I figure I might as well save the shots in bot state because I already need to save the activity ID in bot state anyway. Then there's the question of what information about each shot you should save. In this example I'm only saving the location of the shot that the user entered and not the status of the shot, since I figure I can always recalculate the status whenever I need to.
I've modified your submit action to look like this:
{
"type": "Action.Submit",
"title": "Shoot",
"style": "positive",
"data": {
"behavior": "Shoot",
"cardId": ""
}
}
What I've done here is added two properties to your data object, and this data will be sent to your bot along with the text input's value. The "behavior" property will help your bot route to the correct function in case your bot uses multiple types of actions that can be handled different ways. The "cardId" property is just a placeholder that your bot code will fill in when creating the card. I've stored the names of these properties in the constants KEYBEHAVIOR and KEYCARDID.
You'll want a consistent way to generate your card that you can use when you send the card initially and when you update the card.
internal static IMessageActivity CreateBattleshipCardActivity(
string cardId,
object data = null)
{
data = data ?? new
{
name = "Test shot",
shoots = new string[0],
};
JObject card = CreateAdaptiveCard("battleship", data);
foreach (var token in card.Descendants()
.Select(token => token as JProperty)
.Where(token => token?.Name == KEYCARDID))
{
token.Value = cardId;
}
return MessageFactory.Attachment(new Attachment(
AdaptiveCard.ContentType,
content: card));
}
The CreateAdaptiveCard function loads the JSON template from a file with the given name, transforms it with the given data, and deserializes it into a JObject.
Using this function, you can send the card initially like this in C#:
public async Task TestBattleshipAsync(
ITurnContext turnContext,
CancellationToken cancellationToken)
{
var activity = turnContext.Activity;
var cardId = Guid.NewGuid().ToString();
var reply = CreateBattleshipCardActivity(cardId);
var response = await turnContext.SendActivityAsync(reply, cancellationToken);
var dict = await BattleshipStateAccessor.GetAsync(
turnContext,
() => new Dictionary<string, (string, List<string>)>(),
cancellationToken);
dict[cardId] = (response.Id, new List<string>());
}
And you can update the card in response to the card's "Shoot" submit action like this:
private async Task ShootAsync(
ITurnContext turnContext,
CancellationToken cancellationToken)
{
var activity = turnContext.Activity;
if (activity.ChannelId == Channels.Msteams)
{
var value = JObject.FromObject(activity.Value);
var cardId = Convert.ToString(value[BotUtil.KEYCARDID]);
var dict = await BattleshipStateAccessor.GetAsync(
turnContext,
() => new Dictionary<string, (string, List<string>)>(),
cancellationToken);
if (dict.TryGetValue(cardId, out var savedInfo))
{
savedInfo.Shots.Add(value["id_shoot"].ToString());
var data = new
{
name = "Test shot",
shoots = savedInfo.Shots.Select(shot => new
{
shoot = shot,
status = DetermineHit(shot),
}),
};
var update = CreateBattleshipCardActivity(cardId, data);
update.Id = savedInfo.ActivityId;
update.Conversation = activity.Conversation;
await turnContext.UpdateActivityAsync(update, cancellationToken);
}
}
}

MS Chat Bot --How to access custom adaptive card properties from my C# code

How to dynamically change my custom adaptive card's text property value from within my C# code?
Here is my C# code
public static Attachment CreateMySearchCardAttachment()
{
// combine path for cross platform support
string[] paths = { ".", "Resources", "MySearchCard.json" };
var MySearchCardJson = File.ReadAllText(Path.Combine(paths));
var adaptiveCardAttachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(MySearchCardJson),
};
return adaptiveCardAttachment;
}
And my MySearchCard.json file goes below
{
"type": "AdaptiveCard",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"horizontalAlignment": "Right",
"spacing": "None",
"url": "",
"size": "Medium",
"width": "2px",
"height": "2px"
},
{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "Knowledgebase Search"
},
{
"type": "Input.Text",
"id": "searchText",
"placeholder": "Type your search text and click Search"
}
],
"width": 2
}
]
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Search"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
}
I am able to get this adaptive card to display inside my chat bot.But not sure how to dynamically change the text labels or their values. We want to dynamically change some of the text labels before displaying, then after displaying,dynamically show or hide based on user response.At a later point we want to integrate chat BoT with MS Teams.But prior to that I need to show the same from my emulator
If you look at my json, there is a text property with "text": "Knowledgebase Search". My question is how to change this text value dynamically from within my C# code?
Dynamically Changing Before Being Displayed
There's a few different ways to do this. The first option is probably the best, but they should all work.
1. Use the AdaptiveCards Package
Note: This package is different from, and newer than, Microsoft.AdaptiveCards -- Don't use this one
Since you know the exact part of the card that you'd like to change, you can:
string[] paths = { ".", "AdaptiveCard.json" };
var cardJson = File.ReadAllText(Path.Combine(paths));
var card = AdaptiveCard.FromJson(cardJson).Card;
var columnSet = (card.Body[0] as AdaptiveColumnSet);
var column = (columnSet.Columns[0] as AdaptiveColumn);
var knowledgeBlock = (column.Items[1] as AdaptiveTextBlock);
knowledgeBlock.Text = "Whatever You Want";
var attachment = new Attachment()
{
Content = card,
ContentType = "application/vnd.microsoft.card.adaptive"
};
var reply = stepContext.Context.Activity.CreateReply();
reply.Attachments = new List<Attachment>();
reply.Attachments.Add(attachment);
await stepContext.Context.SendActivityAsync(reply);
Result:
2. Use Data Binding (in Preview)
This is in preview and you still need to use the NuGet package from #1, but makes it easier to modify particular fields.
3. Edit the JSON with Newtonsoft.JSON
This is probably a little simpler, but less flexible. Something like this works and produces the same result as #1:
string[] paths = { ".", "AdaptiveCard.json" };
var cardJsonObject = JObject.Parse(File.ReadAllText(Path.Combine(paths)));
var knowledgeToken = cardJsonObject.SelectToken("body[0].columns[0].items[1]");
knowledgeToken["text"] = "Whatever You Want";
var attachment = new Attachment()
{
Content = cardJsonObject,
ContentType = "application/vnd.microsoft.card.adaptive"
};
var reply = stepContext.Context.Activity.CreateReply();
reply.Attachments = new List<Attachment>();
reply.Attachments.Add(attachment);
await stepContext.Context.SendActivityAsync(reply);
return await stepContext.NextAsync();
Dynamically Changing After Being Displayed
Changing the card after being displayed is a little more difficult. You first have to change the card in the code, as done above. You then have to use UpdateActivityAsync(). Basically, you send an activity with the same id, but a new card and it overwrites the previous card completely.
Note: You can only use this in channels that support updating activities. It's usually pretty easy to tell, because even without bots, the channel either will or won't let you edit messages. It sounds like you want to use Teams, so this will work fine.
You can use my answer here for how to update card activities with Teams. Note that this one is in Node, but you can still do it in C# the same way.
You can also use this other StackOverflow Answer from one of the guys on the MS Teams team.

How do i get the result of a adaptive card?

I'm trying to get the result of my AdaptiveCard.
My bot uses waterfalldialogs. In one Waterfallstep i present the user a number of Rooms with the time and date. The user then can choose a room. I tried it like shown below. Sadly the activity stays null. How do I get the result of the adaptive card
private async Task<DialogTurnResult> AfterChoice(WaterfallStepContext step, CancellationToken cancellationToken)
{
if (step.Result is Activity activity && activity.Value != null && ((dynamic)activity.Value).chosenRoom is JValue chosenRoom)
{
dynamic requestedBooking = JsonConvert.DeserializeObject<ExpandoObject>((string)chosenRoom.Value);
this.roomemail = requestedBooking.roomEmail;
return await step.EndDialogAsync();
}
else
{
return await step.BeginDialogAsync(whatever, cancellationToken: cancellationToken);
}
}
How do I get the users choice?
Adaptive Cards send their Submit results a little different than regular user text. When a user types in the chat and sends a normal message, it ends up in Context.Activity.Text. When a user fills out an input on an Adaptive Card, it ends up in Context.Activity.Value, which is an object where the key names are the id in your card and the values are the field values in the adaptive card.
For example, the json:
{
"type": "AdaptiveCard",
"body": [
{
"type": "TextBlock",
"text": "Test Adaptive Card"
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"text": "Text:"
}
],
"width": 20
},
{
"type": "Column",
"items": [
{
"type": "Input.Text",
"id": "userText",
"placeholder": "Enter Some Text"
}
],
"width": 80
}
]
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Submit"
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
}
.. creates a card that looks like:
If a user enters "Testing Testing 123" in the text box and hits Submit, Context.Activity will look something like:
{ type: 'message',
value: { userText: 'Testing Testing 123' },
from: { id: 'xxxxxxxx-05d4-478a-9daa-9b18c79bb66b', name: 'User' },
locale: '',
channelData: { postback: true },
channelId: 'emulator',
conversation: { id: 'xxxxxxxx-182b-11e9-be61-091ac0e3a4ac|livechat' },
id: 'xxxxxxxx-182b-11e9-ad8e-63b45e3ebfa7',
localTimestamp: 2019-01-14T18:39:21.000Z,
recipient: { id: '1', name: 'Bot', role: 'bot' },
timestamp: 2019-01-14T18:39:21.773Z,
serviceUrl: 'http://localhost:58453' }
The user submission can be seen in Context.Activity.Value.userText.
Note that adaptive card submissions are sent as a postBack, which means that the submission data doesn't appear in the chat window as part of the conversation--it stays on the Adaptive Card.
Using Adaptive Cards with Waterfall Dialogs
Your question doesn't quite relate to this, but since you may end up attempting this, I thought it might be important to include in my answer.
Natively, Adaptive Cards don't work like prompts. With a prompt, the prompt will display and wait for user input before continuing. But with Adaptive Cards (even if it contains an input box and a submit button), there is no code in an Adaptive Card that will cause a Waterfall Dialog to wait for user input before continuing the dialog.
So, if you're using an Adaptive Card that takes user input, you generally want to handle whatever the user submits outside of the context of a Waterfall Dialog.
That being said, if you want to use an Adaptive Card as part of a Waterfall Dialog, there is a workaround. Basically, you:
Display the Adaptive Card
Display a Text Prompt
Convert the user's Adaptive Card input into the input of a Text Prompt
In your Waterfall Dialog class (steps 1 and 2):
private async Task<DialogTurnResult> DisplayCardAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Create the Adaptive Card
var cardPath = Path.Combine(".", "AdaptiveCard.json");
var cardJson = File.ReadAllText(cardPath);
var cardAttachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(cardJson),
};
// Create the text prompt
var opts = new PromptOptions
{
Prompt = new Activity
{
Attachments = new List<Attachment>() { cardAttachment },
Type = ActivityTypes.Message,
Text = "waiting for user input...", // You can comment this out if you don't want to display any text. Still works.
}
};
// Display a Text Prompt and wait for input
return await stepContext.PromptAsync(nameof(TextPrompt), opts);
}
private async Task<DialogTurnResult> HandleResponseAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Do something with step.result
// Adaptive Card submissions are objects, so you likely need to JObject.Parse(step.result)
await stepContext.Context.SendActivityAsync($"INPUT: {stepContext.Result}");
return await stepContext.NextAsync();
}
In your main bot class (<your-bot>.cs) (step 3):
var activity = turnContext.Activity;
if (string.IsNullOrWhiteSpace(activity.Text) && activity.Value != null)
{
activity.Text = JsonConvert.SerializeObject(activity.Value);
}

Botframework V4: Question about Input forms cards

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.

Categories