【引言】

   利用asp.net搭建微信公众平台的案例并不多,微信官方给的案例是用PHP的,网上能找到的代码很多也是存在着这样那样的问题或者缺少部分方法,无法使用,下面是我依照官方文档写的基于.net 搭建微信公众平台源代码。由于经验不足,内可能存在不严谨之处,欢迎交流。

【分析】

   实现的功能较为简单,主要分为验证与消息接收回复两部分,首先是验证:

  

  这已经是验证好后的截图了,需要输入的是URL和你自己设定的Token码,URL为你上传服务器的地址例如:http://XXXXX.com/weixin/weixin.aspx,这里着重要强调的一个问题是加不加WWW的差异,无论加不加WWW,在验证时都是没有影响的,但在接收消息时,加和不加的差异是我最初无法接收到用户消息的直接原因,原因可能和POST带XML的请求机制有关。落实到具体项目中加还是不加,和你配置的DNS等有关,要是不想深入研究,当接收不到用户消息,而公众平台的调试工具里又是正常的,那你可以交替试试。Token码可以随机指定,但必须和代码中的Token指定相同,如果系统安全性要求比较高,建议增加token的复杂程度。

  在下面代码第一行中指定token,必须与上面一直

      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

上面的代码只是pageLoad,里面还要调用到的验证方法这里先不写,在最后会给出全部的源代码。

  验证成功后可以调用api实现消息收发,下面是微信官方给的文档

  接收消息

  

  发送消息

  以及关注事件

  我的做法是首先创建一个接收消息实体以及实体填充方法,如下,其中根据MsgType的不同,选择填充合适的Content或者EventName。当然我这里没有使用微信为开发者提供的高级功能(例如语音定位之类的),如有用到可以增加这个类的属性,并对应修改填充器即可。

         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;
}

  普通使用时(非验证时)的pageload,这个方法主要通过调用PostInput()(详间最后的源代码)还获取post过来的数据,并将它们传入消息适配器中。

         /// <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);////调用消息适配器
}
}
}

  以下消息适配器,通过MsgType来区分消息的类型,并调用对应的方法,这里偷了一个懒,就是用户首次关注时推送消息的方法没有抽象出去,因为我暂时也没有别的enven可调用,如果结构复杂时,可以自行抽象。如果需要改变欢迎词的内容改变msg的值即可。日后扩展新功能时,可以根据case的的MsgType新写对应的方法,我这里主要用到的是textCase(),需要传入用户发送过来的消息实体,因为你的业务逻辑中可能需要用到发件者的各种信息。

        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)
{ }
}

  获取文本回复信息方法,主要目的是按照官方文档的要求,拼接出所要返回给微信服务器的xml格式。它的msg内容来自于getText方法,同样需要传入用户消息实体,我下面给出了我用来测试的方法的内容。

         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); }
         private string getText(ExmlMsg xmlMsg)
{
string con = xmlMsg.Content.Trim(); System.Text.StringBuilder retsb = new StringBuilder();
retsb.Append("这是测试返回");
retsb.Append("接收到的消息:"+ xmlMsg.Content);
retsb.Append("用户的OPEANID:"+ xmlMsg.FromUserName); return retsb.ToString();
}

  如上代码,我得到的测试结果

    好了,可以收工了,这里还会用到的是时间转换的方法,因为文档规定的时间戳为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上了,如果要扩展或者二次开发,请看我上面的分析部分,该说的我都说了。

