BotBuilder-Samples: IStatePropertyAccessor can not be injected through DI in 45.state-management sample

Sample information

  1. Sample type: State Management
  2. Sample language: dotnetcore
  3. Sample name: 45.state-management

Describe the bug

The Conversation State and User State are creating property inside bot and using the value. In case same property needs to be updated for a separate dialog, then will it require Creating the Property Again. Dont seem to find a way to achieve this using DI.

To Reproduce

Steps to reproduce the behavior:

  1. Try to add a dialog and access the PromptedUserForName property inside it without using CreateProperty method
  2. Try to add IStatePropertyAccessor through DI and it fails -
System.ArgumentException
  HResult=0x80070057
  Message=Cannot instantiate implementation type 'Microsoft.Bot.Builder.IStatePropertyAccessor`1[ChatBot.Classic.App.Models.ConversationStateDataModel]' for service type 'Microsoft.Bot.Builder.IStatePropertyAccessor`1[ChatBot.Classic.App.Models.ConversationStateDataModel]'.
  Source=Microsoft.Extensions.DependencyInjection
  StackTrace:
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.Populate(IEnumerable`1 descriptors)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory..ctor(IEnumerable`1 descriptors)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine..ctor(IEnumerable`1 serviceDescriptors, IServiceProviderEngineCallback callback)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CompiledServiceProviderEngine..ctor(IEnumerable`1 serviceDescriptors, IServiceProviderEngineCallback callback)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.Initialize()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at ChatBot.Classic.App.Program.Main(String[] args) in C:\Projects\TPGit\ChatBot\src\ChatBot.Classic.App\Program.cs:line 10

Expected behavior

DI should allow injecting the IStatePropertyAccessor and the same Conversation Property should be accessed and updated from different dialogs.

Screenshots

None

Additional context

Update the sample to include the context of multiple dialogs accessing the state and updating.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 15 (10 by maintainers)

Most upvoted comments

@mutanttech I played around with this some more and you actually can use DI. Basic steps:

  1. Create a class container for your accessors. Something like:
using Microsoft.Bot.Builder;
using Microsoft.BotBuilderSamples;

namespace Microsoft.BotBuilderSamples
{
    public class StateAccessors
    {
        public IStatePropertyAccessor<ConversationData> ConversationStateAccessors { get; }
        public IStatePropertyAccessor<UserProfile> UserStateAccessors { get; }

        public StateAccessors(ConversationState conversationState, UserState userState)
        {
            ConversationStateAccessors = conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
            UserStateAccessors = userState.CreateProperty<UserProfile>(nameof(UserProfile));
        }
    }
}
  1. DI your Accessors and Dialogs
services.AddSingleton<StateAccessors>();

services.AddSingleton<RootDialog>();
services.AddSingleton<ChildDialog>();

// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, StateManagementBot<RootDialog>>();
  1. Have your MyBot.cs call your Root Dialog like the samples do
public class MyBot<T> : ActivityHandler
        where T : Dialog
[...]
public StateManagementBot(ConversationState conversationState, UserState userState, StateAccessors stateAccessors, T dialog)
        {
            _conversationState = conversationState;
            _userState = userState;
            Dialog = dialog;
[...]
await Dialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
  1. Have your Root Dialog constructor take your StateAccessors as an argument along with any Child dialogs and be sure to Add them
public RootDialog(StateAccessors stateAccessors, ChildDialog childDialog)
[...]
AddDialog(childDialog);
  1. Ensure your Child Dialogs take your StateAccessors as an argument
public ChildDialog(StateAccessors stateAccessors)

I think some of our older samples used this method, as well. I’m not sure if this is the best method or not. @johnataylor: If you could chime in on this, that would be great.

@sgellock @johnataylor

I don’t see that we have a current sample that shows how to address this. Would it be worth writing one up? I’m not sure if there’s a better way than my suggestion that uses the newer changes to the SDK like using DI for dialogs and using the DialogExtensions

…or am I just missing something? Like @mutanttech, I tried to use DI but encountered the same error.

@mdrichardson, please attempt a repro and if found investigate an update for the sample to include the context of multiple dialogs accessing the state and updating.