internal static async Task <DialogTurnResult> InternalRunAsync(ITurnContext turnContext, string dialogId, DialogContext dialogContext, DialogStateManagerConfiguration stateConfiguration, CancellationToken cancellationToken) { // map TurnState into root dialog context.services foreach (var service in turnContext.TurnState) { dialogContext.Services[service.Key] = service.Value; } var dialogStateManager = new DialogStateManager(dialogContext, stateConfiguration); await dialogStateManager.LoadAllScopesAsync(cancellationToken).ConfigureAwait(false); dialogContext.Context.TurnState.Add(dialogStateManager); DialogTurnResult dialogTurnResult = null; // Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn. // // NOTE: We loop around this block because each pass through we either complete the turn and break out of the loop // or we have had an exception AND there was an OnError action which captured the error. We need to continue the // turn based on the actions the OnError handler introduced. var endOfTurn = false; while (!endOfTurn) { try { dialogTurnResult = await InnerRunAsync(turnContext, dialogId, dialogContext, cancellationToken).ConfigureAwait(false); // turn successfully completed, break the loop endOfTurn = true; } catch (Exception err) { // fire error event, bubbling from the leaf. var handled = await dialogContext.EmitEventAsync(DialogEvents.Error, err, bubble : true, fromLeaf : true, cancellationToken : cancellationToken).ConfigureAwait(false); if (!handled) { // error was NOT handled, throw the exception and end the turn. (This will trigger the Adapter.OnError handler and end the entire dialog stack) throw; } } } // save all state scopes to their respective botState locations. await dialogStateManager.SaveAllChangesAsync(cancellationToken).ConfigureAwait(false); // return the redundant result because the DialogManager contract expects it return(dialogTurnResult); }
/// <summary> /// Runs dialog system in the context of an ITurnContext. /// </summary> /// <param name="context">turn context.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <returns>result of the running the logic against the activity.</returns> public async Task <DialogManagerResult> OnTurnAsync(ITurnContext context, CancellationToken cancellationToken = default) { var botStateSet = new BotStateSet(); // Preload TurnState with DM TurnState. foreach (var pair in TurnState) { context.TurnState.Set(pair.Key, pair.Value); } if (ConversationState == null) { ConversationState = context.TurnState.Get <ConversationState>() ?? throw new ArgumentNullException(nameof(ConversationState)); } else { context.TurnState.Set(ConversationState); } botStateSet.Add(ConversationState); if (UserState == null) { UserState = context.TurnState.Get <UserState>(); } if (UserState != null) { botStateSet.Add(UserState); } // create property accessors var lastAccessProperty = ConversationState.CreateProperty <DateTime>(LastAccess); var lastAccess = await lastAccessProperty.GetAsync(context, () => DateTime.UtcNow, cancellationToken).ConfigureAwait(false); // Check for expired conversation if (ExpireAfter.HasValue && (DateTime.UtcNow - lastAccess) >= TimeSpan.FromMilliseconds((double)ExpireAfter)) { // Clear conversation state await ConversationState.ClearStateAsync(context, cancellationToken).ConfigureAwait(false); } lastAccess = DateTime.UtcNow; await lastAccessProperty.SetAsync(context, lastAccess, cancellationToken).ConfigureAwait(false); // get dialog stack var dialogsProperty = ConversationState.CreateProperty <DialogState>(_dialogStateProperty); var dialogState = await dialogsProperty.GetAsync(context, () => new DialogState(), cancellationToken).ConfigureAwait(false); // Create DialogContext var dc = new DialogContext(Dialogs, context, dialogState); // get the DialogStateManager configuration var dialogStateManager = new DialogStateManager(dc, StateConfiguration); await dialogStateManager.LoadAllScopesAsync(cancellationToken).ConfigureAwait(false); dc.Context.TurnState.Add(dialogStateManager); DialogTurnResult turnResult = null; // Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn. // // NOTE: We loop around this block because each pass through we either complete the turn and break out of the loop // or we have had an exception AND there was an OnError action which captured the error. We need to continue the // turn based on the actions the OnError handler introduced. var endOfTurn = false; while (!endOfTurn) { try { if (context.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims)) { // The bot is running as a skill. turnResult = await HandleSkillOnTurnAsync(dc, cancellationToken).ConfigureAwait(false); } else { // The bot is running as root bot. turnResult = await HandleBotOnTurnAsync(dc, cancellationToken).ConfigureAwait(false); } // turn successfully completed, break the loop endOfTurn = true; }
/// <summary> /// Runs dialog system in the context of an ITurnContext. /// </summary> /// <param name="context">turn context.</param> /// <param name="cancellationToken">cancelation token.</param> /// <returns>result of the running the logic against the activity.</returns> public async Task <DialogManagerResult> OnTurnAsync(ITurnContext context, CancellationToken cancellationToken = default(CancellationToken)) { var botStateSet = new BotStateSet(); // preload turnstate with DM turnstate foreach (var pair in this.TurnState) { context.TurnState.Set(pair.Key, pair.Value); } if (this.ConversationState == null) { this.ConversationState = context.TurnState.Get <ConversationState>() ?? throw new ArgumentNullException(nameof(this.ConversationState)); } else { context.TurnState.Set(this.ConversationState); } botStateSet.Add(this.ConversationState); if (this.UserState == null) { this.UserState = context.TurnState.Get <UserState>(); } if (this.UserState != null) { botStateSet.Add(this.UserState); } // create property accessors var lastAccessProperty = ConversationState.CreateProperty <DateTime>(LASTACCESS); var lastAccess = await lastAccessProperty.GetAsync(context, () => DateTime.UtcNow, cancellationToken : cancellationToken).ConfigureAwait(false); // Check for expired conversation var now = DateTime.UtcNow; if (this.ExpireAfter.HasValue && (DateTime.UtcNow - lastAccess) >= TimeSpan.FromMilliseconds((double)this.ExpireAfter)) { // Clear conversation state await ConversationState.ClearStateAsync(context, cancellationToken : cancellationToken).ConfigureAwait(false); } lastAccess = DateTime.UtcNow; await lastAccessProperty.SetAsync(context, lastAccess, cancellationToken : cancellationToken).ConfigureAwait(false); // get dialog stack var dialogsProperty = ConversationState.CreateProperty <DialogState>(this.dialogStateProperty); DialogState dialogState = await dialogsProperty.GetAsync(context, () => new DialogState(), cancellationToken : cancellationToken).ConfigureAwait(false); // Create DialogContext var dc = new DialogContext(this.Dialogs, context, dialogState); // get the dialogstatemanager configuration var dialogStateManager = new DialogStateManager(dc, this.StateConfiguration); await dialogStateManager.LoadAllScopesAsync(cancellationToken).ConfigureAwait(false); dc.Context.TurnState.Add(dialogStateManager); DialogTurnResult turnResult = null; // Loop as long as we are getting valid OnError handled we should continue executing the actions for the turn. // // NOTE: We loop around this block because each pass through we either complete the turn and break out of the loop // or we have had an exception AND there was an OnError action which captured the error. We need to continue the // turn based on the actions the OnError handler introduced. while (true) { try { if (dc.ActiveDialog == null) { // start root dialog turnResult = await dc.BeginDialogAsync(this.rootDialogId, cancellationToken : cancellationToken).ConfigureAwait(false); } else { // Continue execution // - This will apply any queued up interruptions and execute the current/next step(s). turnResult = await dc.ContinueDialogAsync(cancellationToken : cancellationToken).ConfigureAwait(false); if (turnResult.Status == DialogTurnStatus.Empty) { // restart root dialog turnResult = await dc.BeginDialogAsync(this.rootDialogId, cancellationToken : cancellationToken).ConfigureAwait(false); } } // turn successfully completed, break the loop break; } catch (Exception err) { // fire error event, bubbling from the leaf. var handled = await dc.EmitEventAsync(DialogEvents.Error, err, bubble : true, fromLeaf : true, cancellationToken : cancellationToken).ConfigureAwait(false); if (!handled) { // error was NOT handled, throw the exception and end the turn. (This will trigger the Adapter.OnError handler and end the entire dialog stack) throw; } } } // save all state scopes to their respective botState locations. await dialogStateManager.SaveAllChangesAsync(cancellationToken).ConfigureAwait(false); // save botstate changes await botStateSet.SaveAllChangesAsync(dc.Context, false, cancellationToken).ConfigureAwait(false); // send trace of memory var snapshot = dc.GetState().GetMemorySnapshot(); var traceActivity = (Activity)Activity.CreateTraceActivity("BotState", "https://www.botframework.com/schemas/botState", snapshot, "Bot State"); await dc.Context.SendActivityAsync(traceActivity).ConfigureAwait(false); return(new DialogManagerResult() { TurnResult = turnResult }); }
/// <summary> /// Inspects a dialogs memory. /// </summary> /// <param name="context">turn context.</param> /// <param name="inspector">Inspector for analyzing/modifying dialog context.</param> /// <param name="cancellationToken">Cancellation token.</param> /// <returns>result of the running the logic against the activity.</returns> public async Task InspectAsync(ITurnContext context, DialogContextInspector inspector, CancellationToken cancellationToken = default) { // This class just lets you load & save memory in parallel var botStateSet = new BotStateSet(); // Some of the memory scopes expect to find things like storage in the turn state foreach (var pair in InitialTurnState) { context.TurnState.Set(pair.Key, pair.Value); } // register DialogManager with TurnState. context.TurnState.Set(this); // At a minimum you need ConversationState. UserState is optional if (ConversationState == null) { ConversationState = context.TurnState.Get <ConversationState>() ?? throw new NullReferenceException(nameof(ConversationState)); } else { context.TurnState.Set(ConversationState); } // Add conversation state & user state to our parallel class botStateSet.Add(ConversationState); if (UserState == null) { UserState = context.TurnState.Get <UserState>(); } else { context.TurnState.Set(UserState); } if (UserState != null) { botStateSet.Add(UserState); } // get dialog stack var dialogsProperty = ConversationState.CreateProperty <DialogState>(_dialogStateProperty); var dialogState = await dialogsProperty.GetAsync(context, () => new DialogState(), cancellationToken).ConfigureAwait(false); // Create DialogContext var dc = new DialogContext(Dialogs, context, dialogState); // promote initial TurnState into dc.services for contextual services foreach (var service in dc.Services) { dc.Services[service.Key] = service.Value; } // map TurnState into root dialog context.services foreach (var service in context.TurnState) { dc.Services[service.Key] = service.Value; } // get the DialogStateManager configuration // - this configures all of the memory scopes and makes sure all of their memory has been loaded. var dialogStateManager = new DialogStateManager(dc, StateConfiguration); await dialogStateManager.LoadAllScopesAsync(cancellationToken).ConfigureAwait(false); dc.Context.TurnState.Add(dialogStateManager); // Find the DC for the active dialog var activeDc = GetActiveDialogContext(dc); inspector(activeDc); // save all state scopes to their respective botState locations. await dialogStateManager.SaveAllChangesAsync(cancellationToken).ConfigureAwait(false); await botStateSet.SaveAllChangesAsync(dc.Context, false, cancellationToken).ConfigureAwait(false); }