Внедрение зависимостей для промежуточного программного обеспечения чат-бота Azure?

Я работаю над новым чат-ботом, используя службу Azure Bot и QnAMaker. Мы используем промежуточное ПО BotBuilder, в том числе настраиваемое ПО промежуточного слоя, чтобы настроить поведение бота.

Одно из промежуточных программ будет вызывать функцию Azure, и я хотел бы использовать новую функцию HttpClientFactory с настраиваемым промежуточным программным обеспечением, но для этого требуется внедрение зависимостей.

Как я могу использовать внедрение зависимостей в промежуточном программном обеспечении BotBuilder, как вы это делаете с обычным промежуточным программным обеспечением .NET Core?

Когда вы посмотрите на конфигурацию бота в Startup.cs, вы увидите, что вам нужно создать все зависимости бота:

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. ....

Есть ли другой способ настройки бота, позволяющий внедрять зависимости?

Если MyCustomMiddleware требует типизированного HttpClient в своем конструкторе, я должен создать новый экземпляр прямо здесь, поэтому я не получаю преимуществ от DI и конфигурации, которую я только что настроил.


person emaia    schedule 21.05.2018    source источник


Ответы (1)


Хотя я не являюсь поклонником шаблона локатора сервисов, текущий дизайн конфигурации бота не очень удобен для внедрения зависимостей.

Используя характер установки промежуточного программного обеспечения бота, но при необходимости предоставить новый экземпляр во время запуска, я придумал следующий обходной путь.

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);
    }
}

Он принимает IServiceCollection как явную зависимость и откладывает создание поставщика услуг и возможное разрешение фактического промежуточного программного обеспечения в делегате фабрики.

Затем его можно реализовать с помощью

middleware.Add(new BotMiddlewareAdapter<MyCustomMiddleware>(services));

Когда адаптер вызывается, он лениво разрешает предполагаемое промежуточное ПО при первоначальном вызове, а затем вызывает его.

Фактически, вы можете сделать еще один шаг вперед и преобразовать его в метод расширения.

public static class BotBuilderMiddlewareExtension {
    public static void Add<TMiddleware>(this IList<IMiddleware> middleware, IServiceCollection services) 
        where TMiddleware : IMiddleware {
        middleware.Add(new BotMiddlewareAdapter<TMiddleware>(services));
    }
}

Что упрощает настройку до

middleware.Add<MyCustomMiddleware>(services);
person Nkosi    schedule 21.05.2018
comment
Это не выглядит правильным. Любое решение, требующее вызова BuildServiceProvider, всегда подозрительно. Это создаст новую вселенную, и вы получите несколько экземпляров синглтонов, ничего не будет передано. - person davidfowl; 08.09.2018