Using custom channel data in bot messages

One of the many benefits of the Microsoft Bot Framework is that it allows the creation of bots capable of running on a multitude of channels (Skype, Facebook Messenger, Slack, etc.). However, due to the subtle differences between these platforms, it is impractical for the framework to cover all possible message types for each one. Even though Bot Builder SDK allows for all sorts of rich message templates, there are still some types of messages, such as a Facebook Messenger Share button, that require you to set the custom channel data of the outgoing message.

What is channel data?

Before understanding how to modify channel data, it helps to first know what it is and why it is important.

When a message is sent between the user and the bot, there is internal data within the message that is specific to each channel, which allows the bot to properly communicate on that channel. This information is called channel data.

For example, when communicating over the Office 365 email channel, the channel data might contain information about the email’s body, subject, and recipients. The channel data for a message via Facebook Messenger might contain extra details about an attachment which specify a special button.

On most channels, modifying the channel data is unnecessary, but if the need arises, the Bot Builder SDK allows the freedom to adapt the underlying message structure.

Now that we have familiarized you with channel data, let’s see how to use it in code!

NodeJS Implementation

NOTE: All the code used for this section is available in a GitHub repository.

Changing channel data with the NodeJS SDK is as easy as instantiating a class and calling one function.

First, make sure you have instantiated your instance of the Bot Builder SDK:

var botbuilder = require("botbuilder");

Now, in order to get access to channel data, you must be inside one of your bot’s dialog handlers:

//root dialog

bot.dialog("/", function(session){
    //access channel data here...

});

For this example, we will use channel data to structure a generic template with a Facebook Messenger Share button.

From the documentation, Facebook requires our channel data to look like this:

"attachment": {
  "type": "template",
  "payload": {
    "template_type": "generic",
    "elements": [{
      "title": "Breaking News: Record Thunderstorms",
      "subtitle": "The local area is due for record thunderstorms over the weekend.",
      "image_url": "https://thechangreport.com/img/lightning.png",
      "buttons": [{
              "type":"element_share"
      }]
    }]
  }
}

In order to mimic this format, we will use the Message class of the SDK, and the .sourceEvent() function (see documentation).

When calling the Message class, you can pass the context of the current dialog session into the constructor, which automatically populates the message’s address information with details such as…

  • user ID
  • bot ID
  • channel ID
  • conversation ID (optional)

…so that you don’t have to. Knowing that, our updated code will look like this:

//root dialog

bot.dialog("/", function(session){
  //construct a new message with the current session context

  var msg = new botbuilder.Message(session).sourceEvent({
    //specify the channel

    facebook: {
      //format according to channel's requirements

      //(in our case, the above JSON required by Facebook)

      attachment: {
        type: "template",
        payload: {
          template_type: "generic",
          elements: [
          {
            title: "Microsoft Bot Framework",
            subtitle: "Check it out!",
            buttons: [{
                type: "web_url",
                url: "https://dev.botframework.com/",
                title: "Go to Dev Portal"
            },{
                //this is our share button

                type: "element_share"
            }]
          }
          ]
        }
      } //end of attachment

    }
  });

  //send message

  session.send(msg);
});

Code Breakdown:

We use new botbuilder.Message(session) to create a new instance of the Message class that will represent our outgoing message. We then call sourceEvent() to modify and customize the channel data to our specifications. It is also important to note that whenever you use sourceEvent() to modify channel data, you must specify the name of the channel the message is intended for. In our example, we did this here:

var msg = new botbuilder.Message(session).sourceEvent({
  //specify channel

  facebook: {
      //custom data here...

  }
});

That’s all you have to do in order to use custom channel data in NodeJS. See the Channels documentation for further details on the capabilities of each channel. You can also take a look at the sample code on Github with a working example of the code in this article.

C# Implementation

NOTE: All the code used for this section is available in a Github repository.

Modifying channel data in C# is very similar to the NodeJS procedure. However, because the channels tend to send information via JSON (JavaScript Object Notation), we need to use an external framework that allows us to convert C# code to JSON. This is where Json.NET comes in, which can be included into your project with the line:

using Newtonsoft.Json;

