首页 > 解决方案 > Bot Composer 自定义操作中的依赖注入

问题描述

我想将 Bot composer 与自定义操作集成。自定义操作依次调用不同的 API 来执行一些业务逻辑。我想将接口和服务提供者注入自定义操作。我在执行此操作时遇到了麻烦,因为它失败并进入空指针异常,尽管我已在 startup.cs 中正确添加了所有内容。你能解释一下我怎么能做到这一点吗?

 [JsonConstructor]    
 public MultiplyDialog(IServiceProvider serviceProvider, [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
        : base()    
    {    
        serviceProvider.GetService<ApiService>() // serviceprovider always null    
        this.RegisterSourceLocation(sourceFilePath, sourceLineNumber);    
    }

标签: botframeworkbot-framework-composer

解决方案


You have to keep in mind that when using Adaptive Dialogs (that is, the core of Composer) Dialogs are singletons and, when using Composer, they're not instantiated from dependency injection (DI).

Also, since dialogs are singletons, you can't (well, you could but you shouldn't) use services like constructor injected DbContexts and similar (when working with the SDK, that is, coding).

The easiest way to solve this is by using HTTP requests using the HttpRequest action. This is the way that's built into the whole adaptive dialogs ecosystem to achieve this kind of functionality.

If you really insist on doing it with DI into the dialogs, you'd have to solve DI from the TurnContext and you'd have to set it up in the adapter. However, that's a bit convoluted an requires you to use a custom runtime.

UPDATE Added the way to implement DI with adaptive dialogs.

1 - Register the service class in the turn state in the adapter

public class AdapterWithErrorHandler : BotFrameworkHttpAdapter
{
    public AdapterWithErrorHandler(
        IConfiguration configuration,
        ILogger<BotFrameworkHttpAdapter> logger,
        //...
        QnAClient qnaClient)
        : base(configuration, logger)
    {
        // Add QnAClient to TurnState so we can use it while in the turn
        Use(new RegisterClassMiddleware<QnAClient>(qnaClient));

        //...
    }
}

In the code above QnAClient is an typed HttpClient created with IHttpClientFactory so it's a safe to use singleton.

2 - Get the service from the TurnState wherever you need it

public async Task SetPropertiesAsync(DialogContext context, ...)
{
    var qnaClient = context.Context.TurnState.Get<QnAClient>();

    //...
}

BTW, this is a nice way to get an HttpClient properly managed by IHttpClientFactory when you register it like this in ConfigureServices:

services.AddHttpClient<QnAClient>()
    .AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(new[] { 1, 2, 3, 5, 8, 13 }.Select(t => TimeSpan.FromSeconds(t))))
    .AddTransientHttpErrorPolicy(p => p.CircuitBreakerAsync(6, TimeSpan.FromSeconds(30)));

In this case with retry policies from Polly.


推荐阅读