C#微信公众号开发入门教程
首先打开开发文档:
微信公众号开发者文档:http://mp.weixin.qq.com/wiki/home/index.html
一、创建测试账号
可以先申请一个开发者测试账号
用自己微信扫描后即可获得测试账号:
就有了appId 和 appsecret了,微信号在右上角。
二、获取access_token (这个access_token是通过appID 和 appsecret来生成的,只要是向微信服务器发送请求都需要带上这个access_token。)
打开 微信公众平台接口调试工具
程序生成access_token,解决2小时失效的问题,2小时候会重新生成一个。
access_token帮助类:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Xml.Linq; namespace YangYiEcormerce.WeChat.Web.Common { /// <summary> /// AccessToken帮助类 /// </summary> public class AccessTokenHelp { //填写自己微信的秘钥 private static string appId = System.Configuration.ConfigurationManager.AppSettings["WeChatAppId"]; private static string appSecret = System.Configuration.ConfigurationManager.AppSettings["WeChatAppSecret"]; private static DateTime GetAccessToken_Time; /// <summary> /// 过期时间为7200秒 /// </summary> private static int Expires_Period = 7200; /// <summary> /// /// </summary> private static string mAccessToken; /// <summary> /// /// </summary> public static string AccessToken { get { //如果为空,或者过期,需要重新获取 if (string.IsNullOrEmpty(mAccessToken) || HasExpired()) { //获取 mAccessToken = GetAccessToken(appId, appSecret); } return mAccessToken; } } /// <summary> /// /// </summary> /// <param name="appId"></param> /// <param name="appSecret"></param> /// <returns></returns> private static string GetAccessToken(string appId, string appSecret) { string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appId, appSecret); string result = HttpUtility.GetData(url); XDocument doc = CommonHelp.ParseJsonToXML(result, "root"); XElement root = doc.Root; if (root != null) { XElement access_token = root.Element("access_token"); if (access_token != null) { GetAccessToken_Time = DateTime.Now; if (root.Element("expires_in") != null) { Expires_Period = int.Parse(root.Element("expires_in").Value); } return access_token.Value; } else { GetAccessToken_Time = DateTime.MinValue; } } return null; } /// <summary> /// 判断Access_token是否过期 /// </summary> /// <returns>bool</returns> private static bool HasExpired() { if (GetAccessToken_Time != null) { //过期时间,允许有一定的误差,一分钟。获取时间消耗 if (DateTime.Now > GetAccessToken_Time.AddSeconds(Expires_Period).AddSeconds(-60)) { return true; } } return false; } } }
HttpUtility类:
/// <summary> ///Http帮助类 /// </summary> public class HttpUtility { /// <summary> /// 发送请求 /// </summary> /// <param name="url">Url地址</param> /// <param name="data">数据</param> public static string SendHttpRequest(string url, string data) { return SendPostHttpRequest(url, "application/x-www-form-urlencoded", data); } /// <summary> /// /// </summary> /// <param name="url"></param> /// <returns></returns> public static string GetData(string url) { return SendGetHttpRequest(url, "application/x-www-form-urlencoded"); } /// <summary> /// 发送请求 /// </summary> /// <param name="url">Url地址</param> /// <param name="method">方法(post或get)</param> /// <param name="method">数据类型</param> /// <param name="requestData">数据</param> public static string SendPostHttpRequest(string url, string contentType, string requestData) { WebRequest request = (WebRequest)HttpWebRequest.Create(url); request.Method = "POST"; byte[] postBytes = null; request.ContentType = contentType; postBytes = Encoding.UTF8.GetBytes(requestData); request.ContentLength = postBytes.Length; using (Stream outstream = request.GetRequestStream()) { outstream.Write(postBytes, 0, postBytes.Length); } string result = string.Empty; using (WebResponse response = request.GetResponse()) { if (response != null) { using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { result = reader.ReadToEnd(); } } } } return result; } /// <summary> /// 发送请求 /// </summary> /// <param name="url">Url地址</param> /// <param name="method">方法(post或get)</param> /// <param name="method">数据类型</param> /// <param name="requestData">数据</param> public static string SendGetHttpRequest(string url, string contentType) { WebRequest request = (WebRequest)HttpWebRequest.Create(url); request.Method = "GET"; request.ContentType = contentType; string result = string.Empty; using (WebResponse response = request.GetResponse()) { if (response != null) { using (Stream stream = response.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { result = reader.ReadToEnd(); } } } } return result; } }
ParseJsonToXML方法: (需要引入Newtonsoft.Json.dll)
/// <summary> /// 将Json转化为XML /// </summary> /// <param name="json"></param> /// <param name="rootName"></param> /// <returns></returns> public static XDocument ParseJsonToXML(string json, string rootName) { return JsonConvert.DeserializeXNode(json, rootName); }
程序调用获取access_token:
string access_token = Common.AccessTokenHelp.AccessToken;
三、创建菜单:
还是使用调试工具来创建:
JSON格式菜单内容:下面创建的都是一级菜单,更多详细的菜单创建,参考说明文档>>
{ "button": [ { "name": "商城", "type": "view", "url": "http://shop.com" //点击菜单访问网址 }, { "name": "防伪扫描", "type": "scancode_push", "key": "FangweiScan" //点击调用微信二维码扫描,是网址直接访问,是文本则显示文本内容 }, { "name": "订单查询", "type": "click", "key": "OrderQuery" //点击出发click事件,向我们配置的API地址进行请求 } ] }
请求成功后,取消微信号关注并退出微信,重新进入关注,应该就可以看到添加好的文档了。
想删除重新创建菜单,调用菜单删除就可以了。
效果:
四、开发接口、处理文本和事件 (当用户使用微信发送消息或者单击菜单出发事件,就会想配置的API发送请求,API进行处理响应) 消息回复参考文档>>
接口是一个一般处理程序:
using System; using System.Collections.Generic; using System.Web; using System.IO; using System.Text; using System.Web.Security; using System.Xml; namespace weixin_api { /// <summary> /// interfaceTest 的摘要说明 /// </summary> public class interfaceTest : IHttpHandler { public void ProcessRequest(HttpContext param_context) { string postString = string.Empty; //用户发送消息或点击等事件一般都是POST过来,微信服务器向接口发送POST请求,根据请求我们进行处理反馈 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); } } else { //第一次配置接口地址的时候,微信服务器会向接口发送一个GET请求来验证你的接口地址 InterfaceTest(); } } /// <summary> /// 处理信息并应答 /// </summary> private void Handle(string postStr) { messageHelp help = new messageHelp(); string responseContent = help.ReturnMessage(postStr); HttpContext.Current.Response.ContentEncoding = Encoding.UTF8; HttpContext.Current.Response.Write(responseContent); } //成为开发者url测试,返回echoStr public void InterfaceTest() { string token = "token"; if (string.IsNullOrEmpty(token)) { return; } //微信服务器会将下面几个参数发送到接口,接口这边返回接收到的echoStr就说明验证通过, //主要为了防止别人盗用你的接口,我这边没做逻辑判断直接返回接收到的echoStr来通过验证 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(); } } public bool IsReusable { get { return false; } } } }
接受/发送消息帮助类
using System; using System.Collections.Generic; using System.Web; using System.IO; using System.Text; using System.Web.Security; using System.Xml; namespace weixin_api { /// <summary> /// 接受/发送消息帮助类 /// </summary> public class messageHelp { //返回消息 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; } //事件 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"); XmlNode ScanResult = xmldoc.SelectSingleNode("/xml/ScanCodeInfo/ScanResult"); if (Event != null) { //菜单单击事件 if (Event.InnerText.Equals("CLICK")) { if (EventKey.InnerText.Equals("OrderQuery"))//点击订单查询 这个OrderQuery就是菜单里面的key { responseContent = string.Format(ReplyType.Message_Text, FromUserName.InnerText, ToUserName.InnerText, DateTime.Now.Ticks, "正在开发中,敬请期待!"); } } else if (Event.InnerText.Equals("scancode_waitmsg")) //扫码推事件且弹出“消息接收中”提示框的事件推送 { if (EventKey.InnerText.Equals("FangweiScan")) //点击防伪扫描 { //....处理返回逻辑 } } } return responseContent; } //接受文本消息 public string TextHandle(XmlDocument xmldoc) { string responseContent = ""; XmlNode ToUserName = xmldoc.SelectSingleNode("/xml/ToUserName"); XmlNode FromUserName = xmldoc.SelectSingleNode("/xml/FromUserName"); XmlNode Content = xmldoc.SelectSingleNode("/xml/Content"); if (Content != null) { //回复文本信息 responseContent = string.Format(ReplyType.Message_Text, FromUserName.InnerText, ToUserName.InnerText, DateTime.Now.Ticks, "欢迎使用微信公共账号,您输入的内容为:" + Content.InnerText); } return responseContent; } //写入日志 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>"; } } } }
五、发送模板消息 (官方说明文档>>)
在公众平台创建模板:
发送模板消息帮助类:
public class TemplateMessage { static JavaScriptSerializer Jss = new JavaScriptSerializer(); /// <summary> /// 给指定的用户发送模板消息 /// </summary> /// <param name="openId">用户标识openid</param> /// <param name="templateId">对应的模板id</param> /// <param name="data">对应模板的参数</param> /// <param name="url">点击对应消息弹出的地址</param> /// <param name="topcolor">颜色</param> /// <returns>返回json数据包</returns> public static string SendTemplate(string openId, string templateId, object data, string url, string topcolor = "#173177") { string access_token = AccessTokenHelp.AccessToken; var msgData = new { touser = openId, template_id = templateId, topcolor = topcolor, url = url, data = data }; string postData = Jss.Serialize(msgData); return HttpUtility.SendHttpRequest("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + access_token, postData); } /// <summary> /// 给指定的用户发送模板消息 /// </summary> /// <param name="openId">用户标识openid</param> /// <param name="templateId">对应的模板id</param> /// <param name="data">对应模板的参数</param> /// <param name="topcolor">颜色</param> /// <returns>返回json数据包</returns> public static string SendTemplate(string openId, string templateId, object data, string topcolor = "#173177") { string access_token = AccessTokenHelp.AccessToken; var msgData = new { touser = openId, template_id = templateId, topcolor = topcolor, data = data }; string postData = Jss.Serialize(msgData); return HttpUtility.SendHttpRequest("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + access_token, postData); } }
发送模板消息调用:
var data = new { first = new { value = "恭喜你购买成功", color = "#173177" } }; string templateId = "ibrQIRAaFkbeRNKpj9SbuJ0Rgs6q1ZTpsNkdf31lZwM"; TemplateMessage.SendTemplate(FromUserName.InnerText, templateId, data);
效果:
六、接口开发完成,配置接口信息
配置验证通过后,用户发消息或事件,接口拿到信息就可以做出处理反馈了。
接口URL验证:
public void ProcessRequest(HttpContext context) { string postString = string.Empty; 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); } } else { //验证签名 if (CheckSignature()) { HttpContext.Current.Response.Write(HttpContext.Current.Request.QueryString["echoStr"]); } else { HttpContext.Current.Response.Write("error"); } } }
验证签名方法:
/// <summary> /// 检查签名 /// </summary> /// <param name="request"></param> /// <returns></returns> private bool CheckSignature() { string token = System.Configuration.ConfigurationManager.AppSettings["WeChatToken"]; string signature = HttpContext.Current.Request.QueryString["signature"]; string timestamp = HttpContext.Current.Request.QueryString["timestamp"]; string nonce = HttpContext.Current.Request.QueryString["nonce"]; List<string> list = new List<string>(); list.Add(token); list.Add(timestamp); list.Add(nonce); //排序 list.Sort(); //拼串 string input = string.Empty; foreach (var item in list) { input += item; } //加密 string new_signature = CommonHelp.SHA1Encrypt(input); //验证 if (new_signature == signature) { return true; } else { return false; } }
签名中的SHA1Encrypt加密算法:
/// <summary> /// SHA1加密 /// </summary> /// <param name="intput">输入字符串</param> /// <returns>加密后的字符串</returns> public static string SHA1Encrypt(string intput) { byte[] StrRes = Encoding.Default.GetBytes(intput); HashAlgorithm mySHA = new SHA1CryptoServiceProvider(); StrRes = mySHA.ComputeHash(StrRes); StringBuilder EnText = new StringBuilder(); foreach (byte Byte in StrRes) { EnText.AppendFormat("{0:x2}", Byte); } return EnText.ToString(); }
此时,如果填写的Token和接口中的Token不一致就会验证失败,就可以防止其他人盗用你的接口了。
七、源码下载
八、相关学习资源推荐:
2.C#开发微信门户及应用 (比较全,winform实现,但没完整源码)
时间匆忙,写的不是很详细,有时间再慢慢完善。