Reputation: 125
Microsoft Bot Framework V4, I have a waterfall Dialog defined in a dialog as below
var waterfallSteps = new WaterfallStep[]
{
CallConfirmAsync,
SimilarProductAsync,
CheckNewVersionAsync,
};
AddDialog(new WaterfallDialog("productenquiry", waterfallSteps));
After the execution of the first two waterfall steps, my conversation is stop due to unresponsiveness from user's end. So I want to resume from the third method when i.e., CheckNewVersionAsync when the user comes back again to the bot.
Can anyone please help me here.
Upvotes: 1
Views: 1594
Reputation: 7241
Edit: Drew's answer is correct, but mine provides another potential solution. You can find more info here: Managing State. In particular:
User state is available in any turn that the bot is conversing with that user on that channel, regardless of the conversation Conversation state is available in any turn in a specific conversation, regardless of user (i.e. group conversations) Private conversation state is scoped to both the specific conversation and to that specific user
Tip
Both user and conversation state are scoped by channel. The same person using different channels to access your bot appears as different users, one for each channel, and each with a distinct user state.
This solution is best for if you're able to specify the from Id
, but cannot ensure that conversation Id
remains the same (see below, under Gotchas).
You could save what step the user is on in their UserState
.
BasicBot does this with its GreetingState
class.
From its GreetingDialog
:
In the first step, it initializes the GreetingState
, which tracks how far along in the dialog the user is by seeing what user variables have already been set:
private async Task<DialogTurnResult> InitializeStateStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var greetingState = await UserProfileAccessor.GetAsync(stepContext.Context, () => null);
if (greetingState == null)
{
var greetingStateOpt = stepContext.Options as GreetingState;
if (greetingStateOpt != null)
{
await UserProfileAccessor.SetAsync(stepContext.Context, greetingStateOpt);
}
else
{
await UserProfileAccessor.SetAsync(stepContext.Context, new GreetingState());
}
}
return await stepContext.NextAsync();
}
And then in each step, it loads the GreetingState
:
var greetingState = await UserProfileAccessor.GetAsync(stepContext.Context);
And checks to see if the step has already been completed with something like:
if (greetingState != null && !string.IsNullOrWhiteSpace(greetingState.Name) && !string.IsNullOrWhiteSpace(greetingState.City))
If there's no greetingState
or .Name
or .City
exists, it prompts for them, and if they are already filled out, it moves on with:
return await stepContext.NextAsync();
At each step, it saves to the GreetingState
with something like:
greetingState.Name = char.ToUpper(lowerCaseName[0]) + lowerCaseName.Substring(1);
await UserProfileAccessor.SetAsync(stepContext.Context, greetingState);
For you, if you don't need to save user information, you could create a simple Step
class:
{
/// <summary>
/// User state properties for Waterfall Step.
/// </summary>
public class Step
{
public string StepNumber { get; set; }
}
}
Make the first step of your WaterfallDialog:
private async Task<DialogTurnResult> InitializeStateStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var StepState = await UserProfileAccessor.GetAsync(stepContext.Context, () => null);
if (StepState == null)
{
var StepStateOpt = stepContext.Options as StepState;
if (StepStateOpt != null)
{
await UserProfileAccessor.SetAsync(stepContext.Context, StepStateOpt );
}
else
{
await UserProfileAccessor.SetAsync(stepContext.Context, new StepState());
}
}
return await stepContext.NextAsync();
}
On each step, load the current Step
:
var stepState = await UserProfileAccessor.GetAsync(stepContext.Context);
Check to see if they're already past the current step:
if (stepState.StepNumber <= 2)
{
// ...do stuff
// Save that user has completed step
stepState.StepNumber++;
await UserProfileAccessor.SetAsync(stepContext.Context, stepState);
}
else
{
return await stepContext.NextAsync();
}
A couple big things to watch out for:
The UserState only persists for the same from ID
and channel ID
. Make sure that the user that leaves in the middle of a waterfall has the same from ID
when they re-enter it and that they re-enter it from the same channel. This isn't the default for the Emulator--in the Emulator, when a session is restarted, a new from ID
is created. (Note: Consider from ID
to be synonymous with User ID
. It just comes from Activity.From.Id
)
The ConversationState only persists for the same conversation ID
and channel ID
. Persistence of the conversation ID
within the channel varies by channel.
More info on the different IDs: ID fields in the Bot Framework.
Upvotes: 0
Reputation: 33379
So, at the bot level, this should happen automatically if you've configured the IStatePropertyAccessor<DialogState>
with the ConversationState
. No matter how long the user takes to respond, your WaterfallDialog
will stay at the top of the stack and it will remember exactly what step it was on. Assuming your user comes back to the same conversation, then it will pick right up where it left off.
Given that, the fact that you are asking this question leads me to believe that perhaps you are using WebChat which doesn't maintain the same conversationId
across page loads unless you set that up yourself. If that's the case, then I would suggest you ask another question about how to do that if you can't figure out how since that's a separate issue from the dialog state being persisted correctly.
Upvotes: 1