Tallan's Technology Blog

Tallan's Top Technologists Share Their Thoughts on Today's Technology Challenges

Global Message Handling and Catching the Bot Framework Way

Matthew Gajdosik

So you’ve set up your new chat bot using the Microsoft Bot Framework, and you need to catch messages before they’re read by your dialogs or FormFlows to apply some global or preemptive logic. Luckily, the Bot Framework team thought ahead and built in a concept for handling message interception, called Scorables.

What Are Scorables?

A Scorable is a class that you can register with Autofac (the dependency injection solution used by Bot Framework), that will silently sit in between your externally-facing code accepting incoming user messages, and your internal dialog code. Each one will get the chance to handle the incoming message, and even compete for priority (which is where the “score” originates).

When To Use Scorables

Since they have full access to Autofac and the dialog stack, they can allow you to do anything from:

  • applying logic across your entire set of dialogs without duplicating code (and while still being able to detect where in the conversation you are!),
  • redirecting the user by replacing the entire dialog stack, or by placing a new situational dialog on top of the current stack,
  • to allowing the user to access global settings or actions (such as the Settings menu in the Contoso Flowers sample bot, or the controls for the Alarm Bot sample).

If you need to do any of the above, or to just in general provide a way to converse outside the current dialog, Scorables might be your answer.

How To Use Scorables

The set of all Scorables that have been registered to the Autofac container are run in a loop as part of the initial, internal processing of messages, and prior to the receipt of the message by any active dialogs.

The code snippets below were all written in C#, and validated against Bot Framework 3.8.0.

First, you will want to make sure that your user messages are being delivered to your bot in a way that allows the built-in Bot Framework response logic to be applied, which will recruit your Scorables automatically.

If you don’t have an existing solution working with Bot Framework, the following steps will assume you’ve set up the .NET Quickstart Bot.

1. Create a root/default dialog for demonstration.
[Serializable]
public class SimpleDialog : IDialog<object>
{
    public async Task StartAsync(IDialogContext context)
    {
        context.Wait(MessageReceivedAsync);
    }

    public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> argument)
    {
        await context.PostAsync("Hey, I got your message!");
        context.Wait(MessageReceivedAsync);
    }
}

We’ll call this dialog as our root in order to make sure that we have a stable reference. Once it’s been created, it should persist for the lifetime of the conversation.

2. Locate your default or root dialog, and add the Conversation.SendAsync call.

Alter the controller you’re using for bot messages to use the following pattern:

    
if (activity.Type == ActivityTypes.Message)
{
    using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
    {
        var dialog = scope.Resolve<SimpleDialog>();
        await Conversation.SendAsync(activity, () => dialog);
    }
}

This should replace the body of the MessagesController created as part of the Quickstart bot / Bot Application template, depending on how you’ve set up your project. This resolves a new copy of our SimpleDialog, and adds the incoming activity to the existing dialog stack, or to the new copy of our SimpleDialog if this is a new conversation.

3. Create a Scorable to intercept a “Hello” message.
[Serializable]
public class HelloScorable : ScorableBase<IActivity, bool, double>
{
    private readonly IBotToUser _botToUser;

    public HelloScorable(IBotToUser botToUser) // Make sure the IBotToUser object has been added so we can respond to the user asynchronously!
    {
        SetField.NotNull(out _botToUser, nameof(_botToUser), botToUser);
    }

    protected override async Task<bool> PrepareAsync(IActivity item, CancellationToken token)
    {
        var message = item as IMessageActivity;

        if (message != null && !string.IsNullOrWhiteSpace(message.Text))
        {
            return message.Text == "Hello";
        }

        return false;
    }

    protected override bool HasScore(IActivity item, bool state)
    {
        return state;
    }

    protected override double GetScore(IActivity item, bool state)
    {
        return state ? 1.0 : 0;
    }

    protected override async Task PostAsync(IActivity item, bool state, CancellationToken token)
    {
        await _botToUser.PostAsync("Hello to you too!");
    }

    protected override Task DoneAsync(IActivity item, bool state, CancellationToken token)
    {
        return Task.CompletedTask;
    }
}

This will scan the incoming messages for “Hello”, and provide a TRUE state if found. This will indicate that the Scorable has a score (“HasScore”) of 1.0 (“GetScore”), and if it’s the highest value Scorable out of the set of Scorables with scores, it will receive the PostAsync action instead of the active dialog.

Note that the second type parameter is set to a bool, and all of the “state” items are of type bool. This is because this sample uses the ScorableBase abstract class, rather than the IScorable interface.

Why bother? The ScorableBase helps by defining exactly what you use to define your state:

public abstract class ScorableBase<Item, State, Score> : IScorable<Item, Score>

Since the State is now a typed generic parameter, we can ensure that we’re passing around a consistent item without worrying about typecasts like in the base IScorable interface, which treats all of the comparable fields as a vague object-type.

4. Autofac Configuration

Run the following commands in the Package Manager Console to make sure that all of the features you might need are available.

Update-Package Microsoft.Bot.Builder
Install-Package Autofac.Mvc5

Then add the following code to Global.asax.cs in order to register your custom objects in Autofac. If you’ve got Modules set up, or an existing AutofacConfig.cs, you can convert the code to fit those as well.

 Conversation.UpdateContainer(
     builder =>
     {
         builder.RegisterType<SimpleDialog>()
             .AsSelf()
             .As<IDialog<object>>();
         builder.RegisterType<HelloScorable>()
             .AsSelf()
             .As<IScorable<IActivity, double>>();
         builder.RegisterControllers(typeof(WebApiApplication).Assembly);
    }
);

DependencyResolver.SetResolver(new AutofacDependencyResolver(Conversation.Container));
5. Try It Out

Now, assuming everything is configured, you should be able to run your bot. Try talking to it with the Bot Framework Emulator and see how it responds to “Hello” and other messages to test your message interception!

 

bot_framework_hello_howdy

As you can see, the Scorables provide a powerful global intermediate layer between incoming messages and your dialogs and forms, and really shows how powerful some of the Bot Framework features are.

For more information, please feel free to comment below, or to visit the ever-expanding Bot Framework documentation on this and other topics!

_________________________________________________________________________________________

To learn more about Chatbots and how Tallan can help you automate business processes while maximizing customer engagement with Chatbot Technology, CLICK HERE.

No comments

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

\\\