BotFramework Exception Wrapping and Custom Error Message

During your time building and debugging your botframework chatbot, you’ve probably already come across the annoyance of seeing a huge error message with a ridiculous stack trace that doesn’t seem to relate to your error, or even just a plain old HTTP 500 and big red exclamation mark..

emulator red exclamation mark

Perhaps you’ve deployed to a production environment and are stuck with the stock error message:

Sorry, my bot code is having an issue

In this article I’ll show you how to 1) display useful exception information, and 2) override the default error message sent to the user

In order to capture the short, top level, exception only, we need to wrap the root Dialog with an exception handling one; let’s start with that.

Exception Handler Dialog

By default, when you start a conversation your MessagesController will look a bit like this:

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
    if (activity.Type == ActivityTypes.Message)
    {
        await Conversation.SendAsync(activity, () => new MyRootDialog());
    }
    else
    {
        HandleSystemMessage(activity);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK);
    return response;
}

When an exception occurs within your root dialog you’ll either see nothing (i.e. an HTTP 500 error) or a huge stack trace. To try to handle both of these scenarios, we need to wrap the root dialog with an exception catching one.

This dialog wrapping code isn’t mine – it came from a GitHub issue, but I use it a lot so am sharing the love:

This dialog will enable us to wrap our root dialog’s StartAsync and ResumeAsync methods in try catch blocks:

[Serializable]
public class ExceptionHandlerDialog<T> : IDialog<object>
{
    private readonly IDialog<T> _dialog;
    private readonly bool _displayException;
    private readonly int _stackTraceLength;

    public ExceptionHandlerDialog(IDialog<T> dialog, bool displayException, int stackTraceLength = 500)
    {
        _dialog = dialog;
        _displayException = displayException;
        _stackTraceLength = stackTraceLength;
    }

    public async Task StartAsync(IDialogContext context)
    {
        try
        {
            context.Call(_dialog, ResumeAsync);
        }
        catch (Exception e)
        {
            if (_displayException)
                await DisplayException(context, e).ConfigureAwait(false);
        }
    }

    private async Task ResumeAsync(IDialogContext context, IAwaitable<T> result)
    {
        try
        {
            context.Done(await result);
        }
        catch (Exception e)
        {
            if (_displayException)
                await DisplayException(context, e).ConfigureAwait(false);
        }
    }

    private async Task DisplayException(IDialogContext context, Exception e)
    {

        var stackTrace = e.StackTrace;
        if (stackTrace.Length > _stackTraceLength)
            stackTrace = stackTrace.Substring(0, _stackTraceLength) + "…";
        stackTrace = stackTrace.Replace(Environment.NewLine, "  \n");

        var message = e.Message.Replace(Environment.NewLine, "  \n");

        var exceptionStr = $"**{message}**  \n\n{stackTrace}";

        await context.PostAsync(exceptionStr).ConfigureAwait(false);
    }
}   

Now we use this dialog in our MessagesController instead of our previous “root” dialog. Change the conversation initiation from (assuming your main dialog is called MyRootDialog and implements IDialog<object>):

await Conversation.SendAsync(activity, () => new MyRootDialog());

to

await Conversation.SendAsync(activity, () =>
   new ExceptionHandlerDialog<object>(
      new MyRootDialog(),
      displayException: true));

This will actually capture the top level exception, and optionally return it as a message; just configure displayException to be true when debugging and false when deployed.

You can also add in logging in the catch section of the ExceptionHandlerDialog.

Now your real exception should be both exposed and summarised.

shortened botframework error message

Sorry, my bot code is having an issue

Next let’s override the default error message. This requires a little detective work, digging into the code in the GitHub repo. Let’s get our Sherlock hats on!

The exception message is sent by the PostUnhandledExceptionToUserTask class, which has this method:

async Task IPostToBot.PostAsync<T>(T item, CancellationToken token)
{
    try
    {
        await this.inner.PostAsync<T>(item, token);
    }
    catch (Exception error)
    {
        try
        {
            if (Debugger.IsAttached)
            {
                await this.botToUser.PostAsync($"Exception: {error}");
            }
            else
            {
                await this.botToUser.PostAsync(this.resources.GetString("UnhandledExceptionToUser"));
            }
        }
        catch (Exception inner)
        {
            this.trace.WriteLine(inner);
        }

        throw;
    }
}

This class can be found in the DialogTask file in the BotBuilder github repository

Notice the reference to this. resources. GetString("UnhandledExceptionToUser"), which gets the message from the resource file for the current context’s language; for English, this is set to:

<data name="UnhandledExceptionToUser" xml:space="preserve">
   <value>Sorry, my bot code is having an issue.</value>
</data>

As can be found in the Resources.resx file

The PostUnhandledExceptionToUserTask class is wired up using Autofac for IoC (Inversion of Control, out of scope for this article, but essentially allows resolution of an interface to an implementation via configuration in the “hosting” code) – notice the last few lines of the following code from the DialogModule.cs Autofac module:

