Create Bot for Microsoft Graph with DevOps 7: BotBuilder features – Dialog 101

I already setup basic DevOps CI/CD pipeline, I will focus on BotBuilder features from now on. Let’s start from Dialog system.

Overview

What makes a chatbot intelligent is understanding conversation or dialog. However, if you think about implementing such function to remember previous conversations with users and handle it later is tedious but troublesome. BotBuilder provides Dialog which already has these capabilities. The official document contains good enough information, so I won’t duplicate the effort. Please read it first. https://docs.microsoft.com/ja-jp/bot-framework/bot-design-conversation-flow

Use Dialog in O365Bot

By default, Bot Application template already implemented RootDialog concept. In MessagesController.cs, it always calls RoogDialog only. Last time, I implement getting events code inside RootDialog, but I should’ve create child dialog for it.

Create a child Dialog

1. Add GetEventsDialog.cs in Dialogs folder and replace the code.

using Autofac;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using O365Bot.Services;
using System;
using System.Threading.Tasks;

namespace O365Bot.Dialogs
{
    [Serializable]
    public class GetEventsDialog : IDialog<bool> // the type of returend value.
    {
        public Task StartAsync(IDialogContext context)
        {
            context.Wait(MessageReceivedAsync);
            return Task.CompletedTask;
        }

        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
        {
            var message = await result as Activity;

            using (var scope = WebApiApplication.Container.BeginLifetimeScope())
            {
                // Resolve IEventService by passing IDialog context for constructor.
                IEventService service = scope.Resolve<IEventService>(new TypedParameter(typeof(IDialogContext), context));
                var events = await service.GetEvents();
                foreach (var @event in events)
                {
                    await context.PostAsync($"{@event.Start.DateTime}-{@event.End.DateTime}: {@event.Subject}");
                }
            }

            // Complete the child dialog
            context.Done(true);
        }
    }
}

2. Replace RootDialog.cs code with following.

  • Call GetEventsDialog for getting events.
  • Add callback method when the child dialog completed.
  • Remember the original message when redirected to authentication.
    *I utilizes State Service to remember/restore message, which I will explain in later article.
using AuthBot;
using AuthBot.Dialogs;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Configuration;
using System.Threading;
using System.Threading.Tasks;

namespace O365Bot.Dialogs
{
    [Serializable]
    public class RootDialog : IDialog<object>
    {
        public Task StartAsync(IDialogContext context)
        {
            context.Wait(MessageReceivedAsync);
            return Task.CompletedTask;
        }

        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
        {
            var message = await result as Activity;

            // Check authentication
            if (string.IsNullOrEmpty(await context.GetAccessToken(ConfigurationManager.AppSettings["ActiveDirectory.ResourceId"])))
            {
                // Store the original message.
                context.PrivateConversationData.SetValue<Activity>("OriginalMessage", message as Activity);
                // Run authentication dialog.
                await context.Forward(new AzureAuthDialog(ConfigurationManager.AppSettings["ActiveDirectory.ResourceId"]), this.ResumeAfterAuth, message, CancellationToken.None);
            }
            else
            {
                await DoWork(context, message);
            }
        }

        private async Task DoWork(IDialogContext context, IMessageActivity message)
        {
            // Call child dialog
            await context.Forward(new GetEventsDialog(), ResumeAfterGetEventsDialog, message, CancellationToken.None);
        }

        private async Task ResumeAfterGetEventsDialog(IDialogContext context, IAwaitable<bool> result)
        {
            // Get the dialog result
            var dialogResult = await result;
            context.Wait(MessageReceivedAsync);
        }

        private async Task ResumeAfterAuth(IDialogContext context, IAwaitable<string> result)
        {
            // Restore the original message.
            var message = context.PrivateConversationData.GetValue<Activity>("OriginalMessage");
            await DoWork(context, message);
        }
    }
}

Dialog 101

I am sharing what I feel important to utilize Dialog.

Serializable attribute

BotBuilder serializes entire Dialog to manage its state, thus you have to mark the class as Serializable. Same rule applies to its memebers, too. I often forget this rule and BotBuilder yells at me at runtime.

IDialog<T> inheritance

Dialog inherits IDialog interface directly or indirectly. T is a type of returned object, so if you know the type, you shall specify the type rather than leave it as ‘object’.

StartAsync method

This method is called at the beginning of the dialog. It is like a constructor of a class, but you can also use constructor. So use StartAsync method to prepare the things for conversation.

Context.Done method

Whenever the child dialog completes it job, you need to call Context.Done method which handles the operation to it’s parent.

Summery

Next time I will explain advanced topic of Dialog system. Don’t forget to check-in the code. All the tests should pass as no logic has been changed.

GitHub: https://github.com/kenakamu/BotWithDevOps-Blog-sample/tree/master/article7

Ken