微信自定义菜单接口是一个比较麻烦的接口,往往开发的小伙伴们看到下面的这段返回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装不下去了)~~~

1. 定义接口方法

先定义一个简单的接口方法,太复杂了后面自己也看不懂。

/// <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

2. 定义JSON模型

先定义根:

/// <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 = ,
/// <summary>
/// 跳转URL
/// </summary>
view = ,
/// <summary>
/// 扫码推事件
/// </summary>
scancode_push = ,
/// <summary>
/// 扫码推事件且弹出“消息接收中”提示框
/// </summary>
scancode_waitmsg = ,
/// <summary>
/// 弹出系统拍照发图
/// </summary>
pic_sysphoto = ,
/// <summary>
/// 弹出拍照或者相册发图
/// </summary>
pic_photo_or_album = ,
/// <summary>
/// 弹出微信相册发图器
/// </summary>
pic_weixin = ,
/// <summary>
/// 弹出地理位置选择器
/// </summary>
location_select = ,
/// <summary>
/// 下发消息(除文本消息)
/// </summary>
media_id = ,
/// <summary>
/// 跳转图文消息URL
/// </summary>
view_limited =
}

枚举方便维护。

然后,要定义菜单基类了,开始搞基了:

/// <summary>
/// 菜单按钮基类
/// </summary>
public class MenuButtonBase
{
/// <summary>
/// 菜单标题,不超过16个字节,子菜单不超过40个字节
/// </summary>
[MaxLength()]
[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()]
[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了,我们继续。

3. 定义自定义对象创建转换器(CustomCreationConverter

这B又可以快乐的装下去了,真开心。

我们先来看看其定义:

从定义中可以看出来,Create是充话费送的,必须实现,然并卵,这玩意儿没法实现我们上述的需求。objectType是拿不到多少有价值的信息的,看看源码就清楚,这货是给ReadJson用的。哎哟,这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 > )
{
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并且开源。希望能够得到各位热心观众的支持。

Magcodes.WeiChat——自定义CustomCreationConverter之实现微信自定义菜单的序列化的更多相关文章

  1. Vue微信自定义分享时安卓系统config:ok,ios系统config:invalid signature签名错误,或者安卓和ios二次分享时均config:ok但是分享无效的解决办法

    简述需求:要求指定页面可以进行微信自定义分享(自定义标题,描述,图片,链接),剩下的页面隐藏所有基础接口.二次分享依然可以正常使用,切换至其他页面也可以正常进行自定义分享. 这两天在做微信自定义分享的 ...

  2. Magicodes.WeiChat——自定义knockoutjs template、component实现微信自定义菜单

    本人一向比较喜欢折腾,玩了这么久的knockoutjs,总觉得不够劲,于是又开始准备折腾自己了. 最近在完善Magicodes.WeiChat微信开发框架时,发现之前做的自定义菜单这块太不给力了,而各 ...

  3. 微信自定义菜单说php json_encode不转义中文汉字的方法

    http://blog.csdn.net/qmhball/article/details/45690017 最近在开发微信自定义菜单. 接口比较简单,就是按微信要求的格式post一段json数据过去就 ...

  4. 微信自定义菜单url默认80端口问题解决

    微信自定义菜单url默认80端口的,但是有些服务器上可能配置了多个tomcat.或者是刚好你服务器上80端口被占用了.在这样的情况下,我们可以通过如下方式解决: 首先安装apache,关于apache ...

  5. ThinkPHP5集成JS-SDK实现微信自定义分享功能

    最近开发一个项目,需要将链接分享给好友时能够自定义标题.简介和logo,现将ThinkPHP5集成JS-SDK实现微信自定义分享功能的过程整理成文. 一.准备工作 1.认证的公众号 不管是订阅号还是服 ...

  6. H5微信自定义分享链接(设置标题+简介+图片)

    起源:最近公司在做招募广告的html5页面,然后做出来后,产品提出一个问题,需要分享出去的链接是卡片形式,内容也要自己定义,这下就难到我了,因为是第一次遇到这种需求,果断百度,然而,我就像大家一样,看 ...

  7. 生成二维码、微信自定义分享到朋友圈、ipa不从应用商店安装

    生成二维码网址:http://www.liantu.com/ 微信自定义分享到朋友圈:http://www.cnblogs.com/memor-y/p/6728179.html ipa不从应用商店安装 ...

  8. java 微信自定义菜单 java微信接口开发 公众平台 SSM redis shiro 多数据源

    A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生成 ...

  9. java SSM 框架 微信自定义菜单 快递接口 SpringMVC mybatis redis shiro ehcache websocket

    A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生成 ...

随机推荐

  1. 什么是Mbps、Kbps、bps、kb、mb及其换算和区别

    Mbps 即 Milionbit pro second(百万位每秒): Kbps 即 Kilobit pro second(千位每秒): bps 即 bit pro second(位每秒): 速度单位 ...

  2. Myeclipse以及Genymotion工具的使用以及java后台开发小结

    1. 服务端的Servlet程序修改并保存后,需要重启tomcat服务器才能使其修改有效.重新部署web项目是没有什么卵用的. 2. servers选项卡若是移走了看不到,在window-show v ...

  3. JS驗證兩位小數

    function SizeCheck(Textdiv) {                var fg = true;                str = $("#" + T ...

  4. C++中的左值与右值(二)

    以前以为自己把左值和右值已经弄清楚了,果然发现自己还是太年轻了,下面的这些东西是自己通过在网上拾人牙慧,加上自己的理解写的. 1. 2. 怎么区分左值和右值:知乎大神@顾露的回答. 3. 我们不能直接 ...

  5. information_schema系列四(跟踪,列约束,表和列)

    这个系列的文章主要是为了能够让自己了解MySQL5.7的一些系统表,统一做一下备注和使用,也希望分享出来让大家能够有一点点的受益. 1:KEY_COLUMN_USAGE 按照官方的解释,这个表描述的是 ...

  6. OC 框架组织架构图

  7. DIOCP之注册编码解码器与ClientContext

    FTcpServer.registerCoderClass(TIOCPStreamDecoder, TIOCPStreamEncoder);//注册编码器与解码器 FTcpServer.registe ...

  8. poj 2823 Sliding Window (单调队列入门)

    /***************************************************************** 题目: Sliding Window(poj 2823) 链接: ...

  9. c语言检测文件是否存在int __cdecl access(const char *, int);

    最近写代码,遇到很多地方需要判断文件是否存在的.网上的方法也是千奇百怪,“百家争鸣”. fopen方式打开的比较多见,也有其他各种方式判断文件是否存在的,由于其他方法与本文无关,所以不打算提及. 笔者 ...

  10. DP总结

    最长回文子序列 int lpsDp(char * str,int n){ int dp[n][n], tmp; memset(dp,0,sizeof(dp)); for(int i=0; i<n ...