微信自定義菜單接口是一個比較麻煩的接口,往往開發的小伙伴們看到下面的這段返回JSON,整個人就會不好了:
{"menu":{"button":[{"type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC","sub_button":[]},{"type":"click","name":"歌手簡介","key":"V1001_TODAY_SINGER","sub_button":[]},{"name":"菜單","sub_button":[{"type":"view","name":"搜索","url":"http://www.soso.com/","sub_button":[]},{"type":"view","name":"視頻","url":"http://v.qq.com/","sub_button":[]},{"type":"click","name":"贊一下我們","key":"V1001_GOOD","sub_button":[]}]}]}}
N個類型,而且每種類型都有不同的屬性,而且還要sub_button!讓我們去淚奔一會。
碰到這種問題,一般的小伙伴是這麼玩的:
首先我們需要確認總共有哪些屬性,如下所示:
public class MenuFull_RootButton { public string type { get; set; } public string key { get; set; } public string name { get; set; } public string url { get; set; } public string media_id { get; set; } public List<MenuFull_RootButton> sub_button { get; set; } }
然後就獲取到了一堆蹩腳對象。作為代碼潔癖者的我,沒法忍!(開始裝B了)
於是就開始悶著頭編碼了(B裝不下去了)~~~
先定義一個簡單的接口方法,太復雜了後面自己也看不懂。
/// <summary> /// 自定義菜單接口 /// http://mp.weixin.qq.com/wiki/10/0234e39a2025342c17a7d23595c6b40a.html /// </summary> public class MenuApi : ApiBase { const string APIName = "menu"; /// <summary> /// 自定義菜單查詢接口 /// https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN /// </summary> /// <returns>菜單返回結果</returns> public MenuGetApiResultModel Get() { //獲取api請求url var url = GetAccessApiUrl("get", APIName); return Get<MenuGetApiResultModel>(url, new MenuButtonsCustomConverter()); } }
這裡值得注意的是MenuGetApiResultModel和MenuButtonsCustomConverter,就靠他倆出招了。
注意:ApiBase和Get的封裝請暫時忽略。Get在這裡只是用於發起Get請求並且序列化JSON而已,其定義如下:
/// <summary> /// GET提交請求,返回ApiResult對象 /// </summary> /// <typeparam name="T">ApiResult對象</typeparam> /// <param name="url">請求地址</param> /// <param name="jsonConverts">Json轉換器</param> /// <returns>ApiResult對象</returns> protected T Get<T>(string url, params JsonConverter[] jsonConverts) where T : ApiResult
先定義根:
/// <summary> /// 菜單返回結果 /// </summary> public class MenuGetApiResultModel : ApiResult { [JsonProperty("menu")] public MenuInfo Menu { get; set; } } /// <summary> /// 菜單信息 /// </summary> public class MenuInfo { [JsonProperty("button")] public List<MenuButtonBase> Button { get; set; } }
然後定義一個菜單類型:
/// <summary> /// 菜單類型 /// </summary> public enum MenuButtonTypes { /// <summary> /// 點擊推事件 /// </summary> click = 1, /// <summary> /// 跳轉URL /// </summary> view = 2, /// <summary> /// 掃碼推事件 /// </summary> scancode_push = 3, /// <summary> /// 掃碼推事件且彈出“消息接收中”提示框 /// </summary> scancode_waitmsg = 4, /// <summary> /// 彈出系統拍照發圖 /// </summary> pic_sysphoto = 5, /// <summary> /// 彈出拍照或者相冊發圖 /// </summary> pic_photo_or_album = 6, /// <summary> /// 彈出微信相冊發圖器 /// </summary> pic_weixin = 7, /// <summary> /// 彈出地理位置選擇器 /// </summary> location_select = 8, /// <summary> /// 下發消息(除文本消息) /// </summary> media_id = 9, /// <summary> /// 跳轉圖文消息URL /// </summary> view_limited = 10 }
枚舉方便維護。
然後,要定義菜單基類了,開始搞基了:
/// <summary> /// 菜單按鈕基類 /// </summary> public class MenuButtonBase { /// <summary> /// 菜單標題,不超過16個字節,子菜單不超過40個字節 /// </summary> [MaxLength(20)] [JsonProperty(PropertyName = "name", Required = Required.Always)] public virtual string Name { get; set; } /// <summary> /// 菜單類型(菜單的響應動作類型) /// </summary> [JsonConverter(typeof(StringEnumConverter))] [JsonProperty(PropertyName = "type")] public MenuButtonTypes Type { get; set; } }
注意:這裡的命名我還是喜歡駱駝命名法,胸大有感覺!所以,JsonProperty是個好東西。另外,JsonConverter用於設置轉換器,這裡使用了StringEnumConverter,用於將字符串轉換為相應的枚舉類型。那個MaxLength請暫時忽略,我是為將來接口自定義驗證預留的,當然你也可以當成我順手撸上的,不過當前我們不是來做驗證的,我們是來做接口滴。
好了,開始搞基。我們先來定義一級按鈕類型。比如含子菜單的情況:
/// <summary> /// 子菜單按鈕 /// </summary> public class SubMenuButton : MenuButtonBase { /// <summary> /// 菜單標題,不超過16個字節,子菜單不超過40個字節 /// </summary> [MaxLength(8)] [JsonProperty(PropertyName = "name", Required = Required.Always)] public override string Name { get; set; } /// <summary> /// 子菜單(二級菜單數組,個數應為1~5個) /// </summary> [JsonProperty(PropertyName = "sub_button")] public List<MenuButtonBase> SubButtons { get; set; } }
以及其他的類型,這裡舉例說明,篇幅所限:
/// <summary> /// Click按鈕(點擊推事件) /// 用戶點擊click類型按鈕後,微信服務器會通過消息接口推送消息類型為event的結構給開發者(參考消息接口指南),並且帶上按鈕中開發者填寫的key值,開發者可以通過自定義的key值與用戶進行交互; /// </summary> public class ClickButton : MenuButtonBase { public ClickButton() { this.Type = MenuButtonTypes.click; } /// <summary> /// 菜單KEY值,用於消息接口推送,不超過128字節 /// </summary> [JsonProperty(PropertyName = "key", Required = Required.Always)] public string Key { get; set; } } /// <summary> /// 下發消息(除文本消息) /// 用戶點擊media_id類型按鈕後,微信服務器會將開發者填寫的永久素材id對應的素材下發給用戶,永久素材類型可以是圖片、音頻、視頻、圖文消息。請注意:永久素材id必須是在“素材管理/新增永久素材”接口上傳後獲得的合法id。 /// </summary> public class MediaIdButton : MenuButtonBase { public MediaIdButton() { this.Type = MenuButtonTypes.media_id; } /// <summary> /// 調用新增永久素材接口返回的合法media_id /// </summary> [JsonProperty(PropertyName = "media_id", Required = Required.Always)] public string MediaId { get; set; } }
下面省略一千行代碼…
好了,JSON模型初步定義好了,看著像模像樣的,然並卵!好吧,不裝B了,我們繼續。
這B又可以快樂的裝下去了,真開心。
我們先來看看其定義:
/// <summary> /// 菜單按鈕自定義對象創建轉換器 /// 根據菜單類型自定義創建 /// </summary> public class MenuButtonsCustomConverter : CustomCreationConverter<MenuButtonBase> { /// <summary> /// 讀取目標對象的JSON表示 /// </summary> /// <param name="reader">JsonReader</param> /// <param name="objectType">對象類型</param> /// <param name="existingValue"></param> /// <param name="serializer"></param> /// <returns>對象</returns> public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) return null; var jObject = JObject.Load(reader); MenuButtonBase target = default(MenuButtonBase); //獲取type屬性 var type = jObject.Property("type"); if (type != null && type.Count > 0) { var typeValue = type.Value.ToString(); var menuButtonType = (MenuButtonTypes)Enum.Parse(typeof(MenuButtonTypes), typeValue); #region 根據類型返回相應菜單類型 switch (menuButtonType) { case MenuButtonTypes.click: target = new ClickButton(); break; case MenuButtonTypes.view: target = new ViewButton(); break; case MenuButtonTypes.scancode_push: target = new ScancodePushButton(); break; case MenuButtonTypes.scancode_waitmsg: target = new ScancodeWaitmsgButton(); break; case MenuButtonTypes.pic_sysphoto: target = new PicSysphotoButton(); break; case MenuButtonTypes.pic_photo_or_album: target = new PicPhotoOrAlbumButton(); break; case MenuButtonTypes.pic_weixin: target = new PicWeixinButton(); break; case MenuButtonTypes.location_select: target = new LocationSelectButton(); break; case MenuButtonTypes.media_id: target = new MediaIdButton(); break; case MenuButtonTypes.view_limited: target = new ViewLimitedButton(); break; default: throw new NotSupportedException("不支持此類型的菜單按鈕:" + menuButtonType); } #endregion } else { target = new SubMenuButton(); } serializer.Populate(jObject.CreateReader(), target); return target; } /// <summary> /// 創建對象(會被填充) /// </summary> /// <param name="objectType">對象類型</param> /// <returns>對象</returns> public override MenuButtonBase Create(Type objectType) { return new SubMenuButton(); } }
至此,整個是OK的。那麼我們來看看結果:
這B總算裝完了。
這是Magicodes.WeiChat.Framework中,MenuApi的設計,上面只是介紹其原理,後續會完善個性化菜單以及相關接口。
Magicodes.WeiChat.Framework為本人輕量設計的微信SDK,框架基本成型後,會將此部分剝離Magicodes.WeiChat並且開源。希望能夠得到各位熱心觀眾的支持。