This is the sample on Botframework v4 docs. But it does not work.
It says "Can't Render Card" on the Microsoft bot emulator.
What i'm trying to do is a carouselCard but this simple card from Microsoft's sample is already not working.
{
"type": "message",
"text": "Plain text is ok, but sometimes I long for more...",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "TextBlock",
"text": "Hello World!",
"size": "large"
},
{
"type": "TextBlock",
"text": "*Sincerely yours,*"
},
{
"type": "TextBlock",
"text": "Adaptive Cards",
"separation": "none"
}
],
"actions": [
{
"type": "Action.OpenUrl",
"url": "http://adaptivecards.io",
"title": "Learn More"
}
]
}
}
]
}
However, if i remove the top part of the code this code works:
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "TextBlock",
"text": "Hello World!",
"size": "large"
},
{
"type": "TextBlock",
"text": "*Sincerely yours,*"
},
{
"type": "TextBlock",
"text": "Adaptive Cards",
"separation": "none"
}
],
"actions": [
{
"type": "Action.OpenUrl",
"url": "http://adaptivecards.io",
"title": "Learn More"
}
]
}
This is how i call the card. Is there a better way to do this?
public class GetNameAndAgeDialog : WaterfallDialog
{
private readonly string _cards = #".\Resources\TryCarouselCard.json";
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;
}
public GetNameAndAgeDialog(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);
return await stepContext.ContinueDialogAsync();
});
}
}
The "top part" of the first block of JSON you posted is the card contained within an activity. The second block of JSON posted is indeed just the card itself and what you'd want to put into an Attachment.
As for your code, it looks correct to me. I might consider caching the attachment JSON as you probably don't want to hit the file system every time you want to display the card, but that would just be an optimization.
I'm unclear if you are experiencing any further problems or just looking for validation of the approach now. If you are still experiencing a problem please update the question with some more details and I'll update my answer to try and help.
Related
when i am trying to configure tab in teams channel,i followed the steps from article https://learn.microsoft.com/en-in/microsoftteams/platform/tutorials/get-started-dotnet-app-studio i could not find selection of tab and save button enabled , why this happned? how to solve it?, I am using echo bot template which is running on bot emulator properly.
Following is the json
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
"manifestVersion": "1.5",
"version": "1.0.0",
"id": "2fac8fdd-7fa3-451f-8562-adba3ab84c8d",
"packageName": "com.contoso.helloworld",
"developer": {
"name": "Hello World App ",
"websiteUrl": "https://www.microsoft.com",
"privacyUrl": "https://www.microsoft.com/privacy",
"termsOfUseUrl": "https://www.microsoft.com/termsofuse"
},
"icons": {
"color": "color.png",
"outline": "outline.png"
},
"name": {
"short": "janApp",
"full": "janApp"
},
"description": {
"short": "SWAN Bot App for Microsoft Teams",
"full": "This sample app provides a very simple app for Microsoft Teams. You can extend this to add more content and capabilities."
},
"accentColor": "#A4D344",
"configurableTabs": [
{
"configurationUrl": "https://echobot57.azurewebsites.net/configure",
"canUpdateConfiguration": true,
"scopes": [
"team",
"groupchat"
]
}
],
"staticTabs": [
{
"entityId": "com.contoso.helloworld.hellotab",
"name": "Botdemo",
"contentUrl": "https://echobot57.azurewebsites.net/hello",
"websiteUrl": "https://echobot57.azurewebsites.net/hello",
"scopes": [
"personal"
]
}
],
"bots": [
{
"botId": "330b4c4d-5f92-4053-a38f-7d4593cfd18a",
"scopes": [
"personal",
"team",
"groupchat"
],
"supportsFiles": true,
"isNotificationOnly": true
}
],
"composeExtensions": [
{
"botId": "330b4c4d-5f92-4053-a38f-7d4593cfd18a",
"canUpdateConfiguration": true,
"commands": [
{
"id": "getRandomText",
"type": "query",
"title": "Get some random text for fun",
"description": "Gets some random text and images",
"initialRun": true,
"fetchTask": false,
"context": [
"commandBox",
"compose",
"message"
],
"parameters": [
{
"name": "cardTitle",
"title": "Card title",
"description": "Card title to use",
"inputType": "text"
}
]
}
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"echobot57.azurewebsites.net"
]
}
Please make sure the tab configuration url is correct.
If it is correct, you should be able to access it.
https://microsoftteamssampleshelloworldweb20200107021253.azurewebsites.net/configure
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);
}
}
}
I am trying to post a Messageback Adaptive card but I am below getting error in emulator:
The card could not be rendered. It is either malformed or uses features not supported by this host.
I am using AdaptiveCards 1.2.2 version. I am using C# to post this adaptive card but I am not able to figure out the issue.
My Json File:
{
"type": "AdaptiveCard",
"selectAction": {
"type": "Action.Submit"
},
"body": [
{
"type": "TextBlock",
"text": "1:1 with John Doe",
"weight": "Bolder"
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"url": "https://images.idgesg.net/images/article/2019/04/google-calendar-android-100794956-large.jpg",
"altText": "Calendar",
"size": "small"
}
],
"width": "auto"
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"text": "Tomorrow, 30 May"
}
],
"width": "stretch"
}
]
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"url": "https://pbs.twimg.com/profile_images/3647943215/d7f12830b3c17a5a9e4afcc370e3a37e_400x400.jpeg",
"altText": "Calendar",
"height": "20px"
}
],
"width": "auto"
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"text": "John Doe",
"spacing": "Medium"
}
],
"width": "stretch"
}
]
},
{
"type": "TextBlock",
"text": "Slots available - **1 hr duration**",
"separator": true,
"spacing": "Medium",
"isSubtle": true
},
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"actions": [
{
"type": "Action.Submit",
"title": "11:00 AM",
"data": {
"msteams": {
"type": "messageBack",
"displayText": "11:00 AM",
"text": "text to bots",
"value": "{\"bfKey\": \"bfVal\", \"conflictKey\": \"from value\"}"
}
}
}
]
}
],
"width": "auto"
},
{
"actions": [
{
"type": "Action.Submit",
"title": "Cancel",
"data": {
"msteams": {
"type": "messageBack",
"displayText": "Cancel",
"text": "text to bots",
"value": "{\"bfKey\": \"bfVal\", \"conflictKey\": \"from value\"}"
}
}
},
{
"type": "Action.Submit",
"title": "Confirm",
"data": {
"msteams": {
"type": "messageBack",
"displayText": "Confirm",
"text": "text to bots",
"value": "{\"bfKey\": \"bfVal\", \"conflictKey\": \"from value\"}"
}
}
}
]
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
}
A great way to test your Adaptive Cards is to use the Adaptive Card Designer or the card editor in App Studio in Microsoft Teams. If a preview isn't rendered then there's something wrong with your card.
In your case, you say you want to use Adaptive Cards 1.2.2 but you're including "version": "1.0" in your JSON. You might want to say "version": "1.2" instead, but keep in mind that the higher the version of your Adaptive Card the fewer clients will be able to render it.
More importantly, you're putting an actions property in both columns and column sets and neither of those have an actions property. Please refer to the schema to see what elements have what properties. You might mean to use an action set (a 1.2 feature), or you might just want to use the actions property of the card itself outside of the card's body.
More importantly still, you have more opening brackets than closing brackets and so your JSON is invalid. I suspect you forgot to close your last column set and its columns array. Please use an editor like Visual Studio Code to automatically format your JSON and make sure it's valid. It's also good etiquette to correctly format your JSON in your Stack Overflow question so that other people can read it without formatting it for you.
Please read my latest blog post for more information about Adaptive Cards and using them with the Microsoft Bot Framework.
private Attachment CardExample()
{
AdaptiveCard card = new AdaptiveCard("1.0");
card.Body.Add(new AdaptiveContainer()
{
Style = AdaptiveContainerStyle.Emphasis,
Items = new List<AdaptiveElement>()
{
new AdaptiveColumnSet()
{
Type = "ColumnSet",
Height = AdaptiveHeight.Auto,
SelectAction = new AdaptiveSubmitAction()
{
Type="Action.Submit",
Id = "Submit",
Title="Submit",
}
},
}
});
Attachment TestCard = new Attachment
{
ContentType = AdaptiveCard.ContentType,
Content= JsonConvert.DeserializeObject(JsonConvert.SerializeObject(card)),
};
return TestCard;
}
//It doesn't have any compiling error but when the adaptive card is rendered the Submit button don't come up
//only an empty container
ColumnSets are able to contain an on click action but there is no button dispayed, it's really as simple as that. If I click on the ColumnSet, it performs the action.
I've included some images, etc. below that illustrate it working. It's a bit clunky but you can see what I mean.
The example is an OpenUrl and it opens up to google.com, a Submit action works the same, just include the data you want to send back to the bot and the catch it and deal with it in OnTurnAsync (if you're using v4 framework) ...
I know this answer is lacking but ... it is the answer. :-)
The issue with your implementation is that you are adding a selectAction on an empty container. A column set needs columns and each column needs content. In your case, there are no actual items in the columns and hence there will be nothing to click on.
Here is an example of how the column set should be implemented :
{
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "ColumnSet",
"spacing": "medium",
"columns": [
{
"type": "Column",
"width": "auto",
"items": [
{
"type": "Image",
"url": "https://unsplash.it/80?image=1083",
"size": "medium"
}
]
},
{
"type": "Column",
"width": 4,
"items": [
{
"type": "TextBlock",
"text": "Silver Star Mountain"
},
{
"type": "TextBlock",
"text": "Maps",
"isSubtle": true,
"spacing": "none"
}
]
}
],
"selectAction": {
"type": "Action.OpenUrl",
"title": "Silver Star Mountain",
"url": "ms-cortana:silver-star-mountain"
}
}
]
}
Is it possible to include action inside columnSet/column in the adaptive cards ?
For example I want something like this :-
----- Body
|___ columnSet
|___ column1
| |___textblock
| |___image
| |___action.url
|
|___ column2
| |___textblock
| |___image
| |___action.url
I did try it with visualiser but the action.url doesn't show despite the visualiser did not show any error.
Thank you for your help.
Column has a selectAction property that can turn the column into a hit target.
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"text": "Column 1"
},
{
"type": "Image",
"url": "http://adaptivecards.io/api/cat"
}
],
"selectAction": {
"type": "Action.OpenUrl",
"title": "cool link",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
See here for a working example:
http://adaptivecards.io/visualizer/?card=/explorer/cards/Column.SelectAction.json
I appreciate this is an old question but figured could be useful to share...
Yes you can. Programatically in C#:
AdaptiveCard adaptiveCard = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0));
adaptiveCard.Body.Add(new AdaptiveContainer()
{
Items =
{
new AdaptiveColumnSet()
{
Columns =
{
new AdaptiveColumn()
{
Width = AdaptiveColumnWidth.Stretch,
Items =
{
new AdaptiveActionSet()
{
Actions =
{
new AdaptiveOpenUrlAction()
{
Title = "open url",
Url = new System.Uri("https://www.blah.com")
}
}
}
}
}
}
}
}
});
JSON schema:
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json", //or wherever your schema is held
"version": "1.2",
"body": [
{
"type": "Container",
"items": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "ActionSet",
"actions": [
{
"type": "Action.OpenUrl",
"title": "open url",
"url": "https://www.blah.com"
}
]
}
]
}
]
}
]
}
]
}
If you don't have control over the card rendering, then I don't think so.
Per the documentation: "actions - Many cards have a set of actions a user may take on it. This property describes those actions which typically get rendered in an "action bar" at the bottom."
It says typically, but it seems they are always rendered at the bottom of the card. Unless you are displaying adaptive cards within your own application, I don't think it's possible to make changes to the way they are rendered.
A potential workaround would be to use a selectaction on an image, and then make your own button for the image. It should work the same way as an action at that point, but will be able to placed anywhere you can place an image. There is the downside of needing to create and host an image for your button though.