【引言】
利用asp.net搭建微信公眾平台的案例並不多,微信官方給的案例是用PHP的,網上能找到的代碼很多也是存在着這樣那樣的問題或者缺少部分方法,無法使用,下面是我依照官方文檔寫的基於.net 搭建微信公眾平台源代碼。由於經驗不足,內可能存在不嚴謹之處,歡迎交流。
【分析】
實現的功能較為簡單,主要分為驗證與消息接收回復兩部分,首先是驗證:
這已經是驗證好后的截圖了,需要輸入的是URL和你自己設定的Token碼,URL為你上傳服務器的地址例如:http://XXXXX.com/weixin/weixin.aspx,這里着重要強調的一個問題是加不加WWW的差異,無論加不加WWW,在驗證時都是沒有影響的,但在接收消息時,加和不加的差異是我最初無法接收到用戶消息的直接原因,原因可能和POST帶XML的請求機制有關。落實到具體項目中加還是不加,和你配置的DNS等有關,要是不想深入研究,當接收不到用戶消息,而公眾平台的調試工具里又是正常的,那你可以交替試試。Token碼可以隨機指定,但必須和代碼中的Token指定相同,如果系統安全性要求比較高,建議增加token的復雜程度。
在下面代碼第一行中指定token,必須與上面一直
1 const string Token = "nidaye1234";//你的token 2 #region 以下代碼只用於第一次驗證 驗證完后請注釋 3 protected void Page_Load(object sender, EventArgs e) 4 { 5 string postStr = ""; 6 if (Request.HttpMethod.ToLower() == "post") 7 { 8 System.IO.Stream s = System.Web.HttpContext.Current.Request.InputStream; 9 byte[] b = new byte[s.Length]; 10 s.Read(b, 0, (int)s.Length); 11 postStr = System.Text.Encoding.UTF8.GetString(b); 12 if (!string.IsNullOrEmpty(postStr)) 13 {16 Response.End(); 17 } 18 //WriteLog("postStr:" + postStr); 19 } 20 else 21 { 22 Valid(); 23 } 24 } 25 #endregion
上面的代碼只是pageLoad,里面還要調用到的驗證方法這里先不寫,在最后會給出全部的源代碼。
驗證成功后可以調用api實現消息收發,下面是微信官方給的文檔
接收消息
發送消息
以及關注事件
我的做法是首先創建一個接收消息實體以及實體填充方法,如下,其中根據MsgType的不同,選擇填充合適的Content或者EventName。當然我這里沒有使用微信為開發者提供的高級功能(例如語音定位之類的),如有用到可以增加這個類的屬性,並對應修改填充器即可。
1 private class ExmlMsg 2 { 3 /// <summary> 4 /// 本公眾賬號 5 /// </summary> 6 public string ToUserName { get; set; } 7 /// <summary> 8 /// 用戶賬號 9 /// </summary> 10 public string FromUserName { get; set; } 11 /// <summary> 12 /// 發送時間戳 13 /// </summary> 14 public string CreateTime { get; set; } 15 /// <summary> 16 /// 發送的文本內容 17 /// </summary> 18 public string Content { get; set; } 19 /// <summary> 20 /// 消息的類型 21 /// </summary> 22 public string MsgType { get; set; } 23 /// <summary> 24 /// 事件名稱 25 /// </summary> 26 public string EventName { get; set; } 27 28 } 29 30 private ExmlMsg GetExmlMsg(XmlElement root) 31 { 32 ExmlMsg xmlMsg = new ExmlMsg() { 33 FromUserName = root.SelectSingleNode("FromUserName").InnerText, 34 ToUserName = root.SelectSingleNode("ToUserName").InnerText, 35 CreateTime = root.SelectSingleNode("CreateTime").InnerText, 36 MsgType = root.SelectSingleNode("MsgType").InnerText, 37 }; 38 if (xmlMsg.MsgType.Trim().ToLower() == "text") 39 { 40 xmlMsg.Content = root.SelectSingleNode("Content").InnerText; 41 } 42 else if (xmlMsg.MsgType.Trim().ToLower() == "event") 43 { 44 xmlMsg.EventName = root.SelectSingleNode("Event").InnerText; 45 } 46 return xmlMsg; 47 }
普通使用時(非驗證時)的pageload,這個方法主要通過調用PostInput()(詳間最后的源代碼)還獲取post過來的數據,並將它們傳入消息適配器中。
1 /// <summary> 2 /// 以下是正常使用時的pageload 請在驗證時將其注釋 並保證在正常使用時可用 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 protected void Page_Load(object sender, EventArgs e) 7 { 8 9 if (Request.HttpMethod == "POST") 10 { 11 string weixin = ""; 12 weixin = PostInput();//獲取xml數據 13 if (!string.IsNullOrEmpty(weixin)) 14 { 15 ResponseMsg(weixin);////調用消息適配器 16 } 17 } 18 }
以下消息適配器,通過MsgType來區分消息的類型,並調用對應的方法,這里偷了一個懶,就是用戶首次關注時推送消息的方法沒有抽象出去,因為我暫時也沒有別的enven可調用,如果結構復雜時,可以自行抽象。如果需要改變歡迎詞的內容改變msg的值即可。日后擴展新功能時,可以根據case的的MsgType新寫對應的方法,我這里主要用到的是textCase(),需要傳入用戶發送過來的消息實體,因為你的業務邏輯中可能需要用到發件者的各種信息。
1 private void ResponseMsg(string weixin)// 服務器響應微信請求 2 { 3 XmlDocument doc = new XmlDocument(); 4 doc.LoadXml(weixin);//讀取xml字符串 5 XmlElement root = doc.DocumentElement; 6 ExmlMsg xmlMsg = GetExmlMsg(root); 7 //XmlNode MsgType = root.SelectSingleNode("MsgType"); 8 //string messageType = MsgType.InnerText; 9 string messageType = xmlMsg.MsgType;//獲取收到的消息類型。文本(text),圖片(image),語音等。 10 11 12 try 13 { 14 15 switch (messageType) 16 { 17 //當消息為文本時 18 case "text": 19 textCase(xmlMsg); 20 break; 21 case "event": 22 if (!string.IsNullOrEmpty(xmlMsg.EventName) && xmlMsg.EventName.Trim() == "subscribe") 23 { 24 //剛關注時的時間,用於歡迎詞 25 int nowtime = ConvertDateTimeInt(DateTime.Now); 26 string msg = "你要關注我,我有什么辦法。隨便發點什么試試吧~~~"; 27 string resxml = "<xml><ToUserName><![CDATA[" + xmlMsg.FromUserName + "]]></ToUserName><FromUserName><![CDATA[" + xmlMsg.ToUserName + "]]></FromUserName><CreateTime>" + nowtime + "</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[" + msg + "]]></Content><FuncFlag>0</FuncFlag></xml>"; 28 Response.Write(resxml); 29 } 30 break; 31 case "image": 32 break; 33 case "voice": 34 break; 35 case "vedio": 36 break; 37 case "location": 38 break; 39 case "link": 40 break; 41 default: 42 break; 43 } 44 Response.End(); 45 } 46 catch (Exception) 47 { 48 49 } 50 }
獲取文本回復信息方法,主要目的是按照官方文檔的要求,拼接出所要返回給微信服務器的xml格式。它的msg內容來自於getText方法,同樣需要傳入用戶消息實體,我下面給出了我用來測試的方法的內容。
1 private void textCase(ExmlMsg xmlMsg) 2 { 3 int nowtime = ConvertDateTimeInt(DateTime.Now); 4 string msg = ""; 5 msg = getText(xmlMsg); 6 string resxml = "<xml><ToUserName><![CDATA[" + xmlMsg.FromUserName + "]]></ToUserName><FromUserName><![CDATA[" + xmlMsg.ToUserName + "]]></FromUserName><CreateTime>" + nowtime + "</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[" + msg + "]]></Content><FuncFlag>0</FuncFlag></xml>"; 7 Response.Write(resxml); 8 9 }
1 private string getText(ExmlMsg xmlMsg) 2 { 3 string con = xmlMsg.Content.Trim(); 4 5 System.Text.StringBuilder retsb = new StringBuilder(200); 6 retsb.Append("這是測試返回"); 7 retsb.Append("接收到的消息:"+ xmlMsg.Content); 8 retsb.Append("用戶的OPEANID:"+ xmlMsg.FromUserName); 9 10 return retsb.ToString(); 11 }
如上代碼,我得到的測試結果
好了,可以收工了,這里還會用到的是時間轉換的方法,因為文檔規定的時間戳為int類型。於是網上找了轉換方法。詳見下面的完整代碼。
【完整代碼】
太長了,我折起來了,需要打開,我開始用的是MVC,但后來想想沒必要,就用了普通的aspx,其實一般處理程序(ashx)也行,而且性能會更好點。我可以保證以下代碼完整,不會缺方法,被坑怕了,呵呵。
using System; using System.Collections.Generic; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Data; using System.IO; using System.Net; using System.Text; using System.Xml; using System.Web.Security; using System.Text.RegularExpressions; namespace WhoLove.weixin { public partial class weixin : System.Web.UI.Page { const string Token = "nidaye1234";//你的token #region 以下代碼只用於第一次驗證 驗證完后請注釋 //protected void Page_Load(object sender, EventArgs e) //{ // string postStr = ""; // if (Request.HttpMethod.ToLower() == "post") // { // System.IO.Stream s = System.Web.HttpContext.Current.Request.InputStream; // byte[] b = new byte[s.Length]; // s.Read(b, 0, (int)s.Length); // postStr = System.Text.Encoding.UTF8.GetString(b); // if (!string.IsNullOrEmpty(postStr)) // { // Response.End(); // } // //WriteLog("postStr:" + postStr); // } // else // { // Valid(); // } //} #endregion #region 以下是正常使用時的pageload 請在驗證時將其注釋 並保證在正常使用時可用 /// <summary> /// 以下是正常使用時的pageload 請在驗證時將其注釋 並保證在正常使用時可用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void Page_Load(object sender, EventArgs e) { if (Request.HttpMethod == "POST") { string weixin = ""; weixin = PostInput();//獲取xml數據 if (!string.IsNullOrEmpty(weixin)) { ResponseMsg(weixin);//調用消息適配器 } } } #endregion #region 獲取post請求數據 /// <summary> /// 獲取post請求數據 /// </summary> /// <returns></returns> private string PostInput() { Stream s = System.Web.HttpContext.Current.Request.InputStream; byte[] b = new byte[s.Length]; s.Read(b, 0, (int)s.Length); return Encoding.UTF8.GetString(b); } #endregion #region 消息類型適配器 private void ResponseMsg(string weixin)// 服務器響應微信請求 { XmlDocument doc = new XmlDocument(); doc.LoadXml(weixin);//讀取xml字符串 XmlElement root = doc.DocumentElement; ExmlMsg xmlMsg = GetExmlMsg(root); //XmlNode MsgType = root.SelectSingleNode("MsgType"); //string messageType = MsgType.InnerText; string messageType = xmlMsg.MsgType;//獲取收到的消息類型。文本(text),圖片(image),語音等。 try { switch (messageType) { //當消息為文本時 case "text": textCase(xmlMsg); break; case "event": if (!string.IsNullOrEmpty(xmlMsg.EventName) && xmlMsg.EventName.Trim() == "subscribe") { //剛關注時的時間,用於歡迎詞 int nowtime = ConvertDateTimeInt(DateTime.Now); string msg = "你要關注我,我有什么辦法。隨便發點什么試試吧~~~"; string resxml = "<xml><ToUserName><![CDATA[" + xmlMsg.FromUserName + "]]></ToUserName><FromUserName><![CDATA[" + xmlMsg.ToUserName + "]]></FromUserName><CreateTime>" + nowtime + "</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[" + msg + "]]></Content><FuncFlag>0</FuncFlag></xml>"; Response.Write(resxml); } break; case "image": break; case "voice": break; case "vedio": break; case "location": break; case "link": break; default: break; } Response.End(); } catch (Exception) { } } #endregion private string getText(ExmlMsg xmlMsg) { string con = xmlMsg.Content.Trim(); System.Text.StringBuilder retsb = new StringBuilder(200); retsb.Append("這里放你的業務邏輯"); retsb.Append("接收到的消息:"+ xmlMsg.Content); retsb.Append("用戶的OPEANID:"+ xmlMsg.FromUserName); return retsb.ToString(); } #region 操作文本消息 + void textCase(XmlElement root) private void textCase(ExmlMsg xmlMsg) { int nowtime = ConvertDateTimeInt(DateTime.Now); string msg = ""; msg = getText(xmlMsg); string resxml = "<xml><ToUserName><![CDATA[" + xmlMsg.FromUserName + "]]></ToUserName><FromUserName><![CDATA[" + xmlMsg.ToUserName + "]]></FromUserName><CreateTime>" + nowtime + "</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[" + msg + "]]></Content><FuncFlag>0</FuncFlag></xml>"; Response.Write(resxml); } #endregion #region 將datetime.now 轉換為 int類型的秒 /// <summary> /// datetime轉換為unixtime /// </summary> /// <param name="time"></param> /// <returns></returns> private int ConvertDateTimeInt(System.DateTime time) { System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); return (int)(time - startTime).TotalSeconds; } private int converDateTimeInt(System.DateTime time) { System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)); return (int)(time - startTime).TotalSeconds; } /// <summary> /// unix時間轉換為datetime /// </summary> /// <param name="timeStamp"></param> /// <returns></returns> private DateTime UnixTimeToTime(string timeStamp) { DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); long lTime = long.Parse(timeStamp + "0000000"); TimeSpan toNow = new TimeSpan(lTime); return dtStart.Add(toNow); } #endregion #region 驗證微信簽名 保持默認即可 /// <summary> /// 驗證微信簽名 /// </summary> /// * 將token、timestamp、nonce三個參數進行字典序排序 /// * 將三個參數字符串拼接成一個字符串進行sha1加密 /// * 開發者獲得加密后的字符串可與signature對比,標識該請求來源於微信。 /// <returns></returns> private bool CheckSignature() { string signature = Request.QueryString["signature"].ToString(); string timestamp = Request.QueryString["timestamp"].ToString(); string nonce = Request.QueryString["nonce"].ToString(); string[] ArrTmp = { Token, timestamp, nonce }; Array.Sort(ArrTmp); //字典排序 string tmpStr = string.Join("", ArrTmp); tmpStr = FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr, "SHA1"); tmpStr = tmpStr.ToLower(); if (tmpStr == signature) { return true; } else { return false; } } private void Valid() { string echoStr = Request.QueryString["echoStr"].ToString(); if (CheckSignature()) { if (!string.IsNullOrEmpty(echoStr)) { Response.Write(echoStr); Response.End(); } } } #endregion #region 寫日志(用於跟蹤) + WriteLog(string strMemo, string path = "*****") /// <summary> /// 寫日志(用於跟蹤) /// 如果log的路徑修改,更改path的默認值 /// </summary> private void WriteLog(string strMemo, string path = "wx.txt") { string filename = Server.MapPath(path); StreamWriter sr = null; try { if (!File.Exists(filename)) { sr = File.CreateText(filename); } else { sr = File.AppendText(filename); } sr.WriteLine(strMemo); } catch { } finally { if (sr != null) sr.Close(); } } //#endregion #endregion #region 接收的消息實體類 以及 填充方法 private class ExmlMsg { /// <summary> /// 本公眾賬號 /// </summary> public string ToUserName { get; set; } /// <summary> /// 用戶賬號 /// </summary> public string FromUserName { get; set; } /// <summary> /// 發送時間戳 /// </summary> public string CreateTime { get; set; } /// <summary> /// 發送的文本內容 /// </summary> public string Content { get; set; } /// <summary> /// 消息的類型 /// </summary> public string MsgType { get; set; } /// <summary> /// 事件名稱 /// </summary> public string EventName { get; set; } } private ExmlMsg GetExmlMsg(XmlElement root) { ExmlMsg xmlMsg = new ExmlMsg() { FromUserName = root.SelectSingleNode("FromUserName").InnerText, ToUserName = root.SelectSingleNode("ToUserName").InnerText, CreateTime = root.SelectSingleNode("CreateTime").InnerText, MsgType = root.SelectSingleNode("MsgType").InnerText, }; if (xmlMsg.MsgType.Trim().ToLower() == "text") { xmlMsg.Content = root.SelectSingleNode("Content").InnerText; } else if (xmlMsg.MsgType.Trim().ToLower() == "event") { xmlMsg.EventName = root.SelectSingleNode("Event").InnerText; } return xmlMsg; } #endregion } }
【最后說的】
如果以上程序存在錯誤,歡迎指出,還是學生,水平有限,聯系方式sina微博@導彈林瀚,謝謝。
也懶得放github上了,如果要擴展或者二次開發,請看我上面的分析部分,該說的我都說了。