微信公众号自定义菜单
参见微信公众平台接口文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432
保存到微信代码
[HttpPost("SaveToWx")]
[AbpAllowAnonymous]//无需登录就可以调用
[AbpAllowAnonymous]
public async Task<ApiResult> SaveToWx()
{
ApiResult apiResult = new ApiResult();
try
{
string AppId = await SettingManager.GetSettingValueAsync(AppSettings.WeChatManagement.AppId);
string AppSecret = await SettingManager.GetSettingValueAsync(AppSettings.WeChatManagement.AppSecret);
// string Token = await SettingManager.GetSettingValueAsync(AppSettings.WeChatManagement.Token);
if (string.IsNullOrEmpty(AppId) || string.IsNullOrEmpty(AppSecret))
{
apiResult.ReturnCode = ReturnCodes.不合法的APPID;
apiResult.Message = "服务号配置存在问题";
}
MenuInfo menuInfo = new MenuInfo();
menuInfo.Button = new List<MenuButtonBase>();
List<MenuSettingInfo> initMenus = GetInitMenus();
List < MenuButtonBase> menu = new List<MenuButtonBase>();
foreach (MenuSettingInfo current in initMenus)
{
string url = _httpContextAccessor.HttpContext.Request.Host.ToString();
switch ((BindType)current.Bind)
{
case BindType.None://不绑定
case BindType.Member:
current.Url = url;
break;
case BindType.Card:
current.Url = "http://" + url + "/Wap/Card";
break;
case BindType.Store:
current.Url = "http://" + url + "/Wap/Store";
break;
case BindType.Message:
current.Url = "http://" + url + "/Wap/Message";
break;
case BindType.Key:
current.Url = current.ReplyId.ToString();
break;
default:
break;
}
if (current.Chilren == null || current.Chilren.Count == 0)
{
MenuButtonBase menuButtonBase = this.BuildMenu(current);
menuButtonBase.Name = current.Name;
menuButtonBase.Key = current.Id.ToString();
menuButtonBase.AppId = AppId;
menuButtonBase.Type = MenuButtonTypes.view;
menu.Add(menuButtonBase);
}
else
{
//一级菜单
MenuButtonBase buttonBase = new MenuButtonBase();
buttonBase.MediaId = "0";
buttonBase.SubButton = new List<MenuButtonBase>();
buttonBase.NewsInfo = new List<NewsInfo>();
buttonBase.Value= current.Id.ToString();
buttonBase.Name = current.Name;
buttonBase.Key = current.Id.ToString();
buttonBase.Url = current.Url;
buttonBase.Pagepath = current.Url;
buttonBase.AppId = AppId;
if (current.Type == "view")
{
buttonBase.Type = MenuButtonTypes.view;
}
else if (current.Type == "click")
{
buttonBase.Type = MenuButtonTypes.click;
}
//二级菜单
foreach (MenuSettingInfo current2 in current.Chilren)
{
MenuButtonBase menuButtonBase = this.BuildMenu(current2);
menuButtonBase.AppId = AppId;
buttonBase.SubButton.Add(menuButtonBase);//子菜单(二级菜单数组,个数应为1~5个)
}
menu.Add(buttonBase);
}
}
menuInfo.Button.AddRange(menu);
string json = JsonConvert.SerializeObject(menuInfo);
MenuApi menuApi = new MenuApi();
MenuInfo newmenuInfo= JsonConvert.DeserializeObject<MenuInfo>(json, new MenuButtonsCustomConverter());
ApiResult text = menuApi.Create(newmenuInfo);
// var url = GetAccessApiUrl("create", "menu");
// ApiResult text= Post<ApiResult>(url, newmenuInfo);
// ApiResult text = menuApi.Create(menuInfo);
// ApiResult text = menuApi.CreateByJson(json);
if (text.IsSuccess())
{
apiResult.ReturnCode = ReturnCodes.请求成功;
apiResult.Message = "成功的把自定义菜单保存到了微信";
}
else
{
apiResult.ReturnCode = ReturnCodes.解析JSON_XML内容错误;
apiResult.Message = text.DetailResult;
}
}
catch (Exception ex)
{
throw ex;
//apiResult.Message = string.Concat(new string[]
// {
// ex.Message,
// "---",
// Token,
// "---",
// AppId,
// "---",
// AppSecret
// });
}
return apiResult;
}
private List<MenuSettingInfo> GetInitMenus()
{
var MenuList = new List<MenuSettingInfo>();
var query = _menu_Repository.GetAllList(m => m.ParentMenuId == 0);
foreach (Media_MenuSetting_Info current in query)
{
var topMenus = new MenuSettingInfo();
topMenus = ConvertMenuSettingInfo(current);
var List = new List<MenuSettingInfo>();
var Chilrenlist = _menu_Repository.GetAllList(m => m.ParentMenuId == current.Id);
foreach (Media_MenuSetting_Info chilren in Chilrenlist)
{
var Chilren = new MenuSettingInfo();
Chilren = ConvertMenuSettingInfo(chilren);
List.Add(Chilren);
}
topMenus.Chilren = List;
if (topMenus.Chilren == null)
{
topMenus.Chilren = new List<MenuSettingInfo>();
}
MenuList.Add(topMenus);
}
return MenuList;
}
private MenuButtonBase BuildMenu(MenuSettingInfo menu)
{
MenuButtonTypes menuButtonTypes = 0;
if (menu.Type == "view")
{
menuButtonTypes = MenuButtonTypes.view;
}
else if (menu.Type == "click")
{
menuButtonTypes = MenuButtonTypes.click;
}
switch ((BindType)menu.Bind)
{
case BindType.None:
case BindType.Key:
return new MenuButtonBase
{
Name = menu.Name,
MediaId = "0",
Type = menuButtonTypes,
SubButton = new List<MenuButtonBase>(),
NewsInfo=new List<NewsInfo> (),
Key = menu.Id.ToString(),
Value = menu.Id.ToString(),
Url = menu.Url,
Pagepath = menu.Url
};
case BindType.Member:
return new MenuButtonBase
{
Name = menu.Name,
MediaId = "",
Type = menuButtonTypes,
SubButton = new List<MenuButtonBase>(),
NewsInfo = new List<NewsInfo>(),
Key = menu.Id.ToString(),
Value = menu.Id.ToString(),
Url = menu.Url,
Pagepath = menu.Url
};
case BindType.Card:
return new MenuButtonBase
{
Name = menu.Name,
MediaId = "",
Type = menuButtonTypes,
SubButton = new List<MenuButtonBase>(),
NewsInfo = new List<NewsInfo>(),
Key = menu.Id.ToString(),
Value = menu.Id.ToString(),
Url = menu.Url,
Pagepath = menu.Url
};
case BindType.Store:
return new MenuButtonBase
{
Name = menu.Name,
MediaId = "",
Type = menuButtonTypes,
SubButton = new List<MenuButtonBase>(),
NewsInfo = new List<NewsInfo>(),
Key = menu.Id.ToString(),
Value = menu.Id.ToString(),
Url = menu.Url,
Pagepath = menu.Url
};
case BindType.Message:
return new MenuButtonBase
{
Name = menu.Name,
MediaId = "",
Type = menuButtonTypes,
SubButton=new List<MenuButtonBase> (),
NewsInfo = new List<NewsInfo>(),
Key = menu.Id.ToString(),
Value = menu.Id.ToString(),
Url = menu.Url,
Pagepath = menu.Url
};
}
return new MenuButtonBase
{
Name = menu.Name,
Key = "None",
MediaId = "0",
Type = menuButtonTypes,
SubButton = new List<MenuButtonBase>(),
NewsInfo = new List<NewsInfo>(),
Value = menu.Id.ToString(),
Url = menu.Url,
Pagepath = menu.Url
};
}
保存到微信时需要注意的问题
一、测试自定义菜单接口时中文菜单名显示为null
设置的中文菜单名,中文未经过编码和解码过程,设置的中文菜单名在最后的微信服务器返回的json格式数据中显示为null。
解决办法:将中文先用unecode方法编码,最后再将菜单数组用undecode解码,再传给微信服务器。方法最上面加上header("content-type=text/html;charset=utf-8"),编码方式必须是utf-8,才能在微信公众平台在线测试接口。
二、自定义菜单中的菜单类型type="click"(或"view")必须小写
大写CLICK和VIEW会出错,错误提示:must use utf-8 charse。
三、自定义菜单类型type为view时,设置的url必须有值,设置为"",会出错
url设置为"",或url不正确,修改后的菜单名都不会得到更新。url的格式必须为"http://" + url + "/。。。/。。。"的格式
四、自定义菜单时,更改菜单名之后微信公众号上得不到及时更新
菜单未得到更新的情况:一是,写的菜单数组代码语法错误,代码有问题;可先测试一下接口,测试代码见我写的微信公众号开发之自定义菜单
二是,有的时候我们在多个环境中去调用access_token,会出现改变了文件却显示的是前一个文件中的菜单的情况;
五、
下面引入一位博友写的总结:
如果有第二地方也请求同一个token的话,那么第一个token会在5分钟之内过期。这也就说明了,为什么在我搭建好第二个环境的时候,老环境就出现了invalid credential, access_token is invalid or not latest hint 这种问题。因为这两个环境用的是同 一个AppID和AppSecret来取得的access_token,而这个access_token的取得并不是在服务器启动的时候,而且是在需要调用接口的画面初期化的时候去取得的。
我的理解是在多个环境中去调用access_token,当在另一个环境中调用access_token时,在一定时间内前一个环境中获取的access_token还未过期,所以在这段时间中显示的是前一个环境中设置的菜单。
access_token的获 取和服务器什么时候启动没有直接的关系,有直接关系的是初次调用接口是什么时候。
注:修改回复消息之后可以在公众号立即看到更新,但是修改菜单之后,公众号不会马上更新菜单,修改菜单代码之后,最好是取消公众号关注,然后再刷新测试号管理(以测试号为例)界面,再扫描二维码重新关注。

浙公网安备 33010602011771号