Привет. После выхода Bot Builder SDK версии 4, мне несколько раз задали один интересный вопрос, а именно, при использовании диалогов, как сохранять незавершённых диалог до выполнения какого-то действия явного его завершения. Реализуется это всё не чуть не сложнее чем в третьей версии, единственное, в текущей документации это не до конца явно описано. Я заглянул в исходный код SDK и ниже опишу что нужно для корректной работы.
На самом деле, всё куда проще чем кажется. Но давайте начнём процесс с анализа. Если мы добавляем диалог, который, например, будет приводить весь текст в верхний регистр, то, вероятно первое, что мы напишем, будет выглядеть следующим образом:
public class ToUpper: IDialog
{
public async Task DialogBegin(DialogContext dc, IDictionary<string, object> dialogArgs = null)
{
var message = dc.Context.Activity.Text;
if (string.IsNullOrEmpty(message))
{
await dc.Context.SendActivity("Введите текст:");
return;
}
await dc.Context.SendActivity(message.ToUpper());
await dc.End();
}
}
Метод, OnTurn
чат-бота, при этом, будет выглядеть следующим образом:
public async Task OnTurn(ITurnContext context)
{
var state = context.GetConversationState<Dictionary<string, object>>();
var dialogCtx = _dialogs.CreateContext(context, state);
if (context.Activity.Type == ActivityTypes.Message)
{
await dialogCtx.Continue();
var message = context.Activity.Text.Trim();
var indexOfSpace = message.IndexOf(" ", StringComparison.Ordinal);
var command = indexOfSpace != -1 ? message.Substring(0, indexOfSpace).ToLower() : message.ToLower();
switch (command)
{
case "toupper":
context.Activity.Text = indexOfSpace >= 0
? context.Activity.Text.Substring(indexOfSpace, message.Length - indexOfSpace)
: String.Empty;
await dialogCtx.Begin("toUpper");
break;
}
}
}
Работать подобный чат-бот будет следующим образом:
Если мы сразу передаём нужный текст, всё отлично отрабатывает. Но уже если текст не был передан, то обратно в данный диалог мы не попадаем. Чтобы понять почему это происходит, необходимо заглянуть внутрь метода Continue
из этой строки:
await dialogCtx.Continue();
Он представляет из себя следующее:
public async Task Continue()
{
// Check for a dialog on the stack
if (ActiveDialog != null)
{
// Lookup dialog
var dialog = Dialogs.Find(ActiveDialog.Id);
if (dialog == null)
{
throw new Exception($"DialogSet.continue(): Can't continue dialog. A dialog with an id of '{ActiveDialog.Id}' wasn't found.");
}
// Check for existence of a continue() method
if (dialog is IDialogContinue)
{
// Continue execution of dialog
await ((IDialogContinue)dialog).DialogContinue(this);
}
}
}
Сразу становится понятно, что продолжение выполнения текущего диалога будет происходить в том случае, если он реализует интерфейс IDialogContinue
. Интерфейс IDialogContinue
, наследуется от IDialog
и представляет из себя следующее:
public interface IDialogContinue : IDialog
{
Task DialogContinue(DialogContext dc);
}
Вернёмся к нашему чат-боту, диалог ToUpper
наследуем от IDialogContinue
и реализуем метод DialogContinue
, весь класс будет представлять из себя следующее:
public class ToUpper: IDialogContinue
{
public async Task DialogBegin(DialogContext dc, IDictionary<string, object> dialogArgs = null)
{
var message = dc.Context.Activity.Text;
if (string.IsNullOrEmpty(message))
{
await dc.Context.SendActivity("Введите текст:");
return;
}
await dc.Context.SendActivity(message.ToUpper());
await dc.End();
}
public async Task DialogContinue(DialogContext dc)
{
await DialogBegin(dc);
}
}
Запустим чат-бот и проверим как он работает:
Всё отработало именно так, как и ожидалось. Завершение работы с чат-ботом происходит только в случае его явного завершения вызовом End или при явном переключении на другие диалоги.
Так что, в случае возникновения вопросов, не ленитесь заглянуть в исходный код проекта, ведь документация может быть не всегда актуальной, либо недостаточно явно описывать решения нужных проблем, а наличие репозитория с исходным кодом позволяет всегда получать актуальную и полную информацию о принципах работы проекта.
Приятного программирования.