Bot Framework internally uses Autofac for dependency injection. If you take a close look at SDK, the top level composition root class
Conversation provides a static instance of Autofac’s
IContainer. I have highlighted the code snipped from Microsoft’s BotBuilder repository for curious mind in you. Head over to Conversation.cs. This makes it possible for a Bot Builder to register and resolve components.
We can seamlessly integrate our own registration module for Autofac on top of BotBuilder’s implementation. If you are really interested, you can have a look what how BotBuilder internally handles scope creation and resolution. BotBuilder’s DialogModule has it’s own static method called BeginLifetimeScope which returns a new tagged sub-scope
typeof(Dialog) with a resolved
IMessageActivity. (refer highlighted part from DialogModule.cs).
Let’s see how can we seamlessly implement Autofac dependency injection in Bot Application.
It is always better to create a bot specific separate registration module for Autofac. We can do it by extending Autofac.Module class and overriding the Load method.
In the code snippet above we can see the registrations for RootDialog (as IDialog<object>), DialogFactory as IDialogfactory (more on it later). You need to register Dialogs and ask a new instance per dependency. All non serializable classes and services should be keyed as FiberModule.Key_DoNotSerialize. Otherwise, you may get an exception “‘Service’ in Assembly ‘Bot Application1, Version=18.104.22.168, Culture=neutral, PublicKeyToken=null’ is not marked as serializable.”
With Dependency Injection in place, you can now inject an external service if it is required in any Dialog implementation.
Without DI in place, you would start the conversation with new instance of RootDialog. Like shown below –
But as you have services being injected in RootDialog now. You need to resolve all the services before creating a new instance of RootDialog. This approach doesn’t look ideal. Now we can make use of our good old friend Conversation.Container to get the activity scope and resolve root dialog from that scope.
Resolving Dialog from activity scope will resolve it’s dependent services too. In our case ProfileService is injected in RootDialog and HttpClient is injected in ProfileService.
Calling child dialogs
Beginning conversation can be handled with tagged sub-scope. But as soon as you are within a conversation and bot has a logic to call a child dialog or forward the conversation to another dialog, we need to resolve the dialog. We can use IDialogFactory implementation for that. DialogFactory injects IComponentContext. Autofac automatically provides IComponentContext instance which is a scope within application. We can use this scope to resolve Dialog.
We need to inject IDialogFactory in required Dialog class. It will be used to create a child dialog later on.
If we have a dialog with custom parameters like string. For example, if newly created identifier is needed in a dialog. We have to pass it as a parameter. We have a provision in our DialogFactory for such incidences. This is how we can do it –
Your bot application should make full use of Dependency Injection goodness now. With this one time effort you can make your bot application testable, maintainable and most importantly scalable.
Good-bye, until then.
Also published on Medium.