Bot Framework と Microsoft Graph で DevOps その 10 : フォームフロー入門編

前回はダイアログ応用編として DialogPrompt の機能など紹介しました。今回はフォームフロー (FormFlow) 入門編をお届けします。

概要

前回 DialogPrompt を利用して、イベント作成に必要な情報を集めました。しかしフォームフローはこれをさらに簡単にしてくれます。フォームフローはクラスの定義を元に、適切なダイアログを作ってくれます。百読は一コードに如かずということで早速。

フォームフローを利用したイベントの作成

モデルの作成

フォームフローはモデルベースであるため、モデルを作ります。Microsoft.Graph.Event クラスをそのままモデルに使いたいんですが、Serializable 属性ないんですよね。あと不要なフィールドなどもあるので、新規作成を。

1. Visual Studio でボットアプリプロジェクトに Models フォルダを追加。

image

2. Models フォルダ内に、OutlookEvent.cs を追加し、以下と差し替え。Serializable 属性忘れないように。

using System;

namespace O365Bot.Models
{
    [Serializable]
    public class OutlookEvent
    {
        public string Subject { get; set; }
        public string Description { get; set; }
        public DateTime Start { get; set; }
        public bool IsAllDay { get; set; }
        public double Hours { get; set; }
    }
}

CreateEventDialog.cs の変更

PromptDialog によるチェーンではなく、フォームフローを直接利用するようにします。以下のコードに差し替え。コード短くなりましたね。

using Autofac;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using Microsoft.Graph;
using O365Bot.Models;
using O365Bot.Services;
using System;
using System.Threading.Tasks;

namespace O365Bot.Dialogs
{
    [Serializable]
    public class CreateEventDialog : IDialog<bool> // このダイアログが完了時に返す型
    {
        public async Task StartAsync(IDialogContext context)
        {
            // FormFlow でダイアログを作成して、呼び出し。
            var outlookEventFormDialog = FormDialog.FromForm(this.BuildOutlookEventForm, FormOptions.PromptInStart);
            context.Call(outlookEventFormDialog, this.ResumeAfterDialog);
        }

        private async Task ResumeAfterDialog(IDialogContext context, IAwaitable<OutlookEvent> result)
        {
            await context.PostAsync("イベントを作成しました。");

            // ダイアログの完了を宣言
            context.Done(true);
        }

        private IForm<OutlookEvent> BuildOutlookEventForm()
        {
            OnCompletionAsyncDelegate<OutlookEvent> processOutlookEventCreate = async (context, state) =>
            {
                using (var scope = WebApiApplication.Container.BeginLifetimeScope())
                {
                    IEventService service = scope.Resolve<IEventService>(new TypedParameter(typeof(IDialogContext), context));
                    // TimeZone は https://graph.microsoft.com/beta/me/mailboxSettings で取得可能だがここでは一旦ハードコード
                    Event @event = new Event()
                    {
                        Subject = state.Subject,
                        Start = new DateTimeTimeZone() { DateTime = state.Start.ToString(), TimeZone = "Tokyo Standard Time" },
                        IsAllDay = state.IsAllDay,
                        End = state.IsAllDay ? null : new DateTimeTimeZone() { DateTime = state.Start.AddHours(state.Hours).ToString(), TimeZone = "Tokyo Standard Time" },
                        Body = new ItemBody() { Content = state.Description, ContentType = BodyType.Text }
                    };
                    await service.CreateEvent(@event);
                }
            };

            return new FormBuilder<OutlookEvent>()
                .Message("イベントを作成します。")
                .AddRemainingFields() // すべてのフィールドを処理対象として追加
                .OnCompletion(processOutlookEventCreate)
                .Build();
        }
        
    }
}

エミュレーターによる検証

エミュレータで実行すると、以下のような感じになります。

image

image

色々文言や質問の順番がきれいではありませんが、動くのは動きますね。ではこの辺直してみましょう。

表示の改善

モデルの変更

ユーザーに対する質問や順番はモデルをいじることで変更できます。モデルをいじらない方法は次回。

モデルを以下のように変更します。属性ですべてを制御できます。また {||} のようなパターンも使えます。使えるパターンはこちら

using Microsoft.Bot.Builder.FormFlow;
using System;

namespace O365Bot.Models
{
    [Serializable]
    public class OutlookEvent
    {
        [Prompt("件名は?")]
        public string Subject { get; set; }
        [Prompt("詳細は?")]
        public string Description { get; set; }
        [Prompt("いつから?yyyy/MM/dd HH:mm 形式で入力してください。")]
        public DateTime Start { get; set; }
        [Prompt("終日イベント?{||}")]
        public bool IsAllDay { get; set; }
        [Prompt("何時間?")]
        public double Hours { get; set; }
    }
}

エミュレーターによる検証

image

image

前回と同じ感じになりました。

チェックインしてテストが通るかも確認してください。(この時点では失敗するテストがあるんですが、解決は次回)

まとめ

フォームフロー簡単すぎて拍子抜けしますが、次回は応用編ということでより高度な処理について紹介します。