当回复通过电子邮件渠道收到的电子邮件时,该漫游器会发送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();
}
}
}
答案 0 :(得分:0)
我知道您已经在GH here和here上修复了此问题的一部分,但是我将在这里为其他人的教育提供答案:
您的无限循环是通过将通道检查放在瀑布对话框中引起的。
在MenuStepAsync中,以下各项的组合:
return await stepContext.EndDialogAsync();
(您的(channelId == email)检查结束时,它随后是else块:
else
{ await stepContext.ContinueDialogAsync(); }
结束了瀑布对话框,是的,但是,对这些循环电子邮件中的每一封电子邮件都没有响应,这意味着一旦未发送答复,它就会重新开始并将初始消息重新发送给您的端点。一遍又一遍。当您放入GH问题时,频道检查应在OnTurnAsync
中。此外,ContinueDialogAsync
在WaterfallStepContext
中的行为与在DialogContext
中的行为不同。正确的方法是stepContext.NextAsync()
;