builder
.Register(c =>
{
    var cc = c.Resolve<IComponentContext>();

    Func<IPostToBot> makeInner = () =>
    {
        var task = cc.Resolve<DialogTask>();
        IDialogStack stack = task;
        IPostToBot post = task;
        post = new ReactiveDialogTask(post, stack, cc.Resolve<IStore<IFiberLoop<DialogTask>>>(), cc.Resolve<Func<IDialog<object>>>());
        post = new ExceptionTranslationDialogTask(post);
        post = new LocalizedDialogTask(post);
        post = new ScoringDialogTask<double>(post, stack, cc.Resolve<TraitsScorable<IActivity, double>>());
        return post;
    };

    IPostToBot outer = new PersistentDialogTask(makeInner, cc.Resolve<IBotData>());
    outer = new SerializingDialogTask(outer, cc.Resolve<IAddress>(), c.Resolve<IScope<IAddress>>());

    // -- we want to override the next line:
    outer = new PostUnhandledExceptionToUserTask(outer, cc.Resolve<IBotToUser>(), cc.Resolve<ResourceManager>(), cc.Resolve<TraceListener>());

    outer = new LogPostToBot(outer, cc.Resolve<IActivityLogger>());
    return outer;
})
.As<IPostToBot>()
.InstancePerLifetimeScope();

We need to override this and have our own class resolved for the IPostToBot interface instead. However, we still need all of the other “wrapper” dialog tasks, so it’s actually easier to copy and paste this into our own AutoFac module, just changing that one line.

Default Exception Message Override Module

Let’s create a DefaultExceptionMessageOverrideModule AutoFac module that looks like this:

public class DefaultExceptionMessageOverrideModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder
            .Register(c =>
            {
                var cc = c.Resolve<IComponentContext>();

                Func<IPostToBot> makeInner = () =>
                {
                    var task = cc.Resolve<DialogTask>();
                    IDialogStack stack = task;
                    IPostToBot post = task;
                    post = new ReactiveDialogTask(post, stack, cc.Resolve<IStore<IFiberLoop<DialogTask>>>(),
                        cc.Resolve<Func<IDialog<object>>>());
                    post = new ExceptionTranslationDialogTask(post);
                    post = new LocalizedDialogTask(post);
                    post = new ScoringDialogTask<double>(post, stack,
                        cc.Resolve<TraitsScorable<IActivity, double>>());
                    return post;
                };

                IPostToBot outer = new PersistentDialogTask(makeInner, cc.Resolve<IBotData>());
                outer = new SerializingDialogTask(outer, cc.Resolve<IAddress>(), c.Resolve<IScope<IAddress>>());

                // --- our new class here
                outer = new PostUnhandledExceptionToUserOverrideTask(outer, cc.Resolve<IBotToUser>(),

                    cc.Resolve<ResourceManager>(), cc.Resolve<TraceListener>());
                outer = new LogPostToBot(outer, cc.Resolve<IActivityLogger>());
                return outer;
            })
            .As<IPostToBot>()
            .InstancePerLifetimeScope();
    }
}

Notice the line which previously registered PostUnhandledExceptionToUserTask now uses our own PostUnhandledExceptionToUserOverrideTask.

Post Unhandled Exception To User Override Task

That new file looks like this – should be very familiar!

public class PostUnhandledExceptionToUserOverrideTask : IPostToBot
{
    private readonly ResourceManager resources;
    private readonly IPostToBot inner;
    private readonly IBotToUser botToUser;
    private readonly TraceListener trace;

    public PostUnhandledExceptionToUserOverrideTask(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<T>(T item, CancellationToken token)
    {
        try
        {
            await inner.PostAsync(item, token);
        }
        catch (Exception)
        {
            try
            {
                await botToUser.PostAsync("OH NOES! Put your own message here, or logic to decide which message to show.", cancellationToken: token);
            }
            catch (Exception inner)
            {
                trace.WriteLine(inner);
            }

            throw;
        }
    }
}

Wiring it all up

Now that we’ve got a new implementation for IPostToBot it has to be wired up in order to override the default one from DialogModule.cs. To do this, put the following code wherever you already do your IoC, or in the Global.asax.cs if you’re not doing it anywhere else yet.

var builder = new ContainerBuilder();
builder.RegisterModule(new DefaultExceptionMessageOverrideModule());
builder.Update(Conversation.Container);

botframework custom exception message

Summary

With these changes in place you can finally avoid scrolling through many screens of stack trace that doesn’t give you any useful information, and customise the friendly error message sent to your user.

4 thoughts on “BotFramework Exception Wrapping and Custom Error Message

  1. Hey Robin, i was trying to implement this feature. But am getting below error message. Can you please help?

    Exception: Cannot create an instance of ExceptionHandlerDialog`1[T] because Type.ContainsGenericParameters is true.
    [File of type ‘text/plain’]

    • Not completely, no; the IPostToUser has changed, and the ability to override IoC registrations more easily has been added too.

Leave a Reply to Chad Cancel reply

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