Souvik Ghosh
Souvik Ghosh

Reputation: 4616

The given key 'dialogs' was not present in the dictionary while using ContinueDialogAsync

I am using Bot Framework SDK v-4. I have created a couple of dialogs inherited from ComponentDialog and overridden the methods: BeginDialogAsync and ContinueDialogAsync. Below is the implementation of the IBot.

public class Bot : IBot
{
    private readonly BotAccessors _accessors;
    private readonly ILogger _logger;

    private DialogSet _dialogs = null;
    private string _botName = string.Empty;
    private IConfiguration _configuration = null;
    private UserDetail _userDetail = null;
    private ConversationData _conversationData = null;
    private IStatePropertyAccessor<TurnState> _turnStateAccessor = null;

    /// <summary>
    /// Initializes a new instance of the class.
    /// </summary>                        
    public Bot(BotAccessors accessors, ILoggerFactory loggerFactory, IConfiguration configuration)
    {
        _accessors = accessors ?? throw new System.ArgumentNullException(nameof(accessors));
        if (loggerFactory == null)
        {
            throw new System.ArgumentNullException(nameof(loggerFactory));
        }

        _configuration = configuration;
        _dialogs = new DialogSet(accessors.ConversationState.CreateProperty<DialogState>(nameof(DialogState)));
        _turnStateAccessor = accessors.TurnStateAccessor;
        _dialogs.Add(new GreetingDialog(_turnStateAccessor, loggerFactory, configuration));
        _dialogs.Add(new QnADialog(_turnStateAccessor, loggerFactory, configuration));
        _botName = configuration["BotName"];
        _logger = loggerFactory.CreateLogger<Bot>();
        _logger.LogTrace("Bot turn start.");
    }

    public override Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
    {
        _turnState = _turnStateAccessor.GetAsync(outerDc.Context).Result;
        outerDc.ContinueDialogAsync();
        return base.BeginDialogAsync(outerDc, options, cancellationToken);
    }

    public override Task<DialogTurnResult> ContinueDialogAsync(DialogContext outerDc, CancellationToken cancellationToken = default(CancellationToken))
    {
        _turnState = _turnStateAccessor.GetAsync(outerDc.Context).Result;

        //some code
        _turnStateAccessor.SetAsync(outerDc.Context, _turnState).ConfigureAwait(false);
        return base.ContinueDialogAsync(outerDc, cancellationToken);
    }

}

I call the dialog from the OnTurnAsync method of the Bot like this: await dialogContext.BeginDialogAsync(nameof(GreetingDialog)).ConfigureAwait(false);. It reaches the BeginDialogAsync method of my dialog and then continues to ContinueDialogAsync. It works fine, however, while returning (using base.ContinueDialogAsync(outerDc, cancellationToken);) from the method, I get some exception which I captured from the Diagnostic tool of the Visual Studio.

enter image description here

Also, I am sending the exception as an activity message to the client (bot framework emulator) which is shown below.

Sorry, it looks like something went wrong. Exception: System.ArgumentNullException: Value cannot be null. Parameter name: dialogId at Microsoft.Bot.Builder.Dialogs.DialogContext.BeginDialogAsync(String dialogId, Object options, CancellationToken cancellationToken) in D:\a\1\s\libraries\Microsoft.Bot.Builder.Dialogs\DialogContext.cs:line 84 at Microsoft.Bot.Builder.Dialogs.ComponentDialog.BeginDialogAsync(DialogContext outerDc, Object options, CancellationToken cancellationToken) in D:\a\1\s\libraries\Microsoft.Bot.Builder.Dialogs\ComponentDialog.cs:line 59 at Microsoft.Bot.Builder.Dialogs.DialogContext.BeginDialogAsync(String dialogId, Object options, CancellationToken cancellationToken) in D:\a\1\s\libraries\Microsoft.Bot.Builder.Dialogs\DialogContext.cs:line 84 at Chatbot.Bot.OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken) in C:\GIS\ChatbotNew\Chatbot\Chatbot\Bot.cs:line 120 at Microsoft.Bot.Builder.MiddlewareSet.ReceiveActivityWithStatusAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken) in D:\a\1\s\libraries\Microsoft.Bot.Builder\MiddlewareSet.cs:line 55 at Microsoft.Bot.Builder.BotAdapter.RunPipelineAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken cancellationToken) in D:\a\1\s\libraries\Microsoft.Bot.Builder\BotAdapter.cs:line 167

