Introduction
This article explains how to create a proactive bot using Microsoft Bot Framework. The nice example is given at Microsoft Bot framework documentation here. https://docs.botframework.com/en-us/azure-bot-service/templates/proactive/
What is a Proactive Bot and Proactive Messages
A proactive bot is a bot which sends the messages to the user proactively. I mean, without user interaction for information. i.e. Alarm Bot.
To achieve this functionality,Bot framework uses proactive messages. examples of Proactive messages are Discount Notifications, latest items notifications etc. which comes to your chatbot automatically on some timely basis.
PreRequisites for Proactive Bot
To start developing you will need to following things.
- Bot deplyoed on bot framework and configured on any channel i.e. skype.
- Directline Channel configured on the Bot Framework.
- Azure account with permissions of creating Azure functions and Azure Database.
Background
The article is divided in two sections. In section 1 will show how to create a basic proactive bot message and in another section 2 will show how to manage it with good user interface with azure database.
Section#1
Basic Proactive Bot
Basically to send a proactive message to bot we need to trigger the bot for the messages. The bot can be triggered in many ways, but the easiest way to trigger a bot to use an Azure Function. By using Azure Functions we can set the out put to the Bot (This will be explained later in this blog).
So to begin with first create the bot solution in visual studio. And name it as ProactiveBot.
Than create a class file named ProactiveBusiness.cs, than add the following code snippet in your ProactiveBusiness.cs class.
[Serializable]
public class ProactiveBusiness : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
}
}
Your message controller will look like this.
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity != null)
{
try
{
switch (activity.GetActivityType())
{
case ActivityTypes.Message:
activity.Text = activity.RemoveRecipientMention();
await Conversation.SendAsync(activity, () => new ProactiveBusiness());
break;
}
}
catch (Exception ex)
{
}
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Now we need a mediator which stores the data and pass to Bot. We can store the data to the AzureQueue storage to start proactive messaging.
Here, to receive the proactive messages from the user we need the message along with ResumptionCookie
of the user. By using which we can identify the user and send the proactive message. So, we will save the message with ResumptionCookie and the text in the json format in the queue.
To do so, first create a class QueueMessage
for the message in the ProactiveBusiness.cs class as below.
[Serializable]
public class QueueMessage
{
public ResumptionCookie ResumptionCookie;
public string MessageText;
}
than add the message and the resumption cookie in the QueueMessage
as shown below.
QueueMessage queueMessage;
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var msg = await argument;
ResumptionCookie resCookie = new ResumptionCookie(msg);
queueMessage = new QueueMessage
{
ResumptionCookie = resCookie,
MessageText = msg.Text
};
}
Now we will add this message to AzureQueue. Below are the steps to show hot to add message to AzureQueue. Good example is given at here
First, you need to add the nuget package to use the AzureStorage. Below is the link for it.
Microsoft Azure Storage Client Library for .NET
Create Azure Storage Account
Login to Azure and Create storage account as shown below.
After Creating the storage navigate to keys section and copy the storage account name and key (we will need this key to create a connection string).
our connection string will be in below format as per the above information.
DefaultEndpointsProtocol=https;AccountName=<storage-account-name>;AccountKey=<account-key>
Now we need to put this storage account connection string in our appconfig section in web.config file of bot solution as shown below.
<add key="StorageConnectionString" value="DefaultEndpointsProtocol=https;AccountName=<stroge-account-name>;AccountKey=<account-key>" />
write the following code to store the message to the queue
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
CloudQueue queue = queueClient.GetQueueReference("myqueue");
queue.CreateIfNotExists();
CloudQueueMessage messageNew = new CloudQueueMessage(JsonConvert.SerializeObject(queueMessage));
queue.AddMessage(messageNew);
Final code will look like this to store the current message to Azure Queue Storage.
(Sometimes it happen that the data stored in queue will not ber retrived, this is because of the timeout mechanism of queue)
References
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.Azure;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json;
Code
[Serializable]
public class QueueMessage
{
public ResumptionCookie ResumptionCookie;
public string MessageText;
}
[Serializable]
public class ProactiveBusiness : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
QueueMessage queueMessage;
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var msg = await argument;
ResumptionCookie resCookie = new ResumptionCookie(msg);
queueMessage = new QueueMessage
{
ResumptionCookie = resCookie,
MessageText = msg.Text
};
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
CloudQueue queue = queueClient.GetQueueReference("myqueue");
queue.CreateIfNotExists();
CloudQueueMessage messageNew = new CloudQueueMessage(JsonConvert.SerializeObject(queueMessage));
queue.AddMessage(messageNew);
}
}
Create Azure Functions
Our first task to store data to azure queue is complete. Now we will create a queue triggered function in azure.
This function will be triggered automatically when any item will be added int the queue. and we will set the output of function to our bot in the azure function.
To do so, Create a function app and give it any name you want. Here, while creating the function app make sure to select the Storage account which you have created to store the queue data.
After creating the function app, navigate to it and create a QueueTrigger
function from the Create Custom Function section. Here the list of functions will be given and we need to select QueueTriggered-CSharp
.
On selecting it scroll down the page and provide the QueueTriggeredFunction
in the 'Name your Function' section. Further, in Queue Name give the name of the queue we have created i.e. myqueue . In the Storage Account connection section we need to give the connection string to our storage account.
If you already have created the connection string than select it, Or you can add it later on (for now save it from any connection string available).
Add connection string in AzureFunction
To add connection string go to the function app settings of the function app you have just created.
Here under develop section there will be Application Settings. click on configure app settings section button besides Application Settings.
This will open up the settings section . There will be different section there i.e. General Settings, Debuggin, App Settings etc.. Here we need to go to the AppSettings and add the key of our storage account. Provide the name of the key in key textbox i.e. StorageConnectionString
and the value of the conection string as used in our code above. i.e. DefaultEndpointsProtocol=https;AccountName=<stroge-account-name>;AccountKey=<account-key>
, and then save the page. The storage connection string will populate in the connection string section of the app in some time.
Once the connection string populates, click on our QueueTriggered
Function and than click on Integrate section. Here a drop down will be there to select the conection string, select StorageConnectionString
and save it
Set Output of function to Bot Framework
Now, we need to set the output to Bot framework, to do so go to our QueueTriggeredFunction
and click on the Integrate Section belwo it. Here, we will find the Output Section on right side. We need to click on +New Output this will open the Output settings panel below it. Here we need to provide three things.
1. Bot Parameter Name 2. Sender Id 3. Direct Line Key
1. Bot Parameter Name: This variable will be used to store the data for bot and we willl return the value to the BotFramework. We will give name it as message
2. Sender Id: This is the Id of our Bot, will be used to identify the bot on bot framework
3. Directline Key: Our bot will receive data from direct line, so here we need to configure the key of direct line client of our bot. To set the directline key first we need to activate the directline channel on bot framework and retrive the key. Than we need to provide that key in the Directline Key section.
The Code
After setting up the input and output go to the develop section from left side navigation. Here the area is given where we can code and write our own logic. Below is the code by using which we read the data from the queue and give it as output to the bot framework
Below is the code which should be pasted in the QueueTriggered
Function
using System;
using System.Net;
using System.Net.Http;
using Microsoft.Azure.WebJobs.Host;
public class BotMessage
{
public string Message { get; set; }
}
public static BotMessage Run(string myQueueItem, out BotMessage message, TraceWriter log)
{
log.Info($"C# Queue trigger function process started: {myQueueItem}");
message = new BotMessage
{
Message = myQueueItem
};
log.Info($"C# Queue trigger function process Complete: {myQueueItem}");
return message;
}
Here the myQueueItem
variable contains the data from the queue and it will be saved to message variable. And it will return the variable message to the bot framework.
This will create a trigger in our bot. Now we need to write the code to handle the trigger and show appropriate message.
Receive the triggered Message
Below is our trigger code to show the triggered text from QueueTriggeredFunction
. We need to write this code in MessageController.cs
case ActivityTypes.Trigger:
ITriggerActivity trigger = activity;
var message = JsonConvert.DeserializeObject<QueueMessage>(((JObject)trigger.Value).GetValue("Message").ToString());
if (!string.IsNullOrEmpty(message.MessageText))
{
var messageactivity = (Activity)message.ResumptionCookie.GetMessage();
var client = new ConnectorClient(new Uri(messageactivity.ServiceUrl));
activity.Text = activity.RemoveRecipientMention();
Activity replyToConversation = messageactivity.CreateReply();
replyToConversation.Recipient = activity.From;
replyToConversation.Type = "message";
replyToConversation.Attachments = new List<Attachment>();
List<CardImage> cardImages = new List<CardImage>();
cardImages.Add(new CardImage(url: "http://cdn.wonderfulengineering.com/wp-content/uploads/2016/02/iron-man-wallpaper-22.jpg"));
List<CardAction> cardButtons = new List<CardAction>();
CardAction plButton = new CardAction()
{
Value = "https://en.wikipedia.org/wiki/Iron_Man",
Type = "openUrl",
Title = "Ironman Wiki"
};
cardButtons.Add(plButton);
HeroCard plCard = new HeroCard()
{
Title = message.MessageText,
Subtitle = "Triggered Iron Man Card",
Images = cardImages,
Buttons = cardButtons
};
Attachment plAttachment = plCard.ToAttachment();
replyToConversation.Attachments.Add(plAttachment);
await client.Conversations.ReplyToActivityAsync(replyToConversation);
}
break;
The above code will get the messages triggered from Azure Queue. It will than parse the data to JSON object and get the resumption cookie and message. Than we created the activity based on the resumption cookie and send the hero card to the user.
Final messagecontroller will look like this :
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity != null)
{
try
{
switch (activity.GetActivityType())
{
case ActivityTypes.Message:
activity.Text = activity.RemoveRecipientMention();
await Conversation.SendAsync(activity, () => new ProactiveBusiness());
break;
case ActivityTypes.Trigger:
ITriggerActivity trigger = activity;
var message = JsonConvert.DeserializeObject<QueueMessage>(((JObject)trigger.Value).GetValue("Message").ToString());
if (!string.IsNullOrEmpty(message.MessageText))
{
var messageactivity = (Activity)message.ResumptionCookie.GetMessage();
var client = new ConnectorClient(new Uri(messageactivity.ServiceUrl));
activity.Text = activity.RemoveRecipientMention();
Activity replyToConversation = messageactivity.CreateReply();
replyToConversation.Recipient = activity.From;
replyToConversation.Type = "message";
replyToConversation.Attachments = new List<Attachment>();
List<CardImage> cardImages = new List<CardImage>();
cardImages.Add(new CardImage(url: "http://cdn.wonderfulengineering.com/wp-content/uploads/2016/02/iron-man-wallpaper-22.jpg"));
List<CardAction> cardButtons = new List<CardAction>();
CardAction plButton = new CardAction()
{
Value = "https://en.wikipedia.org/wiki/Iron_Man",
Type = "openUrl",
Title = "Ironman Wiki"
};
cardButtons.Add(plButton);
HeroCard plCard = new HeroCard()
{
Title = message.MessageText,
Subtitle = "Triggered Iron Man Card",
Images = cardImages,
Buttons = cardButtons
};
Attachment plAttachment = plCard.ToAttachment();
replyToConversation.Attachments.Add(plAttachment);
await client.Conversations.ReplyToActivityAsync(replyToConversation);
}
break;
}
}
catch (Exception ex)
{
}
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Test My Code
To test the code we have done above , we need to publish the code to azure and need to configure the directline API for the bot (which we have done in the previous steps of the article). OR If you know how to use ngrok, you can also directly check it without publishing to azure. The good link to use ngrok is given at https://robinosborne.co.uk/2016/09/19/debugging-botframework-locally-using-ngrok/
To test the proactive scenario, write any message to bot (currently I am using skype channel). and it will be returned back the text you have entered with notification card.
(The triggering activity may take some time for the first time as it uses direct line API, be patience and have a cup of coffee. Many times it may happen that we need to send multiple messages to trigger the activity.)
Hurray !! we are done configuring Proactive Bot. Now for any notifications you can add the message in the queue with resumption cookie and it will be delivered to your user.
#section 2 will be published soon.