在微信公众平台的后台交互模型里,用户发送给公众号的内容会以POST请求的形式推送到开发者配置的URL上。对于C#开发者来说,处理这类请求的核心在于从请求输入流中读取原始XML,判断消息类型,然后构造符合微信规范的响应XML并写回响应流。文本消息和图文消息是最基础也是最常用的两种被动回复形式,掌握它们的数据格式与代码实现,是搭建微信门户的第一步。

微信消息交互的基本协议与接收原理
微信服务器在用户触发事件或发送消息时,会向开发者服务器发起一次HTTP POST请求,请求体为XML格式。在C#的ASP.NET(Web Forms或MVC、Web API)应用中,我们不能依赖表单字段,而必须从Request.InputStream中读取字节流并解码为字符串。微信推送的普通文本消息XML中包含ToUserName(公众号微信号)、FromUserName(粉丝OpenID)、CreateTime(时间戳)、MsgType(值为text)、Content以及MsgId等节点。
理解这个结构非常关键,因为被动回复时,我们需要将原请求的ToUserName和FromUserName互换:即回复消息的ToUserName填粉丝OpenID,FromUserName填公众号微信号。如果弄反,微信会认为消息非法而丢弃。此外,微信对被动回复有5秒超时限制,因此业务逻辑必须轻量,耗时的处理应放入队列或异步任务,先返回空字符串或success告知微信已收到。
在加密模式下(安全模式),推送的XML外还会包裹Encrypt节点,需使用公众号配置的EncodingAESKey进行解密才能拿到真实消息。对于大多数企业内部门户,可以先使用明文模式或兼容模式调试,待逻辑跑通后再切到安全模式。下面代码展示如何在不加密情况下读取并解析文本消息:
using System;
using System.IO;
using System.Xml;
public class WeixinInput
{
public string ToUserName { get; set; }
public string FromUserName { get; set; }
public string MsgType { get; set; }
public string Content { get; set; }
// 从HttpContext读取并解析文本消息
public static WeixinInput Parse(System.Web.HttpContext context)
{
Stream stream = context.Request.InputStream;
stream.Seek(0, SeekOrigin.Begin);
string xmlText = new StreamReader(stream, System.Text.Encoding.UTF8).ReadToEnd();
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlText);
WeixinInput input = new WeixinInput();
input.ToUserName = doc.SelectSingleNode("//ToUserName").InnerText;
input.FromUserName = doc.SelectSingleNode("//FromUserName").InnerText;
input.MsgType = doc.SelectSingleNode("//MsgType").InnerText;
if (input.MsgType == "text")
{
input.Content = doc.SelectSingleNode("//Content").InnerText;
}
return input;
}
}
文本消息应答的构造与返回
文本消息的被动回复格式相对简单,只需构造一个包含ToUserName、FromUserName、CreateTime、MsgType为text以及Content节点的XML。注意CreateTime必须是Unix时间戳(自1970年1月1日以来的秒数),而不是DateTime的默认字符串。在C#中,可以通过(DateTime.Now - new DateTime(1970,1,1)).TotalSeconds取整来获取。
实际开发中建议封装一个统一的回复方法,将对象序列化为XML字符串并写回Response。注意必须将Response.ContentType设为text/xml,且清除缓冲区避免多余输出。如果公众号处于安全模式,还需将明文XML加密后按规范包裹返回,但明文模式下直接输出即可。以下示例演示了文本消息回复的核心代码:
using System;
using System.Web;
using System.Text;
public class WeixinReply
{
// 生成文本消息XML
public static string BuildTextXml(string toUser, string fromUser, string content)
{
long createTime = (long)(DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds;
StringBuilder sb = new StringBuilder();
sb.Append("<xml>");
sb.Append("<ToUserName><![CDATA[" + toUser + "]]></ToUserName>");
sb.Append("<FromUserName><![CDATA[" + fromUser + "]]></FromUserName>");
sb.Append("<CreateTime>" + createTime + "</CreateTime>");
sb.Append("<MsgType><![CDATA[text]]></MsgType>");
sb.Append("<Content><![CDATA[" + content + "]]></Content>");
sb.Append("</xml>");
return sb.ToString();
}
// 在页面中调用并返回
public static void ResponseText(HttpContext context, string toUser, string fromUser, string content)
{
string xml = BuildTextXml(toUser, fromUser, content);
context.Response.Clear();
context.Response.ContentType = "text/xml";
context.Response.Write(xml);
context.Response.End();
}
}
文本消息虽然简单,但在门户应用中常用于自动客服、菜单点击响应和关键词查询。比如用户发送“天气”,后台识别Content包含关键词后,调用内部接口获取数据再回复文本。这种同步应答体验好,但受限于纯文字,无法展示图片或链接,因此图文消息成为补充方案。
图文消息应答的结构与多图文实现
图文消息允许在一条回复中携带一至多条图文卡片,每条卡片含标题、描述、图片URL和跳转链接。其XML中MsgType为news,并通过ArticleCount指定数量,Articles节点下包含多个item。每个item必须有Title、Description、PicUrl和Url,其中图片与链接必须使用完整HTTP地址且域名需在公众号后台配置为可信域名,否则在微信中无法打开。
单图文和多图文的区别仅在于item的数量,微信限制被动回复图文最多8条。在C#中,我们可以用List集合承载条目,再循环拼接XML。需要注意Description不宜过长,否则在客户端会被截断;PicUrl建议使用图片CDN地址以提升加载速度。如果门户系统有内容管理系统,可将文章列表直接映射为图文消息,实现“回复关键词看文章”的效果。
下面的代码演示了如何构造一条包含两篇文章的图文消息,并写回响应。与文本消息类似,时间缀和收发人互换规则一致,只是节点结构更复杂:
using System;
using System.Web;
using System.Text;
using System.Collections.Generic;
public class ArticleItem
{
public string Title { get; set; }
public string Description { get; set; }
public string PicUrl { get; set; }
public string Url { get; set; }
}
public class NewsReply
{
public static string BuildNewsXml(string toUser, string fromUser, List<ArticleItem> list)
{
long createTime = (long)(DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds;
StringBuilder sb = new StringBuilder();
sb.Append("<xml>");
sb.Append("<ToUserName><![CDATA[" + toUser + "]]></ToUserName>");
sb.Append("<FromUserName><![CDATA[" + fromUser + "]]></FromUserName>");
sb.Append("<CreateTime>" + createTime + "</CreateTime>");
sb.Append("<MsgType><CDATA[news]]></MsgType>");
sb.Append("<ArticleCount>" + list.Count + "</ArticleCount>");
sb.Append("<Articles>");
foreach (var item in list)
{
sb.Append("<item>");
sb.Append("<Title><![CDATA[" + item.Title + "]]></Title>");
sb.Append("<Description><![CDATA[" + item.Description + "]]></Description>");
sb.Append("<PicUrl><![CDATA[" + item.PicUrl + "]]></PicUrl>");
sb.Append("<Url><![CDATA[" + item.Url + "]]></Url>");
sb.Append("</item>");
}
sb.Append("</Articles>");
sb.Append("</xml>");
return sb.ToString();
}
}
在真实门户项目里,通常会设计一个消息工厂,根据数据库配置或用户指令自动选择文本或图文应答。例如用户发送“帮助”返回文本菜单,发送“新闻”则返回图文列表。配合缓存和异步日志,既能满足交互丰富度,也能支撑较高并发。只要严格遵循微信的XML schema与超时约束,C#完全可以稳定高效地完成这类门户消息应答需求。