January 5, 2018 Greg

Bot Framework adding code before and after each messages

Messages between a user and the bot can originate from multiple locations in your code. Indeed, when the bot processes a message activity he may post multiple messages back to the user within the same originating web request on the controller. Since a Microsoft Bot Framework dialog between the user and bots will progress, wait and resume based on the state of the dialog, one cannot simply add code within the message controller to execute at the start and end of each request. Instead to execute custom code before and each message exchange you must extend the chain of execution for incoming and outgoing messages from the bot.

A common example of why you would want to add custom code could be for analytic purposes; tracking all exchanges between a user and the bot. In the example bellow we will register our custom cote to track user interactions using Dashbot which is a well-known bot analytics solution.

1- Create your components for pre and post request execution

Implement a class inheriting from the IBotToUser interface to execute code every time a message is sent from the bot to the user:

public class DashbotOutgoing : IBotToUser
{
    private readonly IBotToUser _inner;
    private IDashbotService _dashbotService;

    public DashbotOutgoing(IBotToUser inner, IDashbotService dashbotService)
    {
        SetField.NotNull(out this._inner, nameof(inner), inner);
        SetField.NotNull(out this._dashbotService, nameof(dashbotService), dashbotService);
    }

    IMessageActivity IBotToUser.MakeMessage()
    {
        return this._inner.MakeMessage();
    }

    async Task IBotToUser.PostAsync(IMessageActivity message, CancellationToken cancellationToken)
    {
        Task.Factory.StartNew(() => TrackOutgoing(message, cancellationToken)).ConfigureAwait(false);
        await this._inner.PostAsync(message, cancellationToken);
    }

    private async Task TrackOutgoing(IMessageActivity message, CancellationToken cancellationToken)
    {
        if (!string.IsNullOrWhiteSpace(message?.Text))
            await _dashbotService.TrackOutgoing(message.ChannelId, new DashbotPayload(message.Text, message.Recipient.Id));

    }
}

Implement a class inheriting from IPostToBot interface to execute code on ever message coming from a user to the bot:

public class DashbotIncoming : IPostToBot
{
    private readonly IPostToBot _inner;        
    private IDashbotService _dashbotService;

    public DashbotIncoming(IPostToBot inner, IDashbotService dashbotService)
    {
        SetField.NotNull(out this._inner, nameof(inner), inner);
        SetField.NotNull(out this._dashbotService, nameof(dashbotService), dashbotService);            
    }

    async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
    {
        if (activity.Type == ActivityTypes.Message)
            Task.Factory.StartNew(() => TrackIncoming(activity, token)).ConfigureAwait(false);
        await _inner.PostAsync(activity, token);
    }

    private void TrackIncoming(IActivity activity, CancellationToken cancellationToken)
    {
        IMessageActivity msg = activity?.AsMessageActivity();

        if(!string.IsNullOrWhiteSpace(msg?.Text))
            _dashbotService.TrackIncomming(activity.ChannelId, new DashbotPayload(msg.Text, activity.From.Id));
    }
}

2- Register your classes in the Autofac

Microsoft Bot Framework uses Autofac to allow developers to change and extend how the framework behaves. In our case, we are interested in changing the IBotToUser and IPostToBot chains to register our custom components to execute alongside the out-of-the-box behavior.

First let’s create a Autofac module to register our components:

public class AutofacPrePostExecutionModule : Module
{

    protected override void Load(ContainerBuilder builder)
    {
        base.Load(builder);            

        builder.RegisterKeyedType<DashbotOutgoing, IBotToUser>().InstancePerLifetimeScope();
        builder
            .RegisterAdapterChain<IBotToUser>
            (
                typeof(AlwaysSendDirect_BotToUser),
                typeof(MapToChannelData_BotToUser),
                typeof(LogBotToUser),
                typeof(DashbotOutgoing)
            )
            .InstancePerLifetimeScope();

        builder.RegisterKeyedType<DashbotIncoming, IPostToBot>().InstancePerLifetimeScope();
        builder
            .RegisterAdapterChain<IPostToBot>
            (
                typeof(EventLoopDialogTask),
                typeof(SetAmbientThreadCulture),
                typeof(PersistentDialogTask),
                typeof(ExceptionTranslationDialogTask),
                typeof(SerializeByConversation),
                typeof(PostUnhandledExceptionToUser),
                typeof(LogPostToBot),
                typeof(DashbotIncoming)
            )
            .InstancePerLifetimeScope();
    }
}

Then register our new module while keeping those required by Bot Framework in the Global.asax:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AutofacConfig.Register();
    }
}
public static class AutofacConfig
{ 
    public static void Register()
    {
        ContainerBuilder builder = new ContainerBuilder();

        // register the Bot Builder module
        builder.RegisterModule(new DialogModule());

        // register your custom modules
        builder.RegisterModule(new AutofacPrePostExecutionModule()); 

        // Register your Web API controllers.
        builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

        //Register the Autofac filter provider.
        builder.RegisterWebApiFilterProvider(GlobalConfiguration.Configuration);

        //Updates the out of the box container definition of Bot Builder
        builder.Update(Conversation.Container);

        GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(Conversation.Container);            

        // WebApiConfig stuff
        GlobalConfiguration.Configure(configWebApi =>
        {
            configWebApi.MapHttpAttributeRoutes();

            configWebApi.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional
        }
            );
        });            
    }
}

Voilà!  You now have your custom code running before and after each message going between the bot and a user.

Tagged: , ,

EXPERTISE IS ONLY ONE CLICK AWAY!

We invite you to contact us for more information
about our services.