ASP.NET实现微信功能(1)(创建菜单,验证,给菜单添加事件)
LZ实在 不知道怎么起名字了,索性就取了这个名字,开始吧,说实在的,想给自己的平常的学习做一个总结,总是忘了总结。也只能给工作做一个总结了。
我打算用2篇文章来写,第一篇是关于订阅号的,就是这个号,另一篇是关于服务号的,到时候会介绍更多的东西,闲话不多,开始吧。
首先,我们需要一个能创建自定义菜单的订阅号,微信的个人认证是不可能获得订阅号的,只有企业或者政府机构认证才可以申请。
首先我还是从创建菜单说起吧,创建菜单我们不需要进行服务器配置,我们只需要appid和appsecret就行了,这个东西从微信公众平台的后台进去,然后找到开发者中心。
如下图所示。
只有申请了认证的才可以活动APPID 和APPSECRET,然后我们来创建菜单吧,下面的APPID和APPSECRET将会用XXX代替,带来的不便请谅解。
首先我们需要在前台创建一个按钮,一个创建菜单的按钮,一个删除菜单的按钮。
这次因为赶时间,我把菜单给写死了,下一篇文章写的时候,我会展示给大家一个灵活的菜单。
下面是前台,就2个按钮,一个删除,一个添加修改按钮,针对订阅号。
1 2 3 4 5 6 | <div class = "btn" > <div><asp:Button runat= "server" ID= "Add_Menu" OnClick= "Add_Menu_Click" Text= "创建(修改)菜单" /></div> <div><asp:Button runat= "server" ID= "Del_Menu" OnClick= "Del_Menu_Click" Text= "删除菜单" OnClientClick= "return confirm('是否删除菜单?')" /> </div> |
后台代码我就一个一个解释吧。第一个为APPID和APPSECRET,
公众号有调用接口,第一个为授权,第二个为创建菜单的接口,第三个为删除菜单的接口。
如果有不懂的,可以参考官方API:http://mp.weixin.qq.com/wiki/index.php?title=%E9%A6%96%E9%A1%B5
1 2 3 4 5 6 7 8 | /*以下为订阅号*/ static string appId = "XXXX" ; //公众号的appId static string appSecret = "XXXXX" ; //公众号的appSecret /*以下为公共接口调用URL*/ static string appUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" ; static string postUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" ; static string postDelUrl = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=" ; //删除菜单的URL |
我们要APPID和APPSECRET有什么作用呢?答案就是,我们要获得接入微信的一个凭证,而这个凭证,就是ACCESS_TOKEN.
下面是获得ACCESS_TOKEN的代码。具体的代码我也不是太懂,反正就是一个转化,有兴趣的可以自己测试一下,我就不多累赘了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //获得ACCESS_TOKEN,通过appid和app_secect获得(订阅号) public static string GetAccessToken() { WebClient webClient = new WebClient(); Byte[] bytes = webClient.DownloadData( string .Format( "{0}&appid={1}&secret={2}" , appUrl, appId, appSecret)); string result = Encoding.GetEncoding( "utf-8" ).GetString(bytes); //JObject jObj = JObject.Parse(result); //JToken token = jObj["access_token"]; //return token.ToString().Substring(1, token.ToString().Length - 2); string [] temp = result.Split( ',' ); string [] tp = temp[0].Split( ':' ); return tp[1].ToString().Replace( '"' , ' ' ).Trim().ToString(); } |
下面是删除菜单的代码,主要 也是通过HTTP请求然后把请求转化成流的形式,然后把流读出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | private void DelMenu( string url) { if ( string .IsNullOrEmpty(url)) { throw new ArgumentNullException( "url" ); } Encoding encoding = Encoding.UTF8; HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest; request.Method = "GET" ; HttpWebResponse response = request.GetResponse() as HttpWebResponse; Stream instream = response.GetResponseStream(); StreamReader sr = new StreamReader(instream, encoding); string content = sr.ReadToEnd(); } |
当然上面的删除是部分代码:
我们当然删除是通过微信给的URL去删除啦。
//删除菜单(订阅号) protected void Del_Menu_Click(object sender, EventArgs e) { DelMenu(postDelUrl + GetAccessToken()); }
下面我们来说一下怎么创建菜单,微信官方已经给我们提供了JSON的示例,如下图:
我也附上我自己的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | //创建微信菜单JSON字符串 public string GetWXMenuStr() { string weixin1 = "" ; weixin1 += "{\n" ; weixin1 += "\"button\":[\n" ; weixin1 += "{\n" ; // weixin1 += "\"type\":\"click\",\n"; //第一个菜单 weixin1 += "\"name\":\"公共信息\",\n" ; weixin1 += "\"sub_button\":[\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"click\",\n" ; weixin1 += "\"name\":\"通知公告\",\n" ; weixin1 += "\"key\":\"11\"\n" ; weixin1 += "},\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"click\",\n" ; weixin1 += "\"name\":\"工作动态\",\n" ; weixin1 += "\"key\":\"12\"\n" ; weixin1 += "},\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"click\",\n" ; weixin1 += "\"name\":\"政策法规\",\n" ; weixin1 += "\"key\":\"13\"\n" ; weixin1 += "},\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"click\",\n" ; weixin1 += "\"name\":\"经济视野\",\n" ; weixin1 += "\"key\":\"14\"\n" ; weixin1 += "},\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"click\",\n" ; weixin1 += "\"name\":\"专题报道\",\n" ; weixin1 += "\"key\":\"15\"\n" ; weixin1 += "}]\n" ; weixin1 += "},\n" ; //第二个菜单 weixin1 += "{\n" ; //weixin1 += "\"type\":\"click\",\n"; weixin1 += "\"name\":\"公共服务\",\n" ; weixin1 += "\"sub_button\":[\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"click\",\n" ; weixin1 += "\"name\":\"企业之窗\",\n" ; weixin1 += "\"key\":\"21\"\n" ; weixin1 += "},\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"click\",\n" ; weixin1 += "\"name\":\"金融服务\",\n" ; weixin1 += "\"key\":\"22\"\n" ; weixin1 += "},\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"click\",\n" ; weixin1 += "\"name\":\"创业指导\",\n" ; weixin1 += "\"key\":\"23\"\n" ; weixin1 += "},\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"click\",\n" ; weixin1 += "\"name\":\"管理服务\",\n" ; weixin1 += "\"key\":\"24\"\n" ; weixin1 += "},\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"click\",\n" ; weixin1 += "\"name\":\"法律服务\",\n" ; weixin1 += "\"key\":\"25\"\n" ; weixin1 += "}]\n" ; weixin1 += "},\n" ; //第三个菜单(view类型的) weixin1 += "{\n" ; weixin1 += "\"name\":\"互动交流\",\n" ; weixin1 += "\"sub_button\":[\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"view\",\n" ; weixin1 += "\"name\":\"服务信箱\",\n" ; weixin1 += "\"url\":\"http://www.baidu.com\"\n" ; weixin1 += "},\n" ; weixin1 += "{\n" ; weixin1 += "\"type\":\"view\",\n" ; weixin1 += "\"name\":\"网上咨询\",\n" ; weixin1 += "\"url\":\"http://www.soso.com\"\n" ; weixin1 += "}]\n" ; weixin1 += "}\n" ; weixin1 += "}]\n" ; weixin1 += "}\n" ; return weixin1; } |
需要注意的是,微信一级菜单只允许取3个,二级菜单最多5个,请大家注意。
下面是创建菜单的代码,说实在的我挺失败的,也是有点不求甚解,先放上来吧反正:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | /// <summary> /// /// </summary> /// <param name="url"></param> /// <param name="postData"></param> private void PostMenuData( string url, string postData) { Stream outstream = null ; Stream instream = null ; StreamReader sr = null ; HttpWebResponse response = null ; HttpWebRequest request = null ; Encoding encoding = Encoding.UTF8; byte [] data = encoding.GetBytes(postData); // 准备请求... try { // 设置参数 request = WebRequest.Create(url) as HttpWebRequest; CookieContainer cookieContainer = new CookieContainer(); request.CookieContainer = cookieContainer; request.AllowAutoRedirect = true ; request.Method = "POST" ; request.ContentType = "application/x-www-form-urlencoded" ; request.ContentLength = data.Length; outstream = request.GetRequestStream(); outstream.Write(data, 0, data.Length); outstream.Close(); //发送请求并获取相应回应数据 response = request.GetResponse() as HttpWebResponse; //直到request.GetResponse()程序才开始向目标网页发送Post请求 instream = response.GetResponseStream(); sr = new StreamReader(instream, encoding); //返回结果网页(html)代码 string content = sr.ReadToEnd(); string err = string .Empty; // return content; } catch (Exception ex) { string err = ex.Message; //return string.Empty; } } |
下面点击添加按钮就执行如下方法:
1 2 3 4 5 6 7 8 9 10 | /// <summary> /// 创建自定义菜单(订阅号) /// </summary> private void CreateWxMenu() { string weixin1=GetWXMenuStr(); PostMenuData(postUrl + GetAccessToken(), weixin1); } |
好了,到此我们的菜单就创建好了,下面来介绍一下如何给菜单添加事件,如果是VIEW类型的,只要给一个URL就行了,上面有写到,
如果是CLICK类型的,就要指定KEY,KEY是唯一的一个标识符。
不过在这之前,我们先要知道一点,微信必须要先有一个转发的文件,我们的微信和我们的网站的通信,就通过这个转发URL,打开开发者中心:
我们首先要启用服务器配置,把自己网站的那个转发文件配置进去,注意:微信只支持80端口,如果有一个订阅号,另一个服务号要用到的话,怎么办呢,写2个转发文件就够了。
废话不多说,如果你有条件的话,建议你去买一个云服务器做测试,我做测试是给公司买了一个云服务器的,坑死人,连报销都不给。
我这次是用的一个ASHX文件做转发,当然你也可以用ASPX文件,反正看自己喜好,那么我们还是来分析一下ASHX文件吧。
我们首先要知道,第一次进行验证的时候,肯定是要验证这个文件的,那么就要一个TOKEN,这个TOKEN只用验证一次,验证完了就没用了。
TOKEN是我们自己设置的,但是文件里的TOKEN要和服务器配置里的一样哦,而且验证通过了要启动服务器,这是很容易疏忽的,我都被忽悠了几次,哈哈。
因为第一次验证的时候的请求是GET的,如果验证通过了是POST的,所以我们可以在ProcessRequest方法这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public void ProcessRequest (HttpContext context) { //context.Response.ContentType = "text/plain"; //context.Response.Write("Hello World"); //以下代码只要用一次就行了,通过了就不必 再用了 string postString = string .Empty; if (HttpContext.Current.Request.HttpMethod.ToUpper() == "GET" ) { string token = "123" ; //填写微信服务端的TOKEN if ( string .IsNullOrEmpty(token)) { return ; } string echoString = HttpContext.Current.Request.QueryString[ "echoStr" ]; string signature = HttpContext.Current.Request.QueryString[ "signature" ]; string timestamp = HttpContext.Current.Request.QueryString[ "timestamp" ]; string nonce = HttpContext.Current.Request.QueryString[ "nonce" ]; if (! string .IsNullOrEmpty(echoString)) { HttpContext.Current.Response.Write(echoString); HttpContext.Current.Response.End(); } } else if (HttpContext.Current.Request.HttpMethod.ToUpper() == "POST" ) { using (Stream stream = HttpContext.Current.Request.InputStream) { Byte[] postBytes = new Byte[stream.Length]; stream.Read(postBytes, 0, (Int32)stream.Length); postString = Encoding.UTF8.GetString(postBytes); Handle(postString); } } } |
下面我们主要是以HANDEL方法为主线来看这个文件。
1 2 3 4 5 6 7 8 9 10 11 12 | /// <summary> /// 处理信息并应答 /// </summary> private void Handle( string postStr) { //messageHelp help = new messageHelp(); string responseContent = ReturnMessage(postStr); HttpContext.Current.Response.ContentEncoding = Encoding.UTF8; HttpContext.Current.Response.Write(responseContent); } |
其他的都可以无视,我们的重点主要是在这个ReturnMessage方法上面,就是说,微信服务端给我们传了一串字符串,我们的目的,就是把这串字符串解密。
试想一下,如果我们点击了微信菜单上了某一个内容,然后他会传字符串到我们这个ASHX文件中,如下,有可能是EVENT,事件,点击事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //返回消息 public string ReturnMessage( string postStr) { string responseContent = "" ; XmlDocument xmldoc = new XmlDocument(); xmldoc.Load( new System.IO.MemoryStream(System.Text.Encoding.GetEncoding( "GB2312" ).GetBytes(postStr))); XmlNode MsgType = xmldoc.SelectSingleNode( "/xml/MsgType" ); if (MsgType != null ) { switch (MsgType.InnerText) { case "event" : responseContent = EventHandle(xmldoc); //事件处理 break ; case "text" : responseContent = TextHandle(xmldoc); //接受文本消息处理 break ; default : break ; } } return responseContent; } |
如果是事件的话,那么我们就需要返回这个事件处理之后的字符串,我们这次重点来看这个EvenHandler.关键是看前面的,我后面写了一些业务代码,
大家可以无视,都是关于ID的。大家想一下,我们创建菜单的时候,是不是指定过KEY,这里就可以用到哦,其中下面的ResponseContent也是返回字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | public string EventHandle(XmlDocument xmldoc) { string responseContent = "" ; XmlNode Event = xmldoc.SelectSingleNode( "/xml/Event" ); XmlNode EventKey = xmldoc.SelectSingleNode( "/xml/EventKey" ); XmlNode ToUserName = xmldoc.SelectSingleNode( "/xml/ToUserName" ); XmlNode FromUserName = xmldoc.SelectSingleNode( "/xml/FromUserName" ); if (Event!= null ) { //菜单单击事件 if (Event.InnerText.Equals( "CLICK" )) { if (EventKey.InnerText.Equals( "11" )) //click_one { string typeid = "d19cace294e745fa8e38a8a1593703" ; //类型ID:通知公告 responseContent = GetStrJsonDynamiclly(xmldoc, typeid); } else if (EventKey.InnerText.Equals( "12" )) //click_two { string typeid= "dd2321c73d3946709c49f3fcb16d78" ; //类型ID:工作动态 responseContent = GetStrJsonDynamiclly(xmldoc, typeid); } else if (EventKey.InnerText.Equals( "13" )) //click_three { string typeid = "3fbac2fbc0d44367a5358c80529e05" ; //类型ID:政策法规 responseContent = GetStrJsonDynamiclly(xmldoc, typeid); } else if (EventKey.InnerText.Equals( "14" )) //click_three { string typeid = "9c1fbf93601a4285a77614e9b1261f" ; //类型ID:经济视野 responseContent = GetStrJsonDynamiclly(xmldoc, typeid); } else if (EventKey.InnerText.Equals( "15" )) //click_three { string typeid = "14f0d6b7c4204035b82e3e83e81e59" ; //类型ID:专题报道 responseContent = GetStrJsonDynamiclly(xmldoc, typeid); } else if (EventKey.InnerText.Equals( "21" )) //click_three { string typeid = "ae6fc69cabfa4d059d5ba17bde1faf" ; //类型ID:企业之窗 responseContent = GetStrJsonDynamiclly(xmldoc, typeid); } else if (EventKey.InnerText.Equals( "22" )) //click_three { string typeid = "fb86dbe3d7ed4df4850f7f24ce128b" ; //类型ID:金融服务 responseContent = GetStrJsonDynamiclly(xmldoc, typeid); } else if (EventKey.InnerText.Equals( "23" )) //click_three { string typeid = "f961aa8accba454f840355f67cb492" ; //类型ID:创业指导 responseContent = GetStrJsonDynamiclly(xmldoc, typeid); } else if (EventKey.InnerText.Equals( "24" )) //click_three { string typeid = "0e5033c616214fe484cdff377741b3" ; //类型ID:管理服务 responseContent = GetStrJsonDynamiclly(xmldoc, typeid); } else if (EventKey.InnerText.Equals( "25" )) //click_three { string typeid = "6f22bdf17708471e840a653931d4ec" ; //类型ID:法律服务 responseContent = GetStrJsonDynamiclly(xmldoc, typeid); } } } return responseContent; } |
上面的是GetStrJsonDynamiclly方法的掠影,主要是业务逻辑,大家可以参考下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | //获得要上传到服务器的字符串,其中的typeid为动态值 public string GetStrJsonDynamiclly(XmlDocument xmldoc, string typeid) { string responseContent = "" ; XmlNode Event = xmldoc.SelectSingleNode( "/xml/Event" ); XmlNode EventKey = xmldoc.SelectSingleNode( "/xml/EventKey" ); XmlNode ToUserName = xmldoc.SelectSingleNode( "/xml/ToUserName" ); XmlNode FromUserName = xmldoc.SelectSingleNode( "/xml/FromUserName" ); string picUrl = "" ; //图片地址; string basicPicUrl = "http://XXX/XXX/pic/" ; //创业指导 if (typeid == "f961aa8accba454f840355f67cb492" ) { picUrl = basicPicUrl + "cyzd.jpg" ; } //法律服务 else if (typeid == "6f22bdf17708471e840a653931d4ec" ) { picUrl = basicPicUrl + "flfw.jpg" ; } string strJson = string .Format(ReplyType.Message_News_Main, FromUserName.InnerText, ToUserName.InnerText, DateTime.Now.Ticks, "6" , string .Format(ReplyType.Message_News_Item, GetDataByIndex(1, typeid), "" , picUrl, basicIP + GetArticleParams(1, typeid)[0]) + string .Format(ReplyType.Message_News_Item, GetDataByIndex(2, typeid), "" , "" , basicIP + GetArticleParams(2, typeid)[0]) + string .Format(ReplyType.Message_News_Item, GetDataByIndex(3, typeid), "" , "" , basicIP + GetArticleParams(3, typeid)[0]) + string .Format(ReplyType.Message_News_Item, GetDataByIndex(4, typeid), "" , "" , basicIP + GetArticleParams(4, typeid)[0]) + string .Format(ReplyType.Message_News_Item, GetDataByIndex(5, typeid), "" , "" , basicIP + GetArticleParams(5, typeid)[0]) + string .Format(ReplyType.Message_News_Item, GetDataByIndex(6, typeid), "" , "" , basicIP + GetArticleParams(6, typeid)[0])); return strJson; } |
总之,上面的代码只有一个目的,就是返回字符串,比如点击微信的菜单,可以返回6篇文章,其中可以给文章指定背景图片。其中的方法是获取URL和文章标题的,具体就不累赘了,根据自己的业务具体来办。
另外还给大家提供3个工具方法:
大家根据需求自由使用,具体的完全的代码,我就不给哦。

//写入日志 public void WriteLog(string text) { StreamWriter sw = new StreamWriter(HttpContext.Current.Server.MapPath(".") + "\\log.txt", true); sw.WriteLine(text); sw.Close();//写入 } } //回复类型 public class ReplyType { /// <summary> /// 普通文本消息 /// </summary> public static string Message_Text { get { return @"<xml> <ToUserName><![CDATA[{0}]]></ToUserName> <FromUserName><![CDATA[{1}]]></FromUserName> <CreateTime>{2}</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[{3}]]></Content> </xml>"; } } /// <summary> /// 图文消息主体 /// </summary> public static string Message_News_Main { get { return @"<xml> <ToUserName><![CDATA[{0}]]></ToUserName> <FromUserName><![CDATA[{1}]]></FromUserName> <CreateTime>{2}</CreateTime> <MsgType><![CDATA[news]]></MsgType> <ArticleCount>{3}</ArticleCount> <Articles> {4} </Articles> </xml> "; } } /// <summary> /// 图文消息项 /// </summary> public static string Message_News_Item { get { return @"<item> <Title><![CDATA[{0}]]></Title> <Description><![CDATA[{1}]]></Description> <PicUrl><![CDATA[{2}]]></PicUrl> <Url><![CDATA[{3}]]></Url> </item>"; } }
好了,这一篇因为时间和技术原因就介绍到这里了,过1,2个星期我将会把灵活在后台配置菜单以及服务号C#后台推送图文消息跟大家讲清楚。

出处:http://www.cnblogs.com/kmsfan
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
欢迎大家加入KMSFan之家,以及访问我的优酷空间!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步