Search code examples
botframework

How to read state property accessors outside the dialog in V4 Bot Framework


I'm using bot framework version 4. I would like to access user state properties in the validator method but I didn't find any solution to it.

GitHub

In the GitHub sample above, we have a validator AgePromptValidatorAsync which validates age. But I would want to access Name which I have stored in State property. How could that be achieved. And is it possible to access state/use GetAsync in a method outside dialog which doesn't contain context.

@mdrichardson could you please help me in this.Thank you in advance.


Solution

  • 1. Ensure that UserProfile.Name is saved before hitting validation.

    That sample doesn't do this on it's own, so you would:

    private async Task<DialogTurnResult> NameConfirmStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        stepContext.Values["name"] = (string)stepContext.Result;
    
        // ADDED: This code block saves the Name
        if (!string.IsNullOrEmpty((string)stepContext.Result)) {
            var userProfile = await _userProfileAccessor.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);
            userProfile.Name = (string)stepContext.Result;
            await _userProfileAccessor.SetAsync(stepContext.Context, userProfile);
        }
    
        // We can send messages to the user at any point in the WaterfallStep.
        await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Thanks {stepContext.Result}."), cancellationToken);
    
        // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
        return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = MessageFactory.Text("Would you like to give your age?") }, cancellationToken);
    }
    

    2. Access the User Profile

    // CHANGED: Since this accesses the userProfile, the method is no longer static. Also must be async
    private async Task<bool> AgePromptValidatorAsync(PromptValidatorContext<int> promptContext, CancellationToken cancellationToken)
    {
        // ADDED: Here is how you can access the Name
        //   Note: You can use promptContext.Context instead of stepContext.Context since they're both ITurnContext
        var userProfile = await _userProfileAccessor.GetAsync(promptContext.Context, () => new UserProfile(), cancellationToken);
        var name = userProfile.Name;
        // Do whatever you want with the Name
    
        // CHANGED: Since this is now async, we don't return Task.FromResult(). We just return the result
        return promptContext.Recognized.Succeeded && promptContext.Recognized.Value > 0 && promptContext.Recognized.Value < 150;
    }
    

    Accessing the UserProfile Without Context

    This is kind of possible, but you can't do this easily or out-of-the-box. There are some options that you can use, however (mostly in order from least difficult to most):

    1. Pass the context to whatever method/function you need to use it in. Just about every bot method you'd use has some kind of context that you can pass into another method. This is definitely your best option.
    2. Create a separate class that you use to store variables in bot memory
    3. Either Write Directly to Storage or Implement Custom Storage that you use to track the UserProfile. Note, you'd have to pass around your Storage object, so you may as well just pass around the context, instead.
    4. Use the new Adaptive Dialogs, since they do state management differently. I highly recommend against this, though, as these are "experimental", meaning that there's still bugs and we barely use this internally. I'm adding this as an option more for posterity and users that want to play with new stuff.