3

I would like to integrate Bot composer with Custom actions. The custom actions in turn calls different API to perform some business logic. I would like to inject interfaces and service provider to custom action. I am having trouble in doing this as it is failing and getting in to null pointer exceptions, eventhough I have added everything properly in the startup.cs. Could you please explain how can i achieve this?.

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

2 Answers2

1

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.

Miguel Veloso
  • 1,055
  • 10
  • 16
  • I tried this approach but didn't worked... The injected service is always null. Any other tip please? – solrac Jun 22 '21 at 07:44
  • I'd have to see some of your code, because this works. However, you can see the same concept, with a little different implementation, working in a sample project I'm working with, only it's focused on Composer 2, so it's using the BotComponent approach. In this sample the injected service is a logger, but it should work with any singleton service: https://github.com/mvelosop/explore-bot-composer-2/blob/main/ExplorationBot/components/CustomBotComponents/Recognizers/CustomLuisRecognizer.cs – Miguel Veloso Jun 22 '21 at 09:16
0

The other answer isn't super clear - so I will add some clear snippets. Say you want to inject your service MyService

First, some extra configuration:

services.AddSingleton<IMiddleware, RegisterClassMiddleware<MyService>>(sp => new RegisterClassMiddleware<MyService>(sp.GetRequiredService<MyService>()));

Then in your Dialog:

public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
    var myService = dc.Services.Get<MyService>();
}

Done!

Mark Gibbons
  • 471
  • 3
  • 19