I am working on a new chat bot using Azure Bot Service and QnAMaker. We are using BotBuilder middleware, including custom middleware, to tailor the bot behavior.
One of the middlewares will be calling an Azure function and I would like to use the new HttpClientFactory
feature with the custom middleware - but this requires dependency injection.
How can I use dependency injection in BotBuilder middleware like you do with regular .NET Core middleware?
When you look at the bot configuration in the Startup.cs, you can see how it requires you to new up all of the bot dependencies:
services.AddHttpClient<MyFunctionClient>(client =>
{
client.BaseAddress = new Uri(mySettings.GetValue<string>("myFunctionUrl"));
client.DefaultRequestHeaders.Add("x-functions-key", mySettings.GetValue<string>("myFunctionKey"));
});
services.AddBot<QnAMakerBot>(options =>
{
options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);
options.ConnectorClientRetryPolicy = new RetryPolicy(
new BotFrameworkHttpStatusCodeErrorDetectionStrategy(),
3,
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(20),
TimeSpan.FromSeconds(1));
var middleware = options.Middleware;
middleware.Add(new ConversationState<ChatLog>(new MemoryStorage()));
middleware.Add(new MyCustomMiddleware()); // <- I want to inject a typed HttpClient here
//... etc. ....
Is there a different way to configure the bot that allows for dependency injection?
If MyCustomMiddleware
requires a typed HttpClient
in its constructor, I have to create a new instance right here, so I don't get the benefit of the DI and the configuration I just set up.
While I am not a fan of service locator pattern, the current design of the bot configuration is not very dependency injection friendly.
Using the nature of how the bot middleware are setup but having to provide a new instance during startup, I came up with the following work around.
public class BotMiddlewareAdapter<TMiddleware> : IMiddleware
where TMiddleware : IMiddleware {
private readonly Lazy<TMiddleware> middleware;
public BotMiddlewareAdapter(IServiceCollection services) {
middleware = new Lazy<TMiddleware>(() =>
services.BuildServiceProvider().GetRequiredService<TMiddleware>());
}
public Task OnTurn(ITurnContext context, MiddlewareSet.NextDelegate next) {
return middleware.Value.OnTurn(context, next);
}
}
It takes the IServiceCollection
as an explicit dependency and defers the creation of the service provider and eventual resolution of the actual middleware in a factory delegate.
It can then be implemented using
middleware.Add(new BotMiddlewareAdapter<MyCustomMiddleware>(services));
When the adapter is invoked it will lazy resolve the intended middleware on initial call and then invoke it.
In fact you can take this another step further and convert it to an extension method
public static class BotBuilderMiddlewareExtension {
public static void Add<TMiddleware>(this IList<IMiddleware> middleware, IServiceCollection services)
where TMiddleware : IMiddleware {
middleware.Add(new BotMiddlewareAdapter<TMiddleware>(services));
}
}
Which simplifies the setup to
middleware.Add<MyCustomMiddleware>(services);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With