/// <summary>
        /// Creates a dialog stack and starts a dialog, pushing it onto the stack.
        /// </summary>
        /// <param name="dialog">The dialog to start.</param>
        /// <param name="turnContext">The context for the current turn of the conversation.</param>
        /// <param name="accessor">The <see cref="IStatePropertyAccessor{DialogState}"/> accessor
        /// with which to manage the state of the dialog stack.</param>
        /// <param name="cancellationToken">A cancellation token that can be used by other objects
        /// or threads to receive notice of cancellation.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public static async Task RunAsync(this Dialog dialog, ITurnContext turnContext, IStatePropertyAccessor <DialogState> accessor, CancellationToken cancellationToken)
        {
            var dialogSet = new DialogSet(accessor)
            {
                TelemetryClient = dialog.TelemetryClient
            };

            dialogSet.Add(dialog);

            var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken).ConfigureAwait(false);

            // Handle EoC and Reprompt event from a parent bot (can be root bot to skill or skill to skill)
            if (IsFromParentToSkill(turnContext))
            {
                // Handle remote cancellation request from parent.
                if (turnContext.Activity.Type == ActivityTypes.EndOfConversation)
                {
                    if (!dialogContext.Stack.Any())
                    {
                        // No dialogs to cancel, just return.
                        return;
                    }

                    var activeDialogContext = GetActiveDialogContext(dialogContext);

                    var remoteCancelText = "Skill was canceled through an EndOfConversation activity from the parent.";
                    await turnContext.TraceActivityAsync($"{typeof(Dialog).Name}.RunAsync()", label : $"{remoteCancelText}", cancellationToken : cancellationToken).ConfigureAwait(false);

                    // Send cancellation message to the top dialog in the stack to ensure all the parents are canceled in the right order.
                    await activeDialogContext.CancelAllDialogsAsync(true, cancellationToken : cancellationToken).ConfigureAwait(false);

                    return;
                }

                // Handle a reprompt event sent from the parent.
                if (turnContext.Activity.Type == ActivityTypes.Event && turnContext.Activity.Name == DialogEvents.RepromptDialog)
                {
                    if (!dialogContext.Stack.Any())
                    {
                        // No dialogs to reprompt, just return.
                        return;
                    }

                    await dialogContext.RepromptDialogAsync(cancellationToken).ConfigureAwait(false);

                    return;
                }
            }

            // Continue or start the dialog.
            var result = await dialogContext.ContinueDialogAsync(cancellationToken).ConfigureAwait(false);

            if (result.Status == DialogTurnStatus.Empty)
            {
                result = await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken).ConfigureAwait(false);
            }

            // Skills should send EoC when the dialog completes.
            if (result.Status == DialogTurnStatus.Complete || result.Status == DialogTurnStatus.Cancelled)
            {
                if (SendEoCToParent(turnContext))
                {
                    var endMessageText = $"Dialog {dialog.Id} has **completed**. Sending EndOfConversation.";
                    await turnContext.TraceActivityAsync($"{typeof(Dialog).Name}.RunAsync()", label : $"{endMessageText}", value : result.Result, cancellationToken : cancellationToken).ConfigureAwait(false);

                    // Send End of conversation at the end.
                    var code     = result.Status == DialogTurnStatus.Complete ? EndOfConversationCodes.CompletedSuccessfully : EndOfConversationCodes.UserCancelled;
                    var activity = new Activity(ActivityTypes.EndOfConversation)
                    {
                        Value = result.Result, Locale = turnContext.Activity.Locale, Code = code
                    };
                    await turnContext.SendActivityAsync(activity, cancellationToken).ConfigureAwait(false);
                }
            }
        }
Example #2
0
        /// <summary>
        /// Creates a dialog stack and starts a dialog, pushing it onto the stack.
        /// </summary>
        /// <param name="dialog">The dialog to start.</param>
        /// <param name="turnContext">The context for the current turn of the conversation.</param>
        /// <param name="accessor">The <see cref="IStatePropertyAccessor{DialogState}"/> accessor
        /// with which to manage the state of the dialog stack.</param>
        /// <param name="cancellationToken">A cancellation token that can be used by other objects
        /// or threads to receive notice of cancellation.</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        public static async Task RunAsync(this Dialog dialog, ITurnContext turnContext, IStatePropertyAccessor <DialogState> accessor, CancellationToken cancellationToken)
        {
            var dialogSet = new DialogSet(accessor)
            {
                TelemetryClient = dialog.TelemetryClient
            };

            dialogSet.Add(dialog);

            var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken).ConfigureAwait(false);

            if (turnContext.TurnState.Get <IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity && SkillValidation.IsSkillClaim(claimIdentity.Claims))
            {
                // The bot is running as a skill.
                if (turnContext.Activity.Type == ActivityTypes.EndOfConversation && dialogContext.Stack.Any() && IsEocComingFromParent(turnContext))
                {
                    // Handle remote cancellation request from parent.
                    var activeDialogContext = GetActiveDialogContext(dialogContext);

                    var remoteCancelText = "Skill was canceled through an EndOfConversation activity from the parent.";
                    await turnContext.TraceActivityAsync($"{typeof(Dialog).Name}.RunAsync()", label : $"{remoteCancelText}", cancellationToken : cancellationToken).ConfigureAwait(false);

                    // Send cancellation message to the top dialog in the stack to ensure all the parents are canceled in the right order.
                    await activeDialogContext.CancelAllDialogsAsync(true, cancellationToken : cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    // Process a reprompt event sent from the parent.
                    if (turnContext.Activity.Type == ActivityTypes.Event && turnContext.Activity.Name == DialogEvents.RepromptDialog && dialogContext.Stack.Any())
                    {
                        await dialogContext.RepromptDialogAsync(cancellationToken).ConfigureAwait(false);

                        return;
                    }

                    // Run the Dialog with the new message Activity and capture the results so we can send end of conversation if needed.
                    var result = await dialogContext.ContinueDialogAsync(cancellationToken).ConfigureAwait(false);

                    if (result.Status == DialogTurnStatus.Empty)
                    {
                        var startMessageText = $"Starting {dialog.Id}.";
                        await turnContext.TraceActivityAsync($"{typeof(Dialog).Name}.RunAsync()", label : $"{startMessageText}", cancellationToken : cancellationToken).ConfigureAwait(false);

                        result = await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken).ConfigureAwait(false);
                    }

                    // Send end of conversation if it is completed or cancelled.
                    if (result.Status == DialogTurnStatus.Complete || result.Status == DialogTurnStatus.Cancelled)
                    {
                        var endMessageText = $"Dialog {dialog.Id} has **completed**. Sending EndOfConversation.";
                        await turnContext.TraceActivityAsync($"{typeof(Dialog).Name}.RunAsync()", label : $"{endMessageText}", value : result.Result, cancellationToken : cancellationToken).ConfigureAwait(false);

                        // Send End of conversation at the end.
                        var activity = new Activity(ActivityTypes.EndOfConversation)
                        {
                            Value = result.Result
                        };
                        await turnContext.SendActivityAsync(activity, cancellationToken).ConfigureAwait(false);
                    }
                }
            }