Change the “Sorry, my bot code is having an issue” in Microsoft Bot Framework

23/02/2017

The Microsoft Bot framework makes it easy to build multi platform chat bots. The framework is extensible and offers developer a quick way to get started building bots. Unfortunately, the framework is also under rapid development, so the documentation is not complete and it can be hard to find up-to-date blog posts.

Hopefully this blog post will not turn obsolete too fast, but just in case this write up is based on the Microsoft.Bot.Builder 3.5.2 version.

So you dislike the standard error message that the user gets when your code fails? Or maybe you just want it translated? If so you have come to the right place.
The Microsoft Bot Framework uses dependency injection and uses the framework Autofac for wiring it all up. That is good news for us, because that enables us to replace components of the framework with our own custom ones in our application. If they haven’t used a dependency framework then we would have been forced to rebuild our own version of the framework, which would have been more complicated.

The component we should replace, to change the error message, is the PostUnhandledExceptionToUser class which implements the logic to get the error message from the default resource file of the bot framework.

What I did was to lookup the class from the github repository of the bot framework and copy its content into my own application.
[csharp]
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Builder.Internals.Fibers;
using Microsoft.Bot.Connector;
using System;
using System.Diagnostics;
using System.Net.Mime;
using System.Resources;
using System.Threading;
using System.Threading.Tasks;

namespace Tiimo.Bot.BotFramework
{
public class PostUnhandledExceptionToUser : IPostToBot
{
private readonly ResourceManager resources;
private readonly IPostToBot inner;
private readonly IBotToUser botToUser;
private readonly TraceListener trace;

public PostUnhandledExceptionToUser(IPostToBot inner, IBotToUser botToUser, ResourceManager resources, TraceListener trace)
{
SetField.NotNull(out this.inner, nameof(inner), inner);
SetField.NotNull(out this.botToUser, nameof(botToUser), botToUser);
SetField.NotNull(out this.resources, nameof(resources), resources);
SetField.NotNull(out this.trace, nameof(trace), trace);
}

async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
{
try
{
await this.inner.PostAsync(activity, token);
}
catch (Exception error)
{
try
{
if (Debugger.IsAttached)
{
var message = this.botToUser.MakeMessage();
message.Text = $"Exception: { error.Message}";
message.Attachments = new[]
{
new Attachment(contentType: MediaTypeNames.Text.Plain, content: error.StackTrace)
};

await this.botToUser.PostAsync(message);
}
else
{
await this.botToUser.PostAsync("My Personal Error Message");
}
}
catch (Exception inner)
{
this.trace.WriteLine(inner);
}

throw;
}
}
}
}
[/csharp]
I only changed the line that post the error message to the user, everything else is the same as in the bot framework. If you don’t like that errorhandling is different once the debugger is attached, you can of course change that too.

So now we have built a replacement for the standard PostUnhandledExceptionToUser now we need to tell Autofac to use our version instead of the standard one.

We do so by creating a Autofac module in which we register our type PostUnhandledExceptionToUser, and then we need to setup when in the decorator chain it should be called. This logic is specific for the bot framework, but as I understand it they use the decorator pattern to ensure a number of functions are called on messages leaving the bot framework.

They use an extension method called RegisterAdapterChain to do the setup which I borrowed and placed in the module, obviously, it better suited to be placed somewhere else.

My module class looks like this
[csharp]
using Autofac;
using Autofac.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Builder.History;
using Microsoft.Bot.Builder.Internals.Fibers;
using Microsoft.Bot.Builder.Scorables.Internals;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Resources;
using System.Web;

namespace Tiimo.Bot.BotFramework
{
public class DefaultExceptionMessageOverrideModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<PostUnhandledExceptionToUser>().Keyed<IPostToBot>(typeof(PostUnhandledExceptionToUser)).InstancePerLifetimeScope();

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

public static IRegistrationBuilder<TLimit, SimpleActivatorData, SingleRegistrationStyle> RegisterAdapterChain<TLimit>(ContainerBuilder builder, params Type[] types)
{
return
builder
.Register(c =>
{
// http://stackoverflow.com/questions/23406641/how-to-mix-decorators-in-autofac
TLimit service = default(TLimit);
for (int index = 0; index < types.Length; ++index)
{
// resolve the keyed adapter, passing the previous service as the inner parameter
service = c.ResolveKeyed<TLimit>(types[index], TypedParameter.From(service));
}

return service;
})
.As<TLimit>();
}
}

}
[/csharp]

That is all the plumbing needed to replace the default error message. As you might be able to guess this approach can also be used to replace other aspects of the default bot framework, but that might be the topic of another blog post, once I find a need for it.

One final thing is left, we have to call our module to tell Autofac to use our configuration. We can do that from the global.asax or some other place where you have the global configuration.

That code looks like this
[csharp]
var builder = new ContainerBuilder();
builder.RegisterModule(new DefaultExceptionMessageOverrideModule());
builder.Update(Conversation.Container);
[/csharp]

That is, it, now we have replaced the default error message. It might seem overly complex (and one can argue that it is), but the concept can be used to other things, so it is a good to know about.