BEACHSIDE BLOG

MicrosoftとかC#を好むレンジャーの個人的メモ

Bot Framework(V3) - FormFlow 入門 2/2

レッツ 第4次産業革命
Bot Frameworkでの開発、「FormFlow」について、前回の続きです。

公式のドキュメントのFormFlowの「Adding Business Logic」からをざっくり試したメモです。

> Environment

検証した環境は以下です。

2016年7月13日時点の内容です。SDKのバージョンに要注意です。

3. Adding Business Logic

ビジネスロジックを追加してみましょうのコーナーですが、まずは、TermsAttributeについてです。

クライアント側のユーザーがBotの問に対して入力した値は、デフォルトではフィールド名かプロパティ名かenumの値とマッチしているかを判断するようですが、そのルールをオーバーライドできるAttributeのようです。

ToppingOntionsのenumに、以下のようにEverythingを追加し、[Terms]Attributeも追加します。

public enum ToppingOptions
{
    // This starts at 1 because 0 is the "no value" value
    [Terms("except", "but", "not", "no", "all", "everything")]
    Everything = 1,
    ....

botのクライアントから入力する際、Everythingを認識するのは、「1」「Evrything」と、Termsに書いた"except", "but", "not", "no", "all", "everything"です。それを適切に処理するために"Adding Business Logic"していきます。
変更する箇所は、SandwichOrderクラスのBuildFormメソッドです。以下のコードを書くと、Toppingsについてしか質問されなくなりますが...まずはそれで...

5行目からToppingOntionsに関する処理です。使われている処理は、Fieldメソッドです
引数「validateAsyncDelegate」の戻り値となるValidateResultを生成する際に、ひと手間加えています。「Everything」が含まれている場合(かつ、複数の入力がある場合)、入力した内容以外のToppingOptionsの値を返しています。

デバッグ実行して、「everything but avocado」と入力すると、アボカド以外が選択されることが確認できます。
f:id:beachside:20160712123432p:plain

このような処理を応用して、他の外的条件と複合して判断したい場合はロジックを追加してあげればよいのかなと妄想できます。

4. Using the Form Builder

さらにビジネスロジックを追加してみましょうのコーナーです。FormBuilderクラスのインスタンス生成時にいろんなことをします。
前述でさわったSandwichOrderクラスのBuildFormメソッドにさらにコードを追加していきます(70行目あたり)。質問の選択肢となるものもいくつか追加しました。以下のコードになります。

70行目からのFormBuilderをインスタンス化するコード以降で、書いた順にBotの応答が行われます。なんかごちゃごちゃしましたね。しかし、これで一通り動くようになりました。
本家のサンプルから多少カスタマイズしてますが、やってることに差はありません。

さて、90行目で動的にフィールドを定義するコードが出てきました。

5. Dynamically Defined Fields, Confirmations and Messages

本家ドキュメントのDynamically Defined Fields, Confirmations and Messagesの部分になります。Specialsの定義は、45行目でしていますが、選択肢は用意してません。

ところで、
今まではclassやenumで質問や選択肢を構築してきましたが、実用的なことを考えるとDBとかからデータをとってきて質問や選択肢を構築するんじゃないの?どうするの?と疑念が湧いていましたよね...。

そこで動的生成ですよ。
f:id:beachside:20160713115038p:plain

(ちょまどんさんのフレーズ「そこでXamarinですよ」..をパクりました...汗)


90行目からです。ポイントは、SetType、SetActive、SetDefineの3メソッドです。

  • Advanced.Field.SetActiveは、Delegateを引数として受け取り、Trueの場合のみ、以降の処理(次のメソッドでの動的生成)を行う感じです。

101行目は、Confirm(確認メッセージ的なもの)を動的に生成してます。今回の例だと、LengthOptionsに応じて料金を変えています。
(Confirm部分のインデントが下がってるのに意味はありません...)

ようやく動的に質問とかメッセージを追加する方法がイメージできました。

6. Handling Quit and Exceptions

本家ドキュメントでは、次に「Localization」と「JSON Schema FormFlow」が来ますがとばします。
Localizationは、世界にアプリを発信しやすくなりますので、最初からちゃんと構築しておきたいところと考えてますので、そのうち情報をまとめたです。Jsonは...C#でやるから今はいいいかなーくらいに思ってます。

そして「Handling Quit and Exceptions」です。
Botのクライアントで「終了」(localeがjaの時)と入力したときの処理とエラーハンドリングについてです。サンプルのプログラムでは「終了」を入力すると、Exceptionが起きてしまうので、対応が必要ですね。
本家のドキュメントでは、MessagesControllerクラスのMakeRootDialogメソッドにtry-catchを実装していますので、真似て書いてみます。

internal static IDialog<SandwichOrder> MakeRootDialog()
{
    return Chain.From(() => FormDialog.FromForm(SandwichOrder.BuildForm))
        .Do(async (context, order) =>
        {
            try
            {
                var completed = await order;
                await context.PostAsync("(人´З`) ご来店ありがとうございました♪");
            }
            catch (FormCanceledException<SandwichOrder> e)
            {
                //ここでは使ってないけど、何を注文している途中に終了したのかを確認できる
                var item = e.Last;
                //ここでは使ってないけど、注文していた内容の確認できる
                var form = e.LastForm;

                var reply = e.InnerException == null ? 
                          $"[壁]ω;)...やめちゃうんですか...またのご来店をお待ちしてます。" : "問題が発生しました。もう一度お試しください。";
                await context.PostAsync(reply);
            }
        });
}

IDialogインターフェースの拡張メソッドをチェーンして色々としています。
Doメソッドは、Dialogの処理(今回でいう質問の応答)の処理完了後に、"side-effect"(←なんかかっこいい響き...)な処理を実行するときに使うメソッドのようです。今回は、質問の応答処理が終わったら"ご来店ありがとうございました"を出力しています。その途中でExceptionが起きたら、catchしてエラーハンドリング。

(終了の判断をexceptionでするより別の方法使いたい感もありますが、今回は無視..)

デバッグ実行すると、いい感じで動いています。
これで、ざっくりFormFlowの処理を見れました。なんとなくBotを作れそうな気がします。
(本家ドキュメントでは、次にStateをどうするかや前回ちょっと触れたPattern Languageについて記載があります。)

FormFlowの入門はここまでとします。