跳到主要內容

[C#][Bot Framework] 動態產生FormFlow 選項

Dynamic choice field

假設我們現在有個三明治點餐機器人

而點餐的流程是:要求使用者輸入Sandwich種類,大小( Length),以及  Cheese  種類

那底下的Sample code就能滿足我們目前的需求



[ Serializable ]
public  class  SandwichOrder
{
        
        public  SandwichOptions ? Sandwich;
        [ Prompt ( "What size do you want? {||}" )]
        public  LengthOptions ? Length;
          
        [ Optional ]
        public  CheeseOptions ? Cheese;           

        public  static  IForm < SandwichOrder > BuildForm()
        {
            return  new  FormBuilder < SandwichOrder >()
                        .Message( "Welcome to the simple sandwich order bot!" )                      
                        .Build();
        }
};

public  enum  SandwichOptions {  BLT, BlackForestHam, BuffaloChicken };
public  enum  LengthOptions  { SixInch, FootLong };
public  enum  CheeseOptions  { American, MontereyCheddar, Pepperjack };


但如果今天遊戲規則變成:

不同的三明治就有不同的附餐可以選擇


如:
三明治種類副餐選項
BLTcookie, coke
BlackForestHamcookie, salard
BuffaloChickencoke, salard

此時程式碼就需要改成根據三明治的種類動態產生附餐的選擇

那我們原先定義的form class裡面就需多宣告一個property 來表達副餐如:

        [ Optional ]
        [ Template ( TemplateUsage .NoPreference,  "None" )]
        public  string  Specials;

原來產生Form的程式碼

        public  static  IForm < SandwichOrder > BuildForm()
        {
            return  new  FormBuilder < SandwichOrder >()
                        .Message( "Welcome to the simple sandwich order bot!" )                      
                        .Build();
        }

需改成再透過FormFlow.FormBuilder手動呼叫.Field(IField< T >field) 來產生客製化的field如下


        public static IForm < SandwichOrder > BuildForm()  
        {
            return new FormBuilder < SandwichOrder >()  
                .Message( "Welcome to the simple sandwich order bot!" )                
                .Field( nameof (Sandwich))
                .Field( nameof (Length))
                .Field( nameof (Cheese))
                .Field( new FieldReflector < SandwichOrder >( nameof (Specials)) 
                    .SetType( null )
                    .SetDefine( async  (state, field) =>
                    {
                        var  dic = GetSpecialFree(state.Sandwich);
                        foreach  ( KeyValuePair < string string []> item  in  dic)
                        {
                            field
                                .AddDescription(item.Key, item.Value[0])
                                .AddTerms(item.Key, item.Value);
                        }
                        return true ; 
                    }))
                .Build();
        }

這裡我們使用Advanced.FieldReflector< T >.FieldReflector(string name, bool ignoreAnnotations = false)來替副餐簡單產生一個IField的實體
再加以呼叫Advanced.Field< T >.SetDefine(DefineAsyncDelegate< T > definition )定義副餐field的內容
 
其中:GetSpecialFree 可以改成去Db裡讀資料, 取出和三明治對應的副餐選擇,
(以下的實作方式是為了簡化說明)
        public  static  Dictionary < string ,  string []> GetSpecialFree( SandwichOptions ? sandwichOptions)
        {
            Dictionary < string ,  string []> dic =  new  Dictionary < string ,  string []>();
            switch  (sandwichOptions)
            {
                case  SandwichOptions .BLT:
                    dic.Add( "cookie" ,  new  string [] {  "Free cookie" ,  "cookie"  });
                    dic.Add( "drink" ,  new  string [] {  "Free drink" ,  "drink"  });
                    break ;
                 case  SandwichOptions .BlackForestHam:
                    dic.Add( "cookie" ,  new  string [] {  "Free cookie" ,  "cookie"  });
                    dic.Add( "salard" ,  new  string [] {  "Free salard" ,  "salard"  });
                    break ;
                case  SandwichOptions .BuffaloChicken:
                    dic.Add( "drink" ,  new  string [] {  "Free drink" ,  "drink"  });
                    dic.Add( "salard" ,  new  string [] {  "Free salard" ,  "salard"  });
                    break ;
            }
            return  dic;
        }





結果:


補充I:
若想在某特定條件下才觸發這個選擇
我們可以透過呼叫Advanced.Field< T >.SetActive(ActiveDelegate< T > condition)來達成

.SetActive((state) => state.Length == LengthOptions .FootLong )
表示當 state.Length == LengthOptions .FootLong  這個條件成立時才會產生這個field

補充II:
以上的範例也可以改成呼叫. AddRemainingFields()
把定義在form class裡的成員給帶進來
        public static IForm < SandwichOrder > BuildForm()
        {
            return new FormBuilder < SandwichOrder >()
                .Message( "Welcome to the simple sandwich order bot!" )
                . AddRemainingFields()
                .Field( new FieldReflector < SandwichOrder >( nameof (Specials))
                    .SetType( null )
                    .SetDefine( async (state, field) =>
                    {
                        var dic = GetSpecialFree(state.Sandwich);
                        foreach ( KeyValuePair < string , string []> item in dic)
                        {
                            field
                                .AddDescription(item.Key, item.Value[0])
                                .AddTerms(item.Key, item.Value);
                        }
                        return true ;
                    }))
                .Build();
        }



完整的Sample Code



留言