在上一篇我們瞭解了如何透過使用建立Model然後搭配FormFlow的方式讓我們的chatbot可以從使用者那邊搜集到表單類型的資訊。
不過我們也開始遇到一些問題,舉例來説,欄位名稱是英文,如果中途退出就gg了等等的細節問題。這些問題需要我們對Model或者FormFlow建立的時候做一些調整。
這篇將和大家介紹一下,如何做這些調整。
還記得以前學MVC的時候,做CRUD資料的部分都是搭配Entity Framework儲存在資料庫。那個時候,表單呈現的欄位也是用Model定義出來,如果欄位名稱要調整,那時候我們需要做什麽?
沒錯,那時候我們會用DataAnnotation Attribute來給我們的Model欄位提供一些資訊,在前臺的Helper產生欄位的時候就會使用這些資訊對產生内容做出微調。而BotBuilder是一樣的概念。
還有另外一種微調方式是在把Model轉換成爲FormFlow的時候,那邊也可以做一些調整。
在這篇,將會對以下幾個微調的地方做説明:
增加開始時候的説明文字
處理使用者退出不報錯
調整呈現的文字
加强資料驗證
内建多語系説明
目前,當我們觸發FormFlow的時候,直接就進入了每一個欄位的問題,不過可能對使用者來説少了一個引導詞。因此,一般來説會加一個説明接下來會發生什麽事情。
加的方式是透過建立FormFlow的時候多執行一個Message的方法:
public static IForm<RoomReservation> BuildForm()
{
return new FormBuilder<RoomReservation>()
.Message
("歡迎使用訂房功能。接下來將會問您一系列問題好讓我們幫您找到最好的房間。" +
$"{Environment.NewLine}有任何問題隨時打入Help將有幫助文字出現。")
.Build();
}
呈現效果,左邊是沒加過之前,右邊是加過之後
使用者在填寫表單過程中,如果有退出的情況(輸入quit),這個時候系統會報錯。原因是,當使用者退出的時候,一個exception會被丟出來。目前我們沒有接任何exception因此直接導致整個機器人就出錯了。
要解決也簡單,我們只要在FormFlow觸發時給的那個CallBack加上處理即可:
private async Task AfterReserveRoomAsync(IDialogContext context
, IAwaitable<RoomReservation> result)
{
RoomReservation reservation = null;
try
{
reservation = await result;
await context.PostAsync($"得到的結果:{Environment.NewLine} {JsonConvert.SerializeObject(reservation)}");
}
catch(FormCanceledException<RoomReservation> ex)
{
string reply;
if (ex.InnerException == null)
{
reply = $"您在 {ex.Last} 的時候退出了 -- 如果有遇到任何問題請告訴我們";
}
else
{
reply = "機器人暫時罷工了,請稍後嘗試";
}
await context.PostAsync(reply);
}
finally
{
context.Wait(MessageReceivedAsync);
}
}
左邊是原來的結果,右邊是經過處理的結果
FormFlow在處理英文的時候我盡量幫忙切字,因此詢問欄位的時候不會怪怪的。例如,我們的StartDate在詢問的時候很好的切割變成:sart date
不過中文不是這樣運作,這個時候我們可以透過給Property Attribute設定來告訴FormFlow呈現内容是什麽。總共有3個Attribute和這個有關:
DescribeAttribute
這個是用來設定欄位的名稱
PromptAttribute
這個是用來設定整段問題的文字。
TemplateAttribute
這個和DescribeAttribute是一樣的,不過會影響到每個欄位,DescribeAttribute則是只影響到有被設定的那個欄位。
假設今天我們的StartDate要呈現為:入住日期,那麽我們可以在Model裡面加入PromptAttribute:
[Serializable]
public class RoomReservation
{
[Describe("入住日期")]
public DateTime StartDate { get; set; }
...
左邊是原來的内容,右邊是有調整過呈現的内容
我們可以看到,修改之後,欄位文字變成我們在Describe定義的那樣,不過整體文字還是英文。這個時候就是PromptAttribute進來的時候:
[Serializable]
public class RoomReservation
{
[Describe("入住日期")]
[Prompt("請輸入您的 {&}")]
public DateTime StartDate { get; set; }
...
左邊是原來的内容,右邊是調整過呈現的内容
PromptAttribute修改的是某個欄位的輸出,但是如果我全部的欄位都要調整呢?這個時候就是使用TemplateAttribute的時候。TemplateAttribute是放到class等級,因此影響所有欄位。
Prompt和Template輸入的内容其實是pattern language,裡面有一些特殊字用來替換,例如{&}代表這個地方放入欄位名稱。更多特殊字可以參考:Customize user experience with pattern language
欄位輸入的很大一個注意事項就是驗證,需要過濾掉使用者輸入有問題的欄位。
内建FormFlow已經有幫忙驗證基本形態,舉例來説,如果是數字形態,那麽輸入文字FormFlow會提示輸入錯誤。如果是enum,輸入不在enum的欄位也會提示,不過有些更商務邏輯的驗證就需要開發者處理。
舉例來説,數字類型的輸入只允許1到10之類。或者說,如果某個欄位輸入了什麽,另外一個欄位的值就要是多少。
在FormFlow有提供三種方式:
使用NumericAttribute - 定義數字型可以在什麽範圍内
使用 PatternAttribute - 用RegEx定義,輸入的内容可以是什麽
使用 FormFlow 來定義複雜邏輯
首先,先來看看數字型的驗證:
[Serializable]
public class RoomReservation
{
...
[Numeric(1, 5)]
public int NumberOfNightToStay { get; set; }
...
左邊是原來的情況,右邊則是加入後的結果
PatternAttribute和NumericAttribute是一樣的概念,也是透過設定在某個欄位上面就有效果。
最後一種可以在建立FormFlow的時候設定驗證邏輯。除了驗證邏輯之外,也可以用來改值。舉例來説,如果我要把所有的開始日期都加1天,我可以透過這個客製邏輯方式達到:
public static IForm<RoomReservation> BuildForm()
{
return new FormBuilder<RoomReservation>()
.Message
("歡迎使用訂房功能。接下來將會問您一系列問題好讓我們幫您找到最好的房間。" +
$"{Environment.NewLine}有任何問題隨時打入Help將有幫助文字出現。")
.Field(nameof(StartDate),
validate: async (state, value) =>
{
var result = new ValidateResult
{ IsValid = true, Value = value };
var datetime = (DateTime)value;
result.Value = datetime.AddDays(1);
return result;
})
.Field(nameof(NumberOfNightToStay))
.Field(nameof(NumberOfOccupants))
.Field(nameof(BedSize))
.Build();
}
最後呈現結果,每次日期+1
到目前爲止,我們的FormFlow主要都是英文,當然我們可以透過上面介紹的例如Template來達到全部改成中文,但是實際上FormFlow本身就有支援多語系。
内建只有簡體中文,如果要繁體中文就需要自己處理了。
我們可以透過Bot Emulator v3 版本,連綫的時候輸入中文的語系:zh-cn
設定locale的畫面
接下來我們做測試,會發現FormFlow裡面本來是英文全部變成中文啦:
FormFlow全部變成了中文
不過我們同時會發現,本來關鍵字也變成了中文。例如看狀態的status,可能以爲是狀態,但是實際上是进度:
看到目前輸入情況
詳細的關鍵字,可以從官方source code看到:BotBuilder/CSharp/Library/Microsoft.Bot.Builder/MultilingualResources/Microsoft.Bot.Builder.zh-Hans.xlf
我們發現FormFlow非常的好用,能夠快速收集使用者的資料來完成表單填寫。
但是,魔鬼藏在細節裡面,而這些細節能夠透過這篇裡面做一些微調。
是時候在回到我們之前的問題,邏輯全部都擠在了RootDialog裡面,那以後怎麽維護?我們的SOLID都去了哪裏?
BotBuilder考慮到了這個事情,因此有IDialog,下一篇將來看看怎麽拆分我們邏輯讓一切變得更加乾净并且更SOLID。