10

I've been trying to pass a service to a LuisDialog from the MessagesController like so:

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
...
await Conversation.SendAsync(activity, () => new ActionDialog(botService));

The botService is injected into the MessageController using dependency injection.

When I start a bot conversation I get an error:

Type 'ThetaBot.Services.BotService' in Assembly 'ThetaBot.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

Looking around for a solution I can find: https://github.com/Microsoft/BotBuilder/issues/106

I understand your question better now. We have a similar issue with service objects that we want to instantiate from the container rather than from the serialized blob. Here is how we register those objects in the container - we apply special handling during deserialiation for all objects with the key Key_DoNotSerialize:

builder
        .RegisterType<BotToUserQueue>()
        .Keyed<IBotToUser>(FiberModule.Key_DoNotSerialize)
        .AsSelf()
        .As<IBotToUser>()
        .SingleInstance();

However I cannot find an example or documentation that details how to register your own dependencies into the existing Bot Framework modules.

I also found https://github.com/Microsoft/BotBuilder/issues/252 which indicates that it should be possible to instantiate the dialogs from the container.

I have tried this suggestion:

Func<IDialog<object>> makeRoot = () => actionDialog;
await Conversation.SendAsync(activity, makeRoot);

Together with:

builder
            .RegisterType<ActionDialog>()
            .Keyed<ActionDialog>(FiberModule.Key_DoNotSerialize)
            .AsSelf()
            .As<ActionDialog>()
            .SingleInstance();

This does not work.

I have also tried:

var builder = new ContainerBuilder();
builder.RegisterModule(new DialogModule_MakeRoot());

// My application module
builder.RegisterModule(new ApplicationModule());

using (var container = builder.Build())
using (var scope = DialogModule.BeginLifetimeScope(container, activity))
{
    await Conversation.SendAsync(activity, () => scope.Resolve<ActionDialog>());
}

Together with the following in the ApplicationModule:

            builder
            .RegisterType<ActionDialog>()
            .Keyed<ActionDialog>(FiberModule.Key_DoNotSerialize)
            .AsSelf()
            .As<ActionDialog>()
            .SingleInstance();

This does not work and I encounter the same issue.

If I simply mark all the services and their dependencies as serializable I can get this to work without the need to use FiberModule.Key_DoNotSerialize.

The question is - what is the correct / preferred / recommended way to register and inject dependencies into Bot Framework Dialogs?

Ezequiel Jadib
  • 14,767
  • 2
  • 38
  • 43
jim.taylor.1974
  • 3,493
  • 1
  • 17
  • 11
  • 1
    I believe, you have that exception not because of wrong injection, but because of you have a non-static field of `ThetaBot.Services.BotService` type in your Dialog. While that type is not marked as serializable, it cannot be used this way, independently on if it is injected or not. If you are good to make serializable all the services with their dependencies and store them with the Dialog state, you don't need to use `FiberModule.Key_DoNotSerialize`. In other case you can inject your services in some other place and use them from the Dialog without storing there. – Eugene Berdnikov Aug 31 '16 at 22:21
  • Hi @jim.taylor, Did you get the solution around this problem. I am also trying to pass the service to formflow dialog. – Murtaza Tahir Ali Sep 21 '16 at 03:44
  • 1
    @MurtazaTahirAli - I used a workaround for now. I made the injected service static. [Serializable] public class ActionDialog : LuisDialog { private static IBotService _botService; public ActionDialog(IBotService botService) { _botService = botService; } – jim.taylor.1974 Sep 22 '16 at 05:14

1 Answers1

11

In the Global.asax.cs, you can do do the following to register your services/dialogs:

ContainerBuilder builder = new ContainerBuilder();

builder.RegisterType<IntroDialog>()
  .As<IDialog<object>>()
  .InstancePerDependency();

builder.RegisterType<JobsMapper>()
    .Keyed<IMapper<DocumentSearchResult, GenericSearchResult>>(FiberModule.Key_DoNotSerialize)
    .AsImplementedInterfaces()
    .SingleInstance();

builder.RegisterType<AzureSearchClient>()
    .Keyed<ISearchClient>(FiberModule.Key_DoNotSerialize)
    .AsImplementedInterfaces()
    .SingleInstance();

builder.Update(Conversation.Container);

In your controller, you can then resolve your main dialog as:

using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
    await Conversation.SendAsync(activity, () => scope.Resolve<IDialog<object>>());
}
Ezequiel Jadib
  • 14,767
  • 2
  • 38
  • 43
  • Update is __Obselete__ these days (Containers are considered immutable, lardidadi), replacement is hopefully underway: https://github.com/Microsoft/BotBuilder/issues/1966 – flq Jun 20 '17 at 10:38
  • 2
    The replacement is to use Conversation.UpdateContainer – Ezequiel Jadib Jun 20 '17 at 10:58
  • For those who want to know how to use UpdateContainer see the sample on [ContosoFlower](https://github.com/Microsoft/BotBuilder-Samples/commit/09b58274fe9935de3c13660f4adf30d4ea3b2d77) – Jan Navarro Jul 30 '17 at 04:20
  • Then, what's the correct way to do this? I understand the accepted answer is an outdated way to do it. Please, show us sample code :) Thanks in advance. – SuperJMN Sep 11 '18 at 15:02