Update-1

Based on Drew's answer I removed outerDc.ContinueDialogAsync(); but I get some error (shown below) while stepping through the return base.BeginDialogAsync(outerDc, options, cancellationToken); in BeginDialogAsync function.

enter image description here

Also, while stepping through the OnTurnAsync function I get an error while trying to get the BotState as shown below.

enter image description here

TurnState implementation:

public class TurnState
{
    public int TurnCount { get; set; } = 0;
    public string BotType { get; set; } = string.Empty;
    public string ConversationLanguage { get; set; } = string.Empty;
    //other properties...
}

Similar error while trying to create the DialogContext as well.

ConfigureServices in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddBot<Bot>(options =>
   {
       var secretKey = Configuration.GetSection("botFileSecret")?.Value;

        // Loads .bot configuration file and adds a singleton that your Bot can access through dependency injection.
        var botConfig = BotConfiguration.Load(@".\Chatbot.bot", secretKey);
       services.AddSingleton(sp => botConfig);

        // Retrieve current endpoint.
        var service = botConfig.Services.Where(s => s.Type == "endpoint" && s.Name == "development").FirstOrDefault();
       if (!(service is EndpointService endpointService))
       {
           throw new InvalidOperationException($"The .bot file does not contain a development endpoint.");
       }

       //options.CredentialProvider = new SimpleCredentialProvider(Configuration[MicrosoftAppCredentials.MicrosoftAppIdKey], Configuration[MicrosoftAppCredentials.MicrosoftAppPasswordKey]);
       options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);


       // Creates a logger for the application
       ILogger logger = _loggerFactory.CreateLogger<Bot>();

        // Catches any errors that occur during a conversation turn and logs them.
        options.OnTurnError = async (context, exception) =>
       {
           await context.SendActivityAsync("Sorry, it looks like something went wrong. Exception: " + exception);
       };

       // The Memory Storage used here is for local bot debugging only. When the bot
       // is restarted, everything stored in memory will be gone.
       IStorage dataStore = new MemoryStorage();

       var conversationState = new ConversationState(dataStore);

       options.State.Add(conversationState);

   });

    // Create and register state accesssors.
    // Acessors created here are passed into the IBot-derived class on every turn.
    services.AddSingleton<BotAccessors>(sp =>
    {
        var options = sp.GetRequiredService<IOptions<BotFrameworkOptions>>().Value;
        if (options == null)
        {
            throw new InvalidOperationException("BotFrameworkOptions must be configured prior to setting up the state accessors");
        }

        var conversationState = options.State.OfType<ConversationState>().FirstOrDefault();
        if (conversationState == null)
        {
            throw new InvalidOperationException("ConversationState must be defined and added before adding conversation-scoped state accessors.");
        }

        // Create the custom state accessor.
        // State accessors enable other components to read and write individual properties of state.
        var accessors = new BotAccessors(conversationState)
        {
            TurnStateAccessor = conversationState.CreateProperty<TurnState>(BotAccessors.TurnStateName),
        };

        return accessors;
    });
}

Any help with this please?

Upvotes: 0

Views: 1121

Answers (1)

Drew Marsh
Drew Marsh

Reputation: 33379

I'm trying to wrap my head around exactly what you're expecting to happen here:

public override Task<DialogTurnResult> BeginDialogAsync(DialogContext outerDc, object options = null, CancellationToken cancellationToken = default(CancellationToken))
{
    _turnState = _turnStateAccessor.GetAsync(outerDc.Context).Result;
    outerDc.ContinueDialogAsync();
    return base.BeginDialogAsync(outerDc, options, cancellationToken);
}

Specifically, why are you calling outerDc.ContinueDialogAsync() here? That is basically tying the dialog stack up in a knot. If you removed that line everything else you show here should work perfectly.

If you update your question with some more details on exactly what you were trying to accomplish by calling that I'll happily update my answer to try and set you on the right path for accomplishing whatever that might be.

Upvotes: 0

Related Questions