Proactive Messages in Microsoft Bot Framework

January 21, 2021

Proactive messaging provides your bot the ability to notify the user with messages that can be written and modified by the developer.


Create

In this demo we will be using a core bot created in Azure using Azure Bot Service. To create one you can visit this post.

Open the project in Visual Studio and create a new class named NotifyController.cs. Copy and paste the following code. Change the string in line 59 to the message you would like to appear in your bot.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Schema;
using Microsoft.BotBuilderSamples.Dialogs;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace CoreBot.Controllers
{
    [Route("api/notify")]
    [ApiController]
    public class NotifyController : ControllerBase
    {
        private readonly IBotFrameworkHttpAdapter _adapter;
        private readonly string _appId;
        private readonly ConcurrentDictionary<string, ConversationReference> _conversationReferences;

        public NotifyController(IBotFrameworkHttpAdapter adapter, IConfiguration configuration, ConcurrentDictionary<string, ConversationReference> conversationReferences)
        {
            _adapter = adapter;
            _conversationReferences = conversationReferences;
            _appId = configuration["MicrosoftAppId"];

            // If the channel is the Emulator, and authentication is not in use,
            // the AppId will be null.  We generate a random AppId for this case only.
            // This is not required for production, since the AppId will have a value.
            if (string.IsNullOrEmpty(_appId))
            {
                _appId = Guid.NewGuid().ToString(); //if no AppId, use a random Guid
            }
        }

        public async Task<IActionResult> Get()
        {
            foreach (var conversationReference in _conversationReferences.Values)
            {
                await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, BotCallback, default);
            }

            // Let the caller know proactive messages have been sent
            return new ContentResult()
            {
                Content = "<html><body><h1>Proactive messages have been sent.</h1></body></html>",
                ContentType = "text/html",
                StatusCode = (int)HttpStatusCode.OK,
            };
        }

        private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
        {
            // If you encounter permission-related errors when sending this message, see
            // https://aka.ms/BotTrustServiceUrl

            await turnContext.SendActivityAsync("This is a proactive message!");
        }
    }
}


Implement

Open the MainDialog.cs class and add the following using statement.

      using System.Collections.Concurrent;

Create the ConcurrentDictionary at the top of the file.

      private static ConcurrentDictionary<string, ConversationReference> _conversationReferences;

Change the constructor to include the ConcurrentDictionary<string, ConversationReference> conversationReferences) as an argument in line 1 and initialize the _conversationReferences in line 7.

public MainDialog(FlightBookingRecognizer luisRecognizer, BookingDialog bookingDialog, ILogger<MainDialog> logger, ConcurrentDictionary<string, ConversationReference> conversationReferences)
    : base(nameof(MainDialog))
{
    _luisRecognizer = luisRecognizer;
    Logger = logger;

    _conversationReferences = conversationReferences;

    AddDialog(new TextPrompt(nameof(TextPrompt)));
    AddDialog(bookingDialog);
    AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
    {
        IntroStepAsync,
        ActStepAsync,
        FinalStepAsync,
    }));

    // The initial child Dialog to run.
    InitialDialogId = nameof(WaterfallDialog);
}

Add the AddConversationReference function later in the file.

public static void AddConversationReference(Activity activity)
{
    var conversationReference = activity.GetConversationReference();
    _conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
}

Open the DialogBot.cs class and override the OnConversationUpdateActivityAsync by inserting the following code to the file.

protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
    MainDialog.AddConversationReference(turnContext.Activity as Activity);

    return base.OnConversationUpdateActivityAsync(turnContext, cancellationToken);
}

Lastly open the Startup.cs class and add the two following using statements.

      using Microsoft.Bot.Schema;
      using System.Collections.Concurrent;

Add the following line in the ConfigureServices function.

      services.AddSingleton<ConcurrentDictionary<string, ConversationReference>>();


Test

To test, simply run your bot and load up the emulator like normally, you should get the following messages.


Now to trigger the proactive message click the following link: http://localhost:3978/api/notify
You should see this page in your browser informing you that the Proactive messages have been sent.


Finally, check your emulator, your bot has sent a new message. The text and type of the message is determined by NotifyController.cs so you can make any changes you need. At this point you can refresh the proactive messages page as many times as you like and each will sent a new message to the user.


This is how you send a massage to the user without him opening a dialog. You can schedule your messages as you like to notify the user according to his needs.

About Me

Hi, my name is Demetris Bakas and I am a student that loves to write code and be creative. I always find new technologies intriguing and I like to work with other people and be a part of a team. My goal is to develop software that people will find useful and will aid them in their everyday lives.
For any questions feel free to contact me at social media using the links below.