A bot can use the Bot Builder for .NET SDK to define global message handlers that execute code whenever the user replies to the conversation with a specific word or phrase from anywhere in the bot. These global message handlers allow you to create bot features that are accessible from anywhere in your bot.
This sample shows how to create global message handlers and illustrates two common scenarios:
- Global Dialogs - A global message handler that responds to a global command to add a dialog to the conversation from anywhere in the bot. In this sample, the settings dialog can be accessed anywhere in the bot by replying with 'settings' anywhere in the bot. When the settings dialog is complete, the conversation returns to the prior dialog.
- Stack Manipulation - A global message handler that responds to a global command to manipulate the dialog stack. In this sample, the user is returned to the root dialog whenever the user responds with the word 'cancel' anywhere in the bot.
To run this sample, install the prerequisites by following the steps in the Create a bot with the Bot Builder SDK for .NET section of the documentation.
This sample is based on the Basic Multi-Dialog Sample, so be sure to review that sample before getting started with this one.
The Bot Builder for .NET SDK uses AutoFac for inversion of control and dependency injection. If you're not famililar with AutoFac, you can learn more in this Quick Start Guide.
One of the ways the Bot Builder for .NET SDK uses AutoFac is Scorables. Scorables intercept every message sent to a Conversation and apply a score to the message based on logic you define. The Scorable with the highest score 'wins' the opportunity to process the message, rather the message being sent to the Conversation. You can implement global message handlers by createing a Scorable for each global command you want to implement in your bot.
To create a Scorable you create a class that implements the IScorable
interface by inheriting from the ScorableBase
abstract class. To have that Scorable applied to every message in the conversation, the bot registers that IScorable
interface as a Service
with the Conversation
's Container
. When a new message arrives to the Conversation
, the Conversation
passes that message to each implementation of IScorable
in the Container
to get a score. The Container
then passes that message to the IScorable
with the highest score for processing.
Let's look at how this is done in the sample.
The SettingsDialog
is the dialog we'll add to the dialog stack whenever the user responds with 'settings' in the conversation.
public class SettingsDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("This is the Settings Dialog. Reply with anything to return to prior dialog.");
context.Wait(this.MessageReceived);
}
private async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
if ((message.Text != null) && (message.Text.Trim().Length > 0))
{
context.Done<object>(null);
}
else
{
context.Fail(new Exception("Message was not a string or was an empty string."));
}
}
}
The SettingsScorable
class provides an implementation of the ScorableBase
abstract class in order to implement the IScorable
interface.
In the PrepareAsync()
method, we inspect the incoming message to see if it matches the text we are looking for ('settings'). If there's a match, we return the message to be used as state for scoring, otherwise we return null (no match).
protected override async Task<string> PrepareAsync(IActivity activity, CancellationToken token)
{
var message = activity as IMessageActivity;
if (message != null && !string.IsNullOrWhiteSpace(message.Text))
{
if (message.Text.Equals("settings", StringComparison.InvariantCultureIgnoreCase))
{
return message.Text;
}
}
return null;
}
The HasScore()
method is called by the calling component to determine if the Scorable has a score (we have a match).
protected override bool HasScore(IActivity item, string state)
{
return state != null;
}
The GetScore()
method is called by the calling component to get the score for this Scorable. This score is compared to all other Scorables that have a score.
protected override double GetScore(IActivity item, string state)
{
return 1.0;
}
The Scorable with the highest score will process the message when the PostAsync()
method is called by the calling component. In the PostAsync()
method, we add the SettingsDialog
to the stack so it will become the active dialog.
protected override async Task PostAsync(IActivity item, string state, CancellationToken token)
{
var message = item as IMessageActivity;
if (message != null)
{
var settingsDialog = new SettingsDialog();
var interruption = settingsDialog.Void<object, IMessageActivity>();
this.task.Call(interruption, null);
await this.task.PollAsync(token);
}
}
When the scoring process is complete, the calling component calls the DoneAsync()
method where any resources used in scoring are freed.
protected override Task DoneAsync(IActivity item, string state, CancellationToken token)
{
return Task.CompletedTask;
}
In GlobalMessageHandlersBotModule
, we define a Module
that registers the SettingsScorable
as a Component that provides the IScorable
service.
public class GlobalMessageHandlersBotModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder
.Register(c => new SettingsScorable(c.Resolve<IDialogTask>()))
.As<IScorable<IActivity, double>>()
.InstancePerLifetimeScope();
}
}
In Global.asax.cs
, the SettingsScorable
can be applied to the Conversation
's Container
by registering the GlobalMessageHandlersBotModule
with the Container
.
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
this.RegisterBotModules();
GlobalConfiguration.Configure(WebApiConfig.Register);
}
private void RegisterBotModules()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new ReflectionSurrogateModule());
builder.RegisterModule<GlobalMessageHandlersBotModule>();
builder.Update(Conversation.Container);
}
}
The CancelScorable
is implemented the same way, but resets the dialog stack when the Scorable is called.
protected override async Task PostAsync(IActivity item, string state, CancellationToken token)
{
this.task.Reset();
}
Here's what the conversation looks like in the Bot Framework Emulator when replying to the to any dialog with 'settings'.
Note: When the SettingsDialog
completes, the NameDialog
is returned to the top of the dialog stack, so the next reply will be applied to the 'What is your name?' prompt.
Here's what the conversation looks like when replying to a the AgeDialog
with 'cancel'.
Note: When CancelScorable
is complete, the dialog returns to the RootDialog
, which waits for a message from the user ('Hi' below) before showing it's greeting and the first prompt.
For more information on creating global message handlers using Scorables, check out the following resources: