使用电子邮件频道发送多个答复

时间:2019-01-29 19:51:48

标签: c# frameworks botframework bots

当回复通过电子邮件渠道收到的电子邮件时,该漫游器会发送400多个回复,使收件箱成为垃圾邮件。我不确定为什么会发生这种情况,尽管看起来好像一旦发送一封电子邮件,代码就会进入一个循环答复和接收电子邮件的过程,并且永远不会结束。

我正在使用SDK 4 .Net Core,在Visual Studio Local上运行代码,在发布到Azure Bot Channel门户时使用ngrok在本地调试。 这是代码:

EchoTestBot.cs

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using EchoTest.Dialogs;
using EchoTest.EmailJson;
using EchoTest.State;
using EchoTest.Utils;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace EchoTest
{
    /// <summary>
    /// Represents a bot that processes incoming activities.
    /// For each user interaction, an instance of this class is created and the OnTurnAsync method is called.
    /// This is a Transient lifetime service.  Transient lifetime services are created
    /// each time they're requested. For each Activity received, a new instance of this
    /// class is created. Objects that are expensive to construct, or have a lifetime
    /// beyond the single turn, should be carefully managed.
    /// For example, the <see cref="MemoryStorage"/> object and associated
    /// <see cref="IStatePropertyAccessor{T}"/> object are created with a singleton lifetime.
    /// </summary>
    /// <seealso cref="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1"/>
    public class EchoTestBot : IBot
    {
        // Define the IDs for the dialogs in the bot's dialog set.
        private const string MainDialogId = "mainDialog";
        private const string TicketDialogId = "ticketDialog";
        private const string FAQDialogId = "faqDialog";
        private const string AlarmDialogId = "alarmDialog";
        private const string EmailDialogNestedId = "emailnestedDialog";

        // Define the dialog set for the bot.
        private readonly DialogSet _dialogs;

        // Define the state accessors and the logger for the bot.
        private readonly BotAccessors _accessors;
        private readonly ILogger _logger;

        /// <summary>
        /// Initializes a new instance of the <see cref="HotelBot"/> class.
        /// </summary>
        /// <param name="accessors">Contains the objects to use to manage state.</param>
        /// <param name="loggerFactory">A <see cref="ILoggerFactory"/> that is hooked to the Azure App Service provider.</param>
        public EchoTestBot(BotAccessors accessors, ILoggerFactory loggerFactory)
        {
            if (loggerFactory == null)
            {
                throw new System.ArgumentNullException(nameof(loggerFactory));
            }

            _logger = loggerFactory.CreateLogger<EchoTestBot>();
            _logger.LogTrace($"{nameof(EchoTestBot)} turn start.");
            _accessors = accessors ?? throw new System.ArgumentNullException(nameof(accessors));

            // Define the steps of the main dialog.
            WaterfallStep[] steps = new WaterfallStep[]
            {
        MenuStepAsync,
        HandleChoiceAsync,
        LoopBackAsync,
            };



            // Create our bot's dialog set, adding a main dialog, an email dialog and the three component dialogs.
            _dialogs = new DialogSet(_accessors.DialogStateAccessor)
                .Add(new WaterfallDialog(MainDialogId, steps))
                .Add(new EmailDialog(EmailDialogNestedId))
                .Add(new TicketDialog(TicketDialogId))
                .Add(new FAQDialog(FAQDialogId))
                .Add(new SetAlarmDialog(AlarmDialogId));
        }

        private static async Task<DialogTurnResult> MenuStepAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken = default(CancellationToken))
        {

            if (stepContext.Context.Activity.ChannelId == "email")
            {


                // Receives the ChannelData Json string, deserialize it into a ChannelDataJson object and cast it into a context.value to send it to the component Dialog
                stepContext.Values["channelData"] = JsonConvert.DeserializeObject<ChannelDataJson>(stepContext.Context.Activity.ChannelData.ToString());
                //((ChannelDataJson)stepContext.Values["channelData"]);
                var h = 0;
                await stepContext.BeginDialogAsync(EmailDialogNestedId, stepContext.Values["channelData"]);
                return await stepContext.EndDialogAsync();
            }
            else
            { await stepContext.ContinueDialogAsync(); }
            // Present the user with a set of "suggested actions".
            List<string> options = new List<string> { "Check INC/CHG/RITM Status", "FAQ", "Chat with you (Under Construction)" };
            await stepContext.Context.SendActivityAsync(
                MessageFactory.SuggestedActions(options, "How can I help you?"),
                cancellationToken: cancellationToken);
            return Dialog.EndOfTurn;
        }

        private async Task<DialogTurnResult> HandleChoiceAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            // Get the user's info. (Since the type factory is null, this will throw if state does not yet have a value for user info.)
            UserInfo userInfo = await _accessors.UserInfoAccessor.GetAsync(stepContext.Context, null, cancellationToken);

            // Check the user's input and decide which dialog to start.
            // Pass in the guest info when starting either of the child dialogs.
            string choice = (stepContext.Result as string)?.Trim()?.ToLowerInvariant();
            switch (choice)
            {
                case "check inc/chg/ritm status":
                    return await stepContext.BeginDialogAsync(TicketDialogId, userInfo, cancellationToken);

                case "faq":
                    return await stepContext.BeginDialogAsync(FAQDialogId, userInfo, cancellationToken);

                case "chat with you (under construction)":
                    return await stepContext.BeginDialogAsync(AlarmDialogId, userInfo.Guest, cancellationToken);

                default:
                    // If we don't recognize the user's intent, start again from the beginning.
                    await stepContext.Context.SendActivityAsync(
                        "Sorry, I don't understand that command. Please choose an option from the list.");
                    return await stepContext.ReplaceDialogAsync(MainDialogId, null, cancellationToken);
            }
        }

        private async Task<DialogTurnResult> LoopBackAsync(
            WaterfallStepContext stepContext,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            // Get the user's info. (Because the type factory is null, this will throw if state does not yet have a value for user info.)
            UserInfo userInfo = await _accessors.UserInfoAccessor.GetAsync(stepContext.Context, null, cancellationToken);
            var h = 0;
            // Process the return value from the child dialog.
            switch (stepContext.Result)
            {
                //case TableInfo table:
                //    // Store the results of the reserve-table dialog.
                //    userInfo.Table = table;
                //    await _accessors.UserInfoAccessor.SetAsync(stepContext.Context, userInfo, cancellationToken);
                //    break;
                //case WakeUpInfo alarm:
                //    // Store the results of the set-wake-up-call dialog.
                //    userInfo.WakeUp = alarm;
                //    await _accessors.UserInfoAccessor.SetAsync(stepContext.Context, userInfo, cancellationToken);
                //    break;
                //The TicketDialog returns a FailoverTemp object and this case is activated
                case FailoverTemp failover:
                    // If the user failed to enter a valid ticket number, the FailoverTemp object returned will have a value of 1 and this if is activated.
                    if (failover.failOver == 1)
                    {

                        //We are using the UserInfo accessor to store persistent RetryAttempts so we can do something about it.
                        userInfo.RetryAttempts++;
                        await _accessors.UserInfoAccessor.SetAsync(stepContext.Context, userInfo, cancellationToken);
                    }
                    //else
                    //{
                    //    //if the user entered a valid ticket number the TicketDialog should return a FailoverTemp value of 0 and no retryattempt should be logged
                    //    await _accessors.UserInfoAccessor.SetAsync(stepContext.Context, userInfo, cancellationToken);
                    //}
                    break;

                default:
                    // We shouldn't get here, since these are no other branches that get this far.
                    break;
            }

            // Restart the main menu dialog.
            return await stepContext.ReplaceDialogAsync(MainDialogId, null, cancellationToken);
        }

        // Below starts the Email Dialog Waterfall steps. This will only trigger if the onTurnAsync detects the incoming activity message is of "email" channelid



            /// <summary>
            /// Every conversation turn for our Echo Bot will call this method.
            /// There are no dialogs used, since it's "single turn" processing, meaning a single
            /// request and response.
            /// </summary>
            /// <param name="turnContext">A <see cref="ITurnContext"/> containing all the data needed
            /// for processing this conversation turn. </param>
            /// <param name="cancellationToken">(Optional) A <see cref="CancellationToken"/> that can be used by other objects
            /// or threads to receive notice of cancellation.</param>
            /// <returns>A <see cref="Task"/> that represents the work queued to execute.</returns>
            /// <seealso cref="BotStateSet"/>
            /// <seealso cref="ConversationState"/>
            /// <seealso cref="IMiddleware"/>
            public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (turnContext.Activity.Type == ActivityTypes.Message)
            {


                // Establish dialog state from the conversation state.
                DialogContext dc = await _dialogs.CreateContextAsync(turnContext, cancellationToken);

                // Get the user's info.
                UserInfo userInfo = await _accessors.UserInfoAccessor.GetAsync(turnContext, () => new UserInfo(), cancellationToken);

                await _accessors.UserInfoAccessor.SetAsync(turnContext, userInfo, cancellationToken);

                // Continue any current dialog.
                DialogTurnResult dialogTurnResult = await dc.ContinueDialogAsync();

                // Process the result of any complete dialog.
                if (dialogTurnResult.Status is DialogTurnStatus.Complete)
                {
                    var i = 0;
                    //switch (dialogTurnResult.Result)
                    //{
                    //    case GuestInfo guestInfo:
                    //        // Store the results of the check-in dialog.
                    //        userInfo.Guest = guestInfo;
                    //        await _accessors.UserInfoAccessor.SetAsync(turnContext, userInfo, cancellationToken);
                    //        break;

                    //    default:
                    //        // We shouldn't get here, since the main dialog is designed to loop.
                    //        break;
                    //}
                }

                // Every dialog step sends a response, so if no response was sent,
                // then no dialog is currently active.

                else if (!turnContext.Responded)
                {
                    var h = 0;
                    // if the user attempted to many times to enter an invalid ticket, this condition is met and the if should open a Too many attempts dialog.
                    //if (userInfo.RetryAttempts > 3)
                    //{
                        //We need to think how to handle too many attemps.
                        await dc.BeginDialogAsync(MainDialogId, null, cancellationToken);
                    //}
                    //else
                    //{
                    //    // Otherwise, start our bot's main dialog.
                    //    await dc.BeginDialogAsync(MainDialogId, null, cancellationToken);
                    //}
                }

                // Save the new turn count into the conversation state.
                await _accessors.ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
                await _accessors.UserState.SaveChangesAsync(turnContext, false, cancellationToken);
            }


            else
            {
                // Commenting this to avoid "event detected" message over the chat.
            // await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected");
            }
        }
    }
}

EmailDialog.cs

using EchoTest.Utils;
using MarvinModels;
using MarvinServices;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Choices;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace EchoTest.Dialogs
{
    public class EmailDialog : ComponentDialog
    {


        // Nested Dialog Id, required to be used with the ReplaceDialog
        private const string EmailDialogId = "emailDialogId";

        public EmailDialog(string id) // id inhereted from the parent Dialog.
            : base(id)
        {
            // The InitialDialogId needs to be set to the ID of a dialog in the nested/child dialog, the one to start when the waterfall starts
            InitialDialogId = EmailDialogId;

            // Define the prompts used in this conversation flow.
            //AddDialog(new ChoicePrompt(FAQPrompt));
            //AddDialog(new ConfirmPrompt(RepeatPrompt));

            // Define the conversation flow using a waterfall model.
            WaterfallStep[] waterfallSteps = new WaterfallStep[]
            {
                CheckQuestionAsync,
                FAQChoicePromptAsync,

            };
            AddDialog(new WaterfallDialog(EmailDialogId, waterfallSteps));



        }

        private static async Task<DialogTurnResult> CheckQuestionAsync(
        WaterfallStepContext stepContext,
        CancellationToken cancellationToken = default(CancellationToken))
        {
            ChannelDataJson email = new ChannelDataJson();
            stepContext.Values["emaildata"] = stepContext.Options;
            var j = 0;
            email = ((ChannelDataJson)stepContext.Values["emaildata"]);

            await stepContext.Context.SendActivityAsync(email.TextBody.Text);
            var h = 0;
            //var strings = ChannelDataEmail.ToString();
            //var j = 0;
            //ChannelDataJson EmailObject = new ChannelDataJson();

            //EmailObject = JsonConvert.DeserializeObject<ChannelDataJson>(strings);

            return await stepContext.EndDialogAsync();
        }


        private static async Task<DialogTurnResult> FAQChoicePromptAsync(
    WaterfallStepContext stepContext,
    CancellationToken cancellationToken = default(CancellationToken))
        {

            //return await stepContext.PromptAsync(FAQPrompt,
            //    new PromptOptions
            //    {
            //        Prompt = MessageFactory.Text("What do you want to know?"),
            //        RetryPrompt = MessageFactory.Text("Selected Option not available . Please try again."),
            //        Choices = ChoiceFactory.ToChoices(questions),

            //    },
            //cancellationToken);
            //await stepContext.Context.SendActivityAsync(
                "What do you want to know?");
            return await stepContext.EndDialogAsync();


        }



    }
}

1 个答案:

答案 0 :(得分:0)

我知道您已经在GH herehere上修复了此问题的一部分,但是我将在这里为其他人的教育提供答案:

您的无限循环是通过将通道检查放在瀑布对话框中引起的。

在MenuStepAsync中,以下各项的组合:

return await stepContext.EndDialogAsync();

(您的(channelId == email)检查结束时,它随后是else块:

else
            { await stepContext.ContinueDialogAsync(); }

结束了瀑布对话框,是的,但是,对这些循环电子邮件中的每一封电子邮件都没有响应,这意味着一旦未发送答复,它就会重新开始并将初始消息重新发送给您的端点。一遍又一遍。当您放入GH问题时,频道检查应在OnTurnAsync中。此外,ContinueDialogAsyncWaterfallStepContext中的行为与在DialogContext中的行为不同。正确的方法是stepContext.NextAsync()