We will go into more detail about this later. For now we need to construct a new message to expose the ChannelData object. This is done in the ShareButtonDialog.cs file (in the Github repo).

ShareButtonDialog.cs

public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
  var message = await argument;
  //create a reply message
  var reply = context.MakeMessage();
  //create a channel data object to act as a facebook share button
  reply.ChannelData = .... ;
}

The above code will be called from our messages controller (in the Github repo) when our bot receives a message from Facebook.

Now that we have access to the ChannelData object of our message, can add our own custom JSON data. To do so, we need to use the Newtonsoft.Json assembly that we mentioned at the beginning of this section.

Creating JSON Objects in C#

To create a proper JSON object, we need to define a class for each of the nested objects within our required format. For example, according to the documentation this the format required by Facebook to create a Share Button:

"attachment": {
  "type": "template",
  "payload": {
    "template_type": "generic",
    "elements": [
      {
      "title": "Breaking News: Record Thunderstorms",
      "subtitle": "The local area is due for record thunderstorms over the weekend.",
      "image_url": "https://thechangreport.com/img/lightning.png",
      "buttons": [{
          "type": "element_share"
          }           
        ]}
      ]}
}

Ultimately, this breaks down into several objects:

  • channelData (outermost wrapper)
    • attachment
  • attachment
    • type
    • payload
  • generic template (inside of payload)
    • elements
  • generic template content (inside of elements)
    • title
    • subtitle
    • image_url
    • buttons
  • facebook share button (inside of buttons)

After this section, we will end up with the following C# classes:

  • FacebookChannelData
  • FacebookAttachment
  • FacebookGenericTemplate
  • FacebookGenericTemplateContent
  • FacebookShareButton

We will now create a file for each class. (It is useful to create a folder in your solution explorer called “Models” or something similar to store these files in.) Let’s start from the top, outermost layer of our JSON structure, and work our way inward. This file is in the Github repository.)

FacebookChannelData.cs

namespace Azure_Bot_Generic_CSharp.Models
{
  using Newtonsoft.Json;
  public class FacebookChannelData
  {
    [JsonProperty("attachment")]
    public FacebookAttachment Attachment
    {
      get;
      internal set;
    }
  }
}

At the very top of each of these class files, we make sure to include the Newtonsoft.Json assembly, as we will be assigning attributes to the members of each class that will define their JSON representation. For example, with this class, we want to create a C# object, FacebookChannelData, that will house the first object in our notation.

The desired JSON representation for this object is:

{
  "attachment": {}
}

In C#, we define this by creating an object, FacebookChannelData, with an Attachment member. We then assign an attribute to this property,

[JsonProperty("attachment")]

that will be used to convert it to its desired JSON representation: attachment. So essentially,

FacebookChannelData.Attachment

is converted to:

{
  "attachment": {}
}

Now, if we go a level lower, to define our FacebookAttachment object (in the Github repo) that will be nested inside FacebookChannelData, we follow the same pattern:

FacebookAttachment.cs

public class FacebookAttachment
{
  public FacebookAttachment()
  {
    this.Type = "template";
  }
  [JsonProperty("type")]
  public string Type { get; set; }
  [JsonProperty("payload")]
  public dynamic Payload { get; set; }
}

This C# object will convert to the following JSON:

{
  "type": "template",
  "payload": {}
}

This is done for each level of our desired JSON structure until it is completely filled out. Then, we just have to connect it all together in the ShareButtonDialog.cs file (in the Github repo).

ShareButtonDialog.cs

//create a reply message
var reply = context.MakeMessage();
//create a channel data object to act as a facebook share button
reply.ChannelData = new FacebookChannelData()
{
  Attachment = new FacebookAttachment()
  {
    Payload = new FacebookGenericTemplate()
    {
      Elements = new object[]
      {
        new FacebookGenericTemplateContent()
        {
          Buttons = new[]
          {
              new FacebookShareButton()
          }
        }
      }
    }
  }
};

//send message
await context.PostAsync(reply);

This will produce a Generic Template containing a Share Button in Facebook Messenger! See the documentation for further details on the capabilities of each channel.

You can also take a look at the Github repository containing a working example of the code above.

Happy Making!

Tony Anziano from the Bot Framework team