- 后端
- 即时通讯
- 金融科技
【免费下载链接】WeiXinMPSDK
微信全平台 .NET SDK, Senparc.Weixin for C#,支持 .NET Framework 及 .NET Core、.NET 10.0。已支持微信公众号、小程序、小游戏、微信支付、企业微信/企业号、开放平台、JSSDK、微信周边等全平台。 WeChat SDK for C#.
导读:本文以 Senparc.Weixin(WeiXinMPSDK)开源仓库中 docs/en/guide/wxopen/messagehandler.md 为骨架,系统讲解微信小程序(WxOpen)客服消息体系的
MessageHandler机制。你将掌握如何继承WxOpenMessageHandler<TMC>编写自定义消息处理器、如何区分必须重写与可选重写的方法、如何通过中间件方式(推荐,一行代码注册)与Controller 方式(精细化控制每一步执行)两种途径把消息处理器暴露给微信服务器,并深入理解其背后的消息路由、上下文、消息去重与签名校验实现原理,最终能够在自己的小程序后端中快速落地一套可运行、可扩展的客服消息处理方案。
一、MessageHandler 是什么:小程序客服消息的统一入口
微信小程序与用户之间的客服对话窗口消息,以及微信服务器主动推送的各种事件(如用户进入客服会话、用户发送文字/图片/小程序卡片等),都需要在开发者服务器上有一个统一、稳定的接收与响应入口。在 Senparc.Weixin SDK 中,这个入口就是MessageHandler。
MessageHandler用于处理小程序客服对话窗口的消息以及其他微信服务器的推送信息。SDK 已经为开发者准备好了所有需要的基础功能——包括消息的接收解析、签名校验、消息去重、上下文(会话)管理、响应消息的构造与序列化等——开发者只需要创建一个自定义的子类,重写与业务相关的处理方法,即可完成绝大多数业务场景。
从类继承关系看(见 WxOpenMessageHandler.cs),SDK 提供的是一个泛型抽象基类:
public abstract partial class WxOpenMessageHandler<TMC> : MessageHandler<TMC, IRequestMessageBase, IResponseMessageBase> where TMC : class, IMessageContext<IRequestMessageBase, IResponseMessageBase>, new()其中泛型参数TMC是消息上下文(MessageContext)类型,用于维护单个用户的会话状态。开发者只需要继承这个基类,并指定自己的上下文类型即可。
二、自定义 MessageHandler:CustomWxOpenMessageHandler 实战
2.1 两个核心文件
当前示例中,自定义的 MessageHandler 命名为CustomWxOpenMessageHandler,位于示例项目的MessageHandlers/目录下(Samples/WxOpen/Senparc.Weixin.Sample.WxOpen/MessageHandlers/),包含两个文件:
| 文件 | 作用 |
|---|---|
CustomWxOpenMessageHandler.cs | 自定义 MessageHandler,消息处理核心逻辑 |
CustomWxOpenMessageContext.cs | 自定义重写DefaultWxOpenMessageContext上下文(可选) |
类定义与基类的对应关系(CustomWxOpenMessageHandler.cs):
public partial class CustomWxOpenMessageHandler : WxOpenMessageHandler<CustomWxOpenMessageContext>注意这里用到的上下文类型是CustomWxOpenMessageContext,它继承自 SDK 内置的DefaultWxOpenMessageContext(CustomWxOpenMessageContext.cs)。上下文(MessageContext)保存着某个用户(OpenId)在会话期间的历史消息列表与自定义存储数据,是后续实现"上下文消息记录回显"等功能的基础。
2.2 必须重写与可选重写的方法
在CustomWxOpenMessageHandler.cs中演示的所有重写方法中,只有DefaultResponseMessageAsync()是必须重写的,其他所有OnXxxRequestAsync()方法都是可选的。当用户发送的消息类型找不到对应的重写方法时,框架会自动调用DefaultResponseMessageAsync()作为兜底响应。
对应到 SDK 基类源码,OnXxxRequestAsync系列方法均为virtual虚方法,且默认实现会回落(fallback)到默认响应(见 WxOpenMessageHandler.Message.cs):
public async virtual Task<IResponseMessageBase> OnImageRequestAsync(RequestMessageImage requestMessage) { return await DefaultAsyncMethod(requestMessage, () => OnImageRequest(requestMessage)).ConfigureAwait(false); } public async virtual Task<IResponseMessageBase> OnTextRequestAsync(RequestMessageText requestMessage) { return await DefaultAsyncMethod(requestMessage, () => OnTextRequest(requestMessage)).ConfigureAwait(false); } public async virtual Task<IResponseMessageBase> OnMiniProgramPageRequestAsync(RequestMessageMiniProgramPage requestMessage) { return await DefaultResponseMessageAsync(requestMessage).ConfigureAwait(false); }而消息路由分发逻辑在 WxOpenMessageHandler.cs 中按消息类型调用对应的处理方法,例如:
await OnTextRequestAsync(RequestMessage as RequestMessageText); await OnImageRequestAsync(RequestMessage as RequestMessageImage); ResponseMessage = await OnMiniProgramPageRequestAsync(RequestMessage as RequestMessageMiniProgramPage);也就是说:你重写了哪个类型的处理方法,该类型的消息就走你的逻辑;未重写的类型,统一落到DefaultResponseMessageAsync()。
版本提示:基类中旧的同步方法(
OnTextRequest、OnImageRequest)已被标记为[Obsolete](见 WxOpenMessageHandler.Message.cs),官方推荐全面使用异步方法(OnXxxRequestAsync)。
2.3 一个可直接运行的完整示例
以下是示例项目中CustomWxOpenMessageHandler.cs的完整核心实现(有精简注释),覆盖了文字、图片、进入客服事件、小程序卡片等常见场景,可以直接对照学习:
public partial class CustomWxOpenMessageHandler : WxOpenMessageHandler<CustomWxOpenMessageContext> { private string appId = Config.SenparcWeixinSetting.WxOpenAppId; private string appSecret = Config.SenparcWeixinSetting.WxOpenAppSecret; /// <summary> /// 为中间件提供生成当前类的委托(中间件方式必需) /// </summary> public static Func<Stream, PostModel, int, IServiceProvider, CustomWxOpenMessageHandler> GenerateMessageHandler = (stream, postModel, maxRecordCount, serviceProvider) => new CustomWxOpenMessageHandler(stream, postModel, maxRecordCount, serviceProvider); public CustomWxOpenMessageHandler(Stream inputStream, PostModel postModel, int maxRecordCount = 0, IServiceProvider serviceProvider = null) : base(inputStream, postModel, maxRecordCount, serviceProvider: serviceProvider) { // 设置消息上下文过期时间(单位:分钟) GlobalMessageContext.ExpireMinutes = 3; if (!string.IsNullOrEmpty(postModel.AppId)) { appId = postModel.AppId;// 通过第三方开放平台发送过来的请求 } // 在指定条件下,不使用消息去重 base.OmitRepeatedMessageFunc = requestMessage => { var textRequestMessage = requestMessage as RequestMessageText; if (textRequestMessage != null && textRequestMessage.Content == "容错") { return false; } return true; }; } // 执行前钩子:初始化上下文存储数据 public override async Task OnExecutingAsync(CancellationToken cancellationToken) { var currentMessageContext = await base.GetCurrentMessageContext(); if (currentMessageContext.StorageData == null || (currentMessageContext.StorageData is int)) { currentMessageContext.StorageData = 0; } await base.OnExecutingAsync(cancellationToken); } // 执行后钩子:累加计数 public override async Task OnExecutedAsync(CancellationToken cancellationToken) { await base.OnExecutedAsync(cancellationToken); try { var currentMessageContext = await base.GetCurrentMessageContext(); currentMessageContext.StorageData = ((int)currentMessageContext.StorageData) + 1; } catch (Exception ex) { Senparc.CO2NET.Trace.SenparcTrace.SendCustomLog("小程序 OnExecutedAsync 常规跟踪(开发者请忽略)", ex.ToString()); } } /// <summary> /// 处理文字请求 /// </summary> public override async Task<IResponseMessageBase> OnTextRequestAsync(RequestMessageText requestMessage) { var contentUpper = requestMessage.Content.ToUpper(); if (contentUpper == "LINK") { // 发送图文链接客服消息 await Senparc.Weixin.WxOpen.AdvancedAPIs.CustomApi.SendLinkAsync(appId, OpenId, "欢迎使用 Senparc.Weixin SDK", "感谢大家的支持!\r\n\r\n盛派永远在你身边!", "https://weixin.senparc.com", "https://sdk.weixin.senparc.com/images/book-cover-front-small-3d-transparent.png"); } else if (contentUpper == "CARD") { // 上传封面临时素材后发送小程序卡片客服消息 var uploadResult = await MP.AdvancedAPIs.MediaApi.UploadTemporaryMediaAsync(appId, UploadMediaFileType.image, ServerUtility.ContentRootMapPath("~/Images/Logo.thumb.jpg")); await Senparc.Weixin.WxOpen.AdvancedAPIs.CustomApi.SendMiniProgramPageAsync(appId, OpenId, "欢迎使用 Senparc.Weixin SDK", "pages/websocket/websocket", uploadResult.media_id); } else if (contentUpper == "客服") { // 进入多客服会话 await Senparc.Weixin.WxOpen.AdvancedAPIs.CustomApi.SendTextAsync(appId, OpenId, "您即将进入客服"); var responseMessage = base.CreateResponseMessage<ResponseMessageTransfer_Customer_Service>(); return responseMessage; } else { // 回显用户输入,并展示历史消息记录(来自 MessageContext) var result = new StringBuilder(); result.AppendFormat("您刚才发送了文字信息:{0}\r\n\r\n", requestMessage.Content); var messageContext = await GetCurrentMessageContext().ConfigureAwait(false); if (messageContext.RequestMessages.Count > 1) { result.AppendFormat("您刚才还发送了如下消息({0}/{1}):\r\n", messageContext.RequestMessages.Count, messageContext.StorageData); for (int i = messageContext.RequestMessages.Count - 2; i >= 0; i--) { // ... 遍历历史消息,拼装时间、消息类型与内容 } result.AppendLine("\r\n"); } // 处理微信换行符识别问题 var msg = result.ToString().Replace("\r\n", "\n"); // 发送客服消息 await Senparc.Weixin.WxOpen.AdvancedAPIs.CustomApi.SendTextAsync(appId, OpenId, msg); } return new SuccessResponseMessage(); } // 处理图片请求:回显图片 public override async Task<IResponseMessageBase> OnImageRequestAsync(RequestMessageImage requestMessage) { await Senparc.Weixin.WxOpen.AdvancedAPIs.CustomApi.SendTextAsync(appId, OpenId, "刚才您发送了这张图片:"); await Senparc.Weixin.WxOpen.AdvancedAPIs.CustomApi.SendImageAsync(appId, OpenId, requestMessage.MediaId); return await DefaultResponseMessageAsync(requestMessage); } // 用户进入客服会话事件 public override async Task<IResponseMessageBase> OnEvent_UserEnterTempSessionRequestAsync(RequestMessageEvent_UserEnterTempSession requestMessage) { var msg = @"欢迎您!这条消息来自 Senparc.Weixin 进入客服事件。 ... "; await Senparc.Weixin.WxOpen.AdvancedAPIs.CustomApi.SendTextAsync(appId, OpenId, msg); return await DefaultResponseMessageAsync(requestMessage); } // 小程序卡片消息 public override async Task<IResponseMessageBase> OnMiniProgramPageRequestAsync(RequestMessageMiniProgramPage requestMessage) { var msg = $"您从某个小程序页面来到客服,并且发送了小程序卡片。\r\nTitle:{requestMessage.Title}\r\nAppId:{requestMessage.AppId.Substring(1,5)}...\r\nPagePath:{requestMessage.PagePath}"; await Senparc.Weixin.WxOpen.AdvancedAPIs.CustomApi.SendTextAsync(appId, OpenId, msg); await Senparc.Weixin.WxOpen.AdvancedAPIs.CustomApi.SendImageAsync(appId, OpenId, requestMessage.ThumbMediaId); return await DefaultResponseMessageAsync(requestMessage); } // 兜底响应:所有未处理的消息默认返回这里 public override IResponseMessageBase DefaultResponseMessage(IRequestMessageBase requestMessage) { return new SuccessResponseMessage(); } public override async Task<IResponseMessageBase> DefaultResponseMessageAsync(IRequestMessageBase requestMessage) { return await Task.FromResult(new SuccessResponseMessage()); } }关键设计点说明
GenerateMessageHandler静态委托:这是中间件方式能够直接使用当前类的"桥梁"。中间件需要一种"无参构造消息处理器实例"的能力,因此示例类上定义了与中间件签名完全一致的静态委托(Func<Stream, PostModel, int, IServiceProvider, CustomWxOpenMessageHandler>),中间件注册时直接传入该委托即可。客服消息(主动推送)与被动响应的区别:小程序客服体系下,多数业务回复通过客服消息接口(
CustomApi.SendTextAsync等)主动发送给用户,而不是像公众号那样返回 XML。示例代码最后统一return new SuccessResponseMessage()表示"已成功接收",注释中特别说明:在小程序中像公众号那样回复 XML 是无效的。这是小程序与公众号 MessageHandler 最核心的差异之一。上下文历史消息回显:利用
GetCurrentMessageContext()获取当前用户的MessageContext,读取RequestMessages历史列表与StorageData自定义数据,即可实现"您刚才还发送了如下消息"这类多轮会话体验。消息去重开关:构造函数中通过
OmitRepeatedMessageFunc可以精细控制哪些消息不做去重(示例中用户输入"容错"时不去重),这与 Controller 方式中的OmitRepeatedMessage = true开关配合使用(详见后文)。OnExecutingAsync/OnExecutedAsync钩子:分别在消息处理前、处理后执行,适合做统一的埋点、日志、上下文数据维护,示例中用它完成了StorageData的初始化与计数累加。
2.4 可选的自定义上下文:CustomWxOpenMessageContext
CustomWxOpenMessageContext继承自DefaultWxOpenMessageContext,核心价值在于订阅"上下文过期移除"事件(CustomWxOpenMessageContext.cs):
public class CustomWxOpenMessageContext : DefaultWxOpenMessageContext { public CustomWxOpenMessageContext() { base.MessageContextRemoved += CustomMessageContext_MessageContextRemoved; } void CustomMessageContext_MessageContextRemoved(object sender, WeixinContextRemovedEventArgs<IRequestMessageBase, IResponseMessageBase> e) { // 注意:这个事件不是实时触发的。 // 为了提高效率,根据 WeixinContext 中的算法,过期消息会在过期后、下一条请求执行之前被清除。 var messageContext = e.MessageContext as CustomWxOpenMessageContext; if (messageContext == null) return; // TODO: 这里根据需要执行消息过期时候的逻辑 // Log.InfoFormat("{0}的消息上下文已过期", e.OpenId); // api.SendMessage(e.OpenId, "由于长时间未搭理客服,您的客服状态已退出!"); } }典型用途包括:会话超时后向用户发送"长时间未回复,客服会话已退出"的提示,或清理与该会话关联的临时业务数据。上下文过期时间由GlobalMessageContext.ExpireMinutes控制(示例中设为 3 分钟)。
三、两种承载方式:中间件与 Controller
MessageHandler 有两种承载方式,使其可以被外部(微信服务器)通过 URL 访问到,分别是中间件方式(推荐)和Controller 方式。两种方式所使用的CustomWxOpenMessageHandler是通用的,因此可以随时切换和共存——甚至可以同时注册两个不同路径,分别走两种方式。
3.1 方式一:中间件承载(推荐,最简化)
中间件方式是推荐的方式,也是最简化的方式,无需创建任何新文件,只需在Program.cs文件所有 Senparc.Weixin 注册代码执行后的下方,引入中间件(完整示例见 Program.cs):
app.UseMessageHandlerForWxOpen("/WxOpenAsync", CustomWxOpenMessageHandler.GenerateMessageHandler, options => { options.AccountSettingFunc = context => Senparc.Weixin.Config.SenparcWeixinSetting; });注册完成后,即可通过 URL域名/WxOpenAsync访问 MessageHandler,将该地址设置为小程序后台的消息推送 URL(服务器域名 + 消息推送配置)即可。
在示例项目中,中间件注册还演示了两个进阶选项(Program.cs):
app.UseMessageHandlerForWxOpen("/WxOpenAsync", CustomWxOpenMessageHandler.GenerateMessageHandler, options => { // 获取默认微信配置 var weixinSetting = Senparc.Weixin.Config.SenparcWeixinSetting; // [必填] 指定微信配置 options.AccountSettingFunc = context => weixinSetting; // [可选] 设置文本返回长度限制;如需超长消息可通过客服接口分段回复 options.TextResponseLimitOptions = new TextResponseLimitOptions(2048, weixinSetting.WxOpenAppId); });其中options.AccountSettingFunc为必填项,它决定了中间件在处理请求时使用哪一份微信配置(Token、AppId、EncodingAESKey 等);TextResponseLimitOptions为可选项,用于限制文本返回长度(示例设置为 2048 字符),超长时可通过客服接口分段回复。
中间件方式的底层原理
UseMessageHandlerForWxOpen是 SDK 提供的扩展方法(见 WxOpenMessageHandlerMiddleware.cs),它内部将请求转交给WxOpenMessageHandlerMiddleware<TMC>处理,并以异步方式执行messageHandler.ExecuteAsync():
public static IApplicationBuilder UseMessageHandlerForWxOpen<TMC>(this IApplicationBuilder builder, PathString pathMatch, Func<Stream, PostModel, int, IServiceProvider, MessageHandler<TMC, IRequestMessageBase, IResponseMessageBase>> messageHandler, Action<MessageHandlerMiddlewareOptions<ISenparcWeixinSettingForWxOpen>> options) where TMC : DefaultWxOpenMessageContext, IMessageContext<IRequestMessageBase, IResponseMessageBase>, new() { return builder.UseMessageHandler<WxOpenMessageHandlerMiddleware<TMC>, TMC, PostModel, ISenparcWeixinSettingForWxOpen>(pathMatch, messageHandler, options); }中间件内部封装了完整的两套流程:
- GET 请求(URL 验证):
GetCheckSignature()校验签名,校验通过则直接输出echostr随机字符串返回给微信服务器,完成配置验证(WxOpenMessageHandlerMiddleware.cs); - POST 请求(消息推送):
PostCheckSignature()先做签名校验,校验通过后再进入消息处理管线(WxOpenMessageHandlerMiddleware.cs)。
同时,GetPostModel()从AccountSettingFunc返回的配置中读取WxOpenToken、WxOpenAppId、WxOpenEncodingAESKey,并连同signature、timestamp、nonce、msg_signature等查询参数组装成PostModel(WxOpenMessageHandlerMiddleware.cs),开发者无需手工处理这些细节。
中间件方式的扩展阅读:官方博客《在 .NET Core 2.0/3.0 中使用 MessageHandler 中间件》(同样适用于 .NET 6.0 及以上,用法与公众号相同)。
3.2 方式二:Controller 承载(精细化控制)
当中间件的方式满足不了需求时(例如需要在每个处理步骤之间插入自定义逻辑、干预签名校验、自定义异常处理流程等),可以使用 Controller 将执行过程"展开",对每一步执行进行更加精确的控制或干预。
使用 Controller 方式,需要创建2 个 Action(ActionName 都为Index),分别对应微信后台验证(Get 请求)以及真实消息推送(Post 请求)。项目示例位于 WxOpenController.cs 中。
GET Action:URL 验证
[HttpGet] [ActionName("Index")] public ActionResult Get(PostModel postModel, string echostr) { if (CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, Token)) { return Content(echostr); // 返回随机字符串则表示验证通过 } else { return Content("failed:" + postModel.Signature + "," + MP.CheckSignature.GetSignature(postModel.Timestamp, postModel.Nonce, Token) + "。" + "如果你在浏览器中看到这句话,说明此地址可以被作为微信小程序后台的Url,请注意保持Token一致。"); } }POST Action:消息推送处理
[HttpPost] [ActionName("Index")] public ActionResult Post(PostModel postModel) { if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, Token)) { return Content("参数错误!"); } postModel.Token = Token; // 根据自己后台的设置保持一致 postModel.EncodingAESKey = EncodingAESKey; // 根据自己后台的设置保持一致 postModel.AppId = WxOpenAppId; // 根据自己后台的设置保持一致(必须提供) // v4.2.2 之后的版本,可以设置每个人上下文消息储存的最大数量,防止内存占用过多;如果该参数小于等于0,则不限制 var maxRecordCount = 10; // 自定义 MessageHandler,对微信请求的详细判断操作都在这里面 var messageHandler = new CustomWxOpenMessageHandler(Request.GetRequestMemoryStream(), postModel, maxRecordCount); try { /* 如果需要添加消息去重功能,只需打开 OmitRepeatedMessage 功能,SDK 会自动处理。 * 收到重复消息通常是因为微信服务器没有及时收到响应,会持续发送 2-5 条不等的相同内容的 RequestMessage */ messageHandler.OmitRepeatedMessage = true; // 测试时可开启此记录,帮助跟踪数据,使用前请确保 App_Data 文件夹存在,且有读写权限。 messageHandler.SaveRequestMessageLog(); // 记录 Request 日志(可选) messageHandler.Execute(); // 执行微信处理过程(关键) messageHandler.SaveResponseMessageLog(); // 记录 Response 日志(可选) return new FixWeixinBugWeixinResult(messageHandler); // 为了解决官方微信 5.0 软件换行 bug 暂时添加的方法 // return new WeixinResult(messageHandler); // v0.8+ 常规返回 } catch (Exception ex) { // 将异常详情(含 InnerException 与 ResponseDocument)写入 App_Data 目录下的日志文件 // ... return Content(""); } }Controller 方式相比中间件的优势在于:
- 手动控制执行细节:可以自行决定是否开启
OmitRepeatedMessage消息去重、是否记录 Request/Response 日志(SaveRequestMessageLog/SaveResponseMessageLog)、以及异常发生时的自定义处理(示例中会把完整异常栈与messageHandler.ResponseDocument写入App_Data下的日志文件); - 可以自由扩展 Action:同一个 Controller 中还能承载登录(
OnLogin)、解密(DecodeEncryptedData、DecryptPhoneNumber)、订阅消息(SubscribeMessage)等其它小程序接口,方便统一管理; - 精确控制响应:通过
FixWeixinBugWeixinResult/WeixinResult包装返回结果。
Controller 方式注意事项:
- 同步与异步:示例 Controller 中使用的是同步
messageHandler.Execute(),这是历史兼容用法;目前 SDK 已全面转向异步方法驱动,官方建议使用messageHandler.ExecuteAsync()等异步方法(见 WxOpenController.cs 的提示); - 请求流读取:需要
Request.GetRequestMemoryStream()读取微信推送的请求流,并将其作为CustomWxOpenMessageHandler构造参数之一; - AppId 必须提供:
postModel.AppId必须赋值(示例中直接使用WxOpenAppId),否则签名校验与后续消息处理可能失败。
完成后,即可通过 URL域名/WxOpen访问 MessageHandler,设置为小程序后台的消息推送 URL。
Controller 方式的扩展阅读:官方博客《了解 MessageHandler》(推荐使用全套异步方法,基本用法与公众号相同)。
四、两种承载方式的对比与选型建议
| 对比维度 | 中间件方式(推荐) | Controller 方式 |
|---|---|---|
| 代码量 | 极简,仅Program.cs中一段注册代码 | 需编写 2 个 Action 及完整处理流程 |
| 新增文件 | 无 | 一个 Controller 文件(可复用已有 Controller) |
| URL 路径 | 域名/WxOpenAsync(可自定义 pathMatch) | 域名/WxOpen(由路由决定) |
| 消息去重 | 通过OmitRepeatedMessageFunc配置 | 通过messageHandler.OmitRepeatedMessage = true手动开启 |
| 日志记录 | 内置,无需干预 | 可手动调用SaveRequestMessageLog/SaveResponseMessageLog |
| 签名校验 | 中间件自动完成(GET/POST 均内置) | 开发者手动调用CheckSignature.Check |
| 异常处理 | 中间件默认处理 | 开发者可完全自定义(如写入日志文件) |
| 扩展 Action | 不适用 | 可在同一 Controller 中添加登录、解密等其它接口 |
| 适用场景 | 绝大多数常规项目 | 需要精确干预每个处理步骤、特殊异常流程、混合多接口的场景 |
选型建议:常规项目直接使用中间件方式;当需要"展开"执行过程、对每一步做精确控制或干预(例如自定义日志、自定义异常兜底、与登录/解密等接口共存)时,切换到 Controller 方式。由于两者共用同一个CustomWxOpenMessageHandler,迁移成本几乎为零,甚至可以同时注册两个路径并存使用。
五、前置配置与完整接入步骤
要让 MessageHandler 真正跑起来,还需要完成以下前置配置(以 appsettings.json 为例)。
5.1 小程序账号配置
在appsettings.json的SenparcWeixinSetting节点下,需要配置小程序相关的四个参数:
{ "SenparcWeixinSetting": { "IsDebug": true, "WxOpenAppId": "你的小程序AppId", "WxOpenAppSecret": "你的小程序AppSecret", "WxOpenToken": "你的Token(与小程序后台一致)", "WxOpenEncodingAESKey": "你的EncodingAESKey(与小程序后台一致)" } }参数说明:
| 参数 | 含义 | 与后台的对应关系 |
|---|---|---|
WxOpenAppId | 小程序 AppId | 小程序后台"开发管理 → 开发设置"中获取 |
WxOpenAppSecret | 小程序 AppSecret | 与 AppId 同页获取,注意保密 |
WxOpenToken | 消息校验 Token | 消息推送配置时自定义的 Token,区分大小写 |
WxOpenEncodingAESKey | 消息加解密密钥 | 消息推送配置时生成的 EncodingAESKey,区分大小写 |
说明:配置文件中的占位符(如
#{WxOpenAppId}#)是 Azure DevOps 默认占位符格式,部署时请替换为明文真实值,并删除两侧的#{}符号。WxOpenToken与WxOpenEncodingAESKey必须与小程序后台消息推送配置中的值完全一致(区分大小写),否则签名校验会失败。
5.2 注册与启用
在 Program.cs 中完成 Senparc.Weixin 的注入与注册:
// 注入 builder.Services.AddSenparcWeixin(builder.Configuration); // 注册(app 构建之后) var registerService = app.UseSenparcWeixin(app.Environment, null /* 传入 null 则使用 appsettings 中的 SenparcSetting 配置 */, null /* 传入 null 则使用 appsettings 中的 SenparcWeixinSetting 配置 */, register => { }, (register, weixinSetting) => { // 注册小程序账号信息(示例) register.RegisterWxOpenAccount(weixinSetting, "盛派小助手·小程序"); });5.3 后台消息 URL 配置
最后,在小程序后台的"开发管理 → 开发设置 → 消息推送"中:
- 服务器地址(URL)填写:
https://你的域名/WxOpenAsync(中间件方式)或https://你的域名/WxOpen(Controller 方式); - Token 与 EncodingAESKey 与
appsettings.json中配置保持一致; - 提交后微信服务器会发起一次 GET 请求校验 URL,校验通过后即可开始接收消息推送。
六、扩展阅读
- 中间件方式官方博客:《在 .NET Core 2.0/3.0 中使用 MessageHandler 中间件》(同样适用于 .NET 6.0 及以上,用法与公众号相同);
- Controller 方式官方博客:《了解 MessageHandler》(推荐使用全套异步方法);
- 更多小程序相关指南,可参考 docs/en/guide/wxopen/ 与 docs/zh/guide/wxopen/ 目录下的系列文档;
- 中间件完整源码:WxOpenMessageHandlerMiddleware.cs;
- 基类消息路由与处理方法:WxOpenMessageHandler.cs、WxOpenMessageHandler.Message.cs;
- 完整示例项目:Samples/WxOpen/Senparc.Weixin.Sample.WxOpen/(含 MessageHandlers、Controllers、Program.cs、appsettings.json)。
- 后端
- 即时通讯
- 金融科技
【免费下载链接】WeiXinMPSDK
微信全平台 .NET SDK, Senparc.Weixin for C#,支持 .NET Framework 及 .NET Core、.NET 10.0。已支持微信公众号、小程序、小游戏、微信支付、企业微信/企业号、开放平台、JSSDK、微信周边等全平台。 WeChat SDK for C#.
相关推荐
Senparc.Weixin 小程序 MessageHandler:自定义消息处理器与中间件/Controller 两种承载方式
Senparc.Weixin 小程序 MessageHandler:自定义消息处理器与中间件/Controller 两种承载方式 在微信小程序服务端开发中,客服
后端即时通讯金融科技Senparc.Weixin 企业微信 MessageHandler 实战:自定义消息处理器与中间件、Controller 两种承载方式
Senparc.Weixin 企业微信 MessageHandler 实战:自定义消息处理器与中间件、Controller 两种承载方式 本文基于仓库文档 Me
后端即时通讯金融科技WeiXinMPSDK 企业微信 MessageHandler 开发指南:自定义消息处理类的两种承载方式与源码级实现
WeiXinMPSDK 企业微信 MessageHandler 开发指南:自定义消息处理类的两种承载方式与源码级实现 企业微信(Work)应用接收来自对话窗口的
后端即时通讯金融科技
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考