I'm designing a C# bot using the Microsoft Bot Framework. I'd like the bot to be easily extensible, allowing developers to add dialogs/responses while making code changes in as few places as possible. From my own experiments and looking at the examples in Microsoft's BotBuilder repo, it seems like adding a new dialog/response requires adding code in two places - a dispatcher in either MessagesController or RootDialog, plus the actual code implementing the dialog. Is there any way to structure the code so that extensions only require adding code in one place?
Example code:
From MessagesController.cs:
[...]
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
[...]
This method is the main entrypoint to the bot; any messages it receives trigger a POST call to this endpoint. In this code, that just triggers RootDialog creation and execution, but it could be written to trigger a different dialog depending on the details of the message.
From RootDialog.cs:
[...]
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if (activity.Text == "command-one")
{
await context.PostAsync("Received command one.");
}
else if (activity.Text == "command-two")
{
someOtherObject.doSomething();
await context.PostAsync("Received command two.");
}
else
{
// calculate something for us to return
int length = (activity.Text ?? string.Empty).Length;
// return our reply to the user
await context.PostAsync($"You sent {activity.Text} which was {length} characters");
}
context.Wait(MessageReceivedAsync);
}
[...]
This method does different things depending on the message text. In the command-one case, it just displays a simple response. Extensions along these lines just require adding code to this method, which is fine. In the command-two case, it calls the method of some other class, which may perform some arbitrary other activity. Adding an extension like this requires another class as well as adding a dispatcher to RootDialog. To restate my question, is there a way to avoid having to add the dispatcher in RootDialog, perhaps by adding some sort of annotation to methods in other classes?
Map<string, Command>
linking a command name to a function / delegate / object to call on that command would solve the problem. InMessageReceivedAsync
, get the command by name, execute it if it's present, report an unknown command if it's absent.