C# asp.net 搭建微信公众平台(可实现关注消息与消息自动回复)的代码以及我所遇到的问题的更多相关文章

  1. [c#]asp.net开发微信公众平台(6)阶段总结、服务搭建、接入

    经过前5篇,跟着一步步来的话,任何人都能搭建好一个能处理各种微信消息的框架了,总结一下最容易忽略的问题: 1.文本消息中可以使用换行符\n    : 2.微信发来的消息中带的那个长整型的时间,我们完全 ...

  2. [C#]asp.net开发微信公众平台----目录汇总-持续更新

    1.[c#]asp.net微信公众平台开发(1)数据库设计 2.[c#]asp.net微信公众平台开发(2)多层架构框架搭建和入口实现 3.[c#]asp.net微信公众平台开发(3)微信消息封装及反 ...

  3. asp.net开发微信公众平台----目录汇总-持续更新

    1.[c#]asp.net微信公众平台开发(1)数据库设计 2.[c#]asp.net微信公众平台开发(2)多层架构框架搭建和入口实现 3.[c#]asp.net微信公众平台开发(3)微信消息封装及反 ...

  4. [c#]asp.net开发微信公众平台(8)微信9大高级接口,自定义菜单

    前7篇把最基础的消息接收和回复全做完了,  也把高级接口的入口和分拆处理写好了空方法,  此篇接着介绍微信的9大高级接口, 并着重讲解其中的自定义菜单. 微信9大接口为: 1.语音识别接口 2.客服接 ...

  5. [c#]asp.net开发微信公众平台(7)前6篇的整体框架demo源码

    这里给出的demo是具备整体框架的微信公众平台源码, 所谓demo就是拿过去就可以直接演示使用的东西,  当然不会具备非常详细的具体到业务层面.数据层面的东西, 每个人都可以在此基础上自由发挥,  只 ...

  6. [c#]asp.net开发微信公众平台(5)微信图文消息

    上篇已经成功响应了关注事件,也实现了文本消息的发送,这篇开始图文消息处理, 微信中最常用的消息类型就是图文消息了,因为它图文并茂,最能表达信息. 图文消息在微信中的接口定义如下: <xml> ...

  7. 微信公众平台开发教程Java版(三) 消息接收和发送

    https://www.iteye.com/blog/tuposky-2017429 前面两章已经介绍了如何接入微信公众平台,这一章说说消息的接收和发送 可以先了解公众平台的消息api接口(接收消息, ...

  8. 使用python一步一步搭建微信公众平台(一)

    使用的工具,python 新浪SAE平台,微信的公众平台 你需要先在微信的公众平台与新浪SAE平台上各种注册,微信平台注册的时候需要你拍张手持身份证的照片,还有几天的审核期 微信公众平台:http:/ ...

  9. asp.net 实现微信公众平台的主动推送信息

    通过学习借鉴朋友的实现方法进行整理(微信公众帐号主动发送消息给用户,asp.net版本). /// <summary> /// MD5 32位加密 /// </summary> ...

随机推荐

  1. java 单例模式

    懒汉式 public class Singleton{ //@单例类只能有一个实例 //@单例类必须自行创建这个实例 //@单例类必须给所有对象提供这一个实例//必须向整个系统提供这个这个实例 pri ...

  2. Sql Server函数全解<二>数学函数

    阅读目录 1.绝对值函数ABS(x)和返回圆周率的函数PI() 2.平方根函数SQRT(x) 3.获取随机函数的函数RAND()和RAND(x) 4.四舍五入函数ROUND(x,y) 5.符号函数SI ...

  3. yii2 登录用户和未登录用户使用不同的 layout

    可以在配置文件中增加一个 “beforeRequest” 事件: 'on beforeRequest' => function () { Yii::$app->layout = Yii:: ...

  4. org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [XXX] in DispatcherServlet with name 'springMVC'

    在web.xml中添加 <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern ...

  5. mysql 查询表结构

    use information_schema; select column_name, column_type, data_type, is_nullable, column_comment from ...

  6. git: fatal: Not a git repository (or any of the parent directories): .git

    在看书 FlaskWeb开发:基于Python的Web应用开发实战 时,下载完源码后 git clone https://github.com/miguelgrinberg/flasky.git 试着 ...

  7. sudo:有效用户 ID 不是 0,sudo 属于 root 并设置了 setuid 位吗

    遇见这种问题应该检查sudo文件拥有者名称 ---x--x--x. 1 cmp cmp 130720  sudo 明显拥有者有问题 chown root:root /usr/bin/sudo chmo ...

  8. 【python】安装指定模块

    使用pip 1.卸载模块 sudo pip uninstall xxx 2.安装指定版本模块 sudo pip install xxx==2.0.1.3

  9. 还敢说你是程序员?一律师闲着没事写了个app,用户量600万

    今天周五,是我在上海上班的第五天. 这几天怎么说呢,跟混日子差不多,因为处处有“”惊喜”. 上班第一天领来办公电脑,登上自己的公司邮箱,惊喜来了!我的擦擦擦,全TM是英文呐!作为一个从村儿里来的码农, ...

  10. java.io.Serializable 序列化接口

    什么是序列化.反序列化? Serialization(序列化)是一种将对象以一连串的字节描述的过程: 反序列化deserialization是一种将这些字节重建成一个对象的过程. 序列化通俗一点说就是 ...