8
votes

The goal

I want to create (and then edit) Adaptive Cards designs with the Adaptive Cards Designer and use them in my bot.

The steps

1) I created my Adaptive card in the Designer, with sample values, and copied its JSON representation. For example:

{
  "type": "AdaptiveCard",
  "body": [
    {
      "type": "FactSet",
      "id": "myFactSet",
      "facts": [
        {
          "title": "Name:",
          "value": "John Doe"
        }
      ]
    }
  ],
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "version": "1.0"
 }

2) Now I want to use this card's design in my C# Bot Framework bot. I put this JSON into my project, where AdaptiveCards v1.1.0 nuget package is included.

3) I parse the card JSON into AdaptiveCards library classes with this line of code:

var card = AdaptiveCard.FromJson(card).Card;

4) Before sending the card to the user I have to fill it with real values, replacing the sample data.

The questions

  1. How do I replace "John Doe" with a real user name? What is the simplest approach? (I already wrote an extension method to get an AdaptiveElement by its id, so I can easily change its values. But tell me please if there is a simpler approach)

  2. And how do I clone an existing AdaptiveElement and put other values into it? For example, if I want to add another FactSet to the card by copying existing myFactSet with all the same style? AdaptiveElement has no Clone() method.

Why other solutions don't fit

Write it in C#

I want to be able to easily edit the card's design with the Designer, but if I rewrite the whole card in C# code, I'll have to do make a change to it every time the card's design changes, and I won't see the result in the Designer.

Do it in javascript

I understand it's easily done with javascript, but I use C# in my bot.

Parse JSON and work with it

I also understand that I can parse the card into JsonObject and deal directly with JsonObjects, clone them, change their properties, but

  1. they are not typed, so there's no IntelliSense support and I can make a typo

  2. I still have to walk all the elements to find the one with the given id (by the way, is there an extension method to easily do this?)

  3. Using JsonObject will not eliminate the need of AdaptiveCards library and the need to call AdaptiveCard.FromJson(card) method (because setting Attachment.Content to a JsonObject doesn't work for some reason).

6
@stuartd, what for? AdaptiveCards package already provides C# types to access JSON-defined objects.Artemious
This is something the adaptive cards team is actively working on solving, but we won't have anything to share until next year -- hopefully within the first few months. I actually demo a preview of this here: youtu.be/GJkep8wToVA?t=3773. Any feedback most welcome on our GitHubMatt Hidinger
@MattHidinger great! However, it seems to solve only half the problem. Will we be able to clone an item inside a container? (e.g. I want to add another row in a table, same style, different values)Artemious
Is my answer acceptable?Kyle Delaney

6 Answers

1
votes

Remember that JSON is just a string, and I think both of your problems can be solved with string manipulation.

  1. How do I replace "John Doe" with a real user name? What is the simplest approach?

You can replace "John Doe" in the JSON before you convert it into a C# object:

json = json.Replace("John Doe", userName);
var card = AdaptiveCard.FromJson(json).Card;
  1. And how do I clone an existing AdaptiveElement and put other values into it?

The question of deep copying in C# is much discussed. With some ingenuity, you can make a deep copy of any object and Adaptive Card elements are no different. You can use a technique that copies any object or you can make a function that's tailored specifically to Adaptive Cards. Since Adaptive Cards are all about JSON, consider converting the element to JSON and then back again:

var factSet = card.Body.First() as AdaptiveFactSet;
factSet = JsonConvert.DeserializeObject<AdaptiveFactSet>(JsonConvert.SerializeObject(factSet));
factSet.Facts.First().Value = userName;
card.Body.Add(factSet);

However, you may not even need to worry about deep-copying since you're starting out with JSON. Maybe you can just take the JSON for a specific element from the designer and then deserialize it multiple times:

factSet1 = JsonConvert.DeserializeObject<AdaptiveFactSet>(factSetJson);
factSet2 = JsonConvert.DeserializeObject<AdaptiveFactSet>(factSetJson);

I want to be able to easily edit the card's design with the Designer, but if I rewrite the whole card in C# code, I'll have to do make a change to it every time the card's design changes, and I won't see the result in the Designer.

If you have your card represented in both C# and JSON, then it shouldn't be too difficult to make a small change in the designer to see how it looks and then modify your C# accordingly. If you think it would be really cool to have a tool that can take a card's JSON and automatically write the C# that could generate it, then I agree :)

1
votes

we (the Adaptive Cards team) are working specifically on this! See our post on GitHub to learn more.

We have prototype JSON templating libraries available for .NET and JS, and would love if you start trying out our prototypes and validating whether it works for your scenarios! Reach out to us on GitHub if you'd like to get started now :)

1
votes

The solution is to use the new feature: Data Binding in Adaptive Cards (Preview).

This supports placeholders like {name},support for splitting template and content via $data, and much more:

  • Binding syntax starts with { and ends with }. E.g., {myProperty}
  • Dot-notation to access sub-objects
  • Indexer syntax to retrieve properties by key or items in an array
  • Graceful null handling for deep hierarchies
  • Escape syntax documentation to come soon

NOTE: "Currently you need to build the SDKs from source in order to use data binding outside of the designer."

(Link is in preview and URL will likely change after general availability.)

1
votes

We can use Adaptive Card Templating SDKs

Here is the link for templating approach in .Net https://docs.microsoft.com/en-us/adaptive-cards/templating/sdk#net

Here is the link for templating approach in javascript. https://docs.microsoft.com/en-us/adaptive-cards/templating/sdk#javascript

0
votes

I didn't yet try the new Data Binding feature, so I can't tell if it fully meets my needs, but what I can't see from the description is copying adaptive card nodes (for dynamic lists of item in the adaptive card).

I used Adaptive Cards v1, so I had to clone adaptive card nodes and change the values according to my data. Here's some code from my project

0
votes

I use RazorEngine to process the card json generated from the designer against a data model object and replace the Razor variables with real values. Then use AdaptiveCard.FromJson() to parse the card again and get an AdaptiveCard that can be easily attached.

My implementation predates the templating that the AdaptiveCard folks are building out, so you might want to try that first, now, although I'm not sure it is generally available or what the platform support is like. I have found that Razor works very well in most cases, and building on a pretty battle-tested templating library, particularly one that doesn't make use of curly-braces for it's replacement tokens, has been very simple.

Rough gist of the process:

Json from designer:

{
"type": "AdaptiveCard",
"body": [
    {
        "type": "TextBlock",
        "text": "Hi @Model.Username"
    }
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"

}

Create the RazorEngine templater:

var config = new TemplateServiceConfiguration() {
    EncodedStringFactory = new RawStringFactory() // This encodes values as plain strings, rather than HTML-encoded
};
var engine = RazorEngineService.Create(config);

Create the model object:

var modelObject = new object {
    Username = "Foo Barrington"
}

Run the json through the templater:

json = engine.RunCompile(json, Guid.NewGuid().ToString(), null, modelObject);

Parse the json to an AdaptiveCard instance:

var parsedCard = AdaptiveCard.FromJson(json);
if (parsedCard.Warnings.Any()) {
    // handle parsing errors...
}
AdaptiveCard card = parsedCard.Card;

Build the Attachment with the templated and parsed card:

var attachment = new Attachment(AdaptiveCard.ContentType, content: card);