.NET微信公众号开发-6.0模板消息
一.前言
为了保证用户不受到骚扰,在开发者出现需要主动提醒、通知用户时,才允许开发者在公众平台网站中模板消息库中选择模板,选择后获得模板ID,再根据模板ID向用户主动推送提醒、通知消息。这个意思也就是,我们作为微信商户,不能主动的给用户推送消息,如果这个功能完全开放,那么用户有可能会受到大量的垃圾信息,为了做一个很好的控制,微信那边给我们开放了一个模板消息,通过模板消息我们可以友好的给用户发送一些相关的消息提醒。
二.开发前的准备
2.0查看我们的模板消息是否申请成功。申请成功后你可以看到如下图片:
三.开始编码
一般我们在客户支付成功订单以后,会有一个支付的跳转页面,在我们前面的代码中也有相应的页面,代码如下:
<script type="text/javascript"> //调用微信JS api 支付 function jsApiCall() { WeixinJSBridge.invoke( 'getBrandWCPayRequest', <%=wxJsApiParam%>,//josn串 function (res) { if (res.err_msg == "get_brand_wcpay_request:ok") { var OrderId=$("#OrderId").val(); var orderProductName=$("#orderProductName").val(); var orderMoneySum=$("#orderMoneySum").val(); window.location.href="http://www.baidu.com/PaySkip.aspx?OrderId="+OrderId+"&orderMoneySum="+orderMoneySum+"&orderProductName="+orderProductName;//支付成功后的跳转页面 }else { WeixinJSBridge.call('closeWindow'); } } ); } function callpay() { if (typeof WeixinJSBridge == "undefined") { if (document.addEventListener) { document.addEventListener('WeixinJSBridgeReady', jsApiCall, false); } else if (document.attachEvent) { document.attachEvent('WeixinJSBridgeReady', jsApiCall); document.attachEvent('onWeixinJSBridgeReady', jsApiCall); } } else { jsApiCall(); } } </script>
在上一篇,微信支付成功后,我们有这样一个支付跳转页面,在这个支付跳转页面里,我们可以写一些我们自己的业务逻辑,比如我们今天的主角,模板消息提醒:直接上代码
#region 发送支付成功的消息 ErrorMessage errorMessage; string username = System.Configuration.ConfigurationManager.AppSettings["weixinid"].ToString(); string templateId = System.Configuration.ConfigurationManager.AppSettings["templateId"].ToString(); string first = "您已成功付费,欢迎下次光临。"; string orderMoneySum = Request["orderMoneySum"] ?? "0"; string orderProductName = Request["orderProductName"] ?? "有位停车付费系统测试"; string Remark = "如有问题请致电400-6238-136或直接在微信留言,我们将第一时间为您服务!"; Color color = Color.Red; Tuple<string, string, Color>[] data = new Tuple<string, string, Color>[]{ new Tuple<string,string,Color>("first",first,Color.Blue), new Tuple<string,string,Color>("orderMoneySum",orderMoneySum,Color.Red), new Tuple<string,string,Color>("orderProductName",orderProductName,Color.Orange), new Tuple<string,string,Color>("Remark",Remark,Color.Green) }; long msgId = TemplateMessage.Send(username, openId, templateId, "", color, data, out errorMessage); if (errorMessage.IsSuccess) { Response.Write("errorMessage " + errorMessage.errmsg + "<br/>"); } else { Response.Write("errorMessage.errmsg: " + errorMessage.errmsg + "<br/>"); } #endregion
从上面的代码中我们可以看到TemplateMessage.Send()这个方法是我们发送消息的关键,我们来看看这个方法是怎样的:
#region 发送模板消息 /// <summary> /// 发送模板消息 /// </summary> /// <param name="userName">公众号</param> /// <param name="touser">接收消息的账号</param> /// <param name="templateId">模板id</param> /// <param name="detailUrl">详情地址</param> /// <param name="topColor">顶端颜色</param> /// <param name="data">数据</param> /// <param name="errorMessage">返回发送是否成功</param> /// <returns>返回消息id;如果发送失败,返回-1。</returns> public static long Send(string userName, string touser, string templateId, string detailUrl, Color topColor, Tuple<string, string, Color>[] data, out ErrorMessage errorMessage) { errorMessage = new ErrorMessage(ErrorMessage.ExceptionCode, ""); long id = -1; //校验参数 if (string.IsNullOrWhiteSpace(touser)) { errorMessage.errmsg = "接收消息的账号不能为空。"; return id; } if (string.IsNullOrWhiteSpace(templateId)) { errorMessage.errmsg = "模板id不能为空。"; return id; } if (data == null || data.Length == 0) { errorMessage.errmsg = "模板数据不能为空。"; return id; } foreach (Tuple<string, string, Color> item in data) { if (string.IsNullOrWhiteSpace(item.Item1) || string.IsNullOrWhiteSpace(item.Item2)) { errorMessage.errmsg = "模板数据不能为空。"; return id; } } //获取许可令牌 AccessToken token = AccessToken.Get(userName); if (token == null) { errorMessage.errmsg = "获取许可令牌失败。"; return id; } string url = string.Format(urlForSending, token.access_token); //生成待发送的数据 dynamic postData = new ExpandoObject(); postData.touser = touser; postData.template_id = templateId; postData.url = detailUrl ?? string.Empty; postData.topcolor = Utility.GetColorString(topColor); postData.data = new ExpandoObject(); IDictionary<string, object> dataDict = (IDictionary<string, object>)postData.data; foreach (Tuple<string, string, Color> item in data) { dataDict.Add(item.Item1, new { value = item.Item2, color = Utility.GetColorString(item.Item3) }); } string json = JsonConvert.SerializeObject(postData); //发送数据 string responseContent; if (!HttpHelper.Request(url, out responseContent, httpMethod, json)) { errorMessage.errmsg = "提交数据到微信服务器失败。"; return id; } //解析结果 JObject jo = JObject.Parse(responseContent); JToken jt; if (jo.TryGetValue("errcode", out jt) && jo.TryGetValue("errmsg", out jt)) { errorMessage.errcode = (int)jo["errcode"]; errorMessage.errmsg = (string)jo["errmsg"]; if (jo.TryGetValue("msgid", out jt)) id = (long)jt; } else errorMessage.errmsg = "解析返回结果失败。"; return id; } #endregion
AccessToken token = AccessToken.Get(userName);获取许可令牌上一篇文章中我们已经说过这个类,这里就不多说了,HttpHelper帮助类的代码如下:
/// <summary> /// HttpHelper:http请求与响应辅助类 /// </summary> public static class HttpHelper { /// <summary> /// 向微信服务器发送请求时的编码 /// </summary> public static readonly Encoding RequestEncoding = Encoding.UTF8; /// <summary> /// 微信服务器响应的编码 /// </summary> public static readonly Encoding ResponseEncoding = Encoding.UTF8; /// <summary> /// 向微信服务器提交数据,并获取微信服务器响应的数据 /// </summary> /// <param name="url">服务器地址</param> /// <param name="responseData">返回响应数据</param> /// /// <param name="httpMethod">http方法</param> /// <param name="data">数据</param> /// <returns>返回是否提交成功</returns> public static bool Request(string url, out byte[] responseData, string httpMethod = WebRequestMethods.Http.Get, byte[] data = null) { bool success = false; responseData = null; Stream requestStream = null; HttpWebResponse response = null; Stream responseStream = null; MemoryStream ms = null; try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = httpMethod; if (data != null && data.Length > 0) { request.ContentLength = data.Length; requestStream = request.GetRequestStream(); requestStream.Write(data, 0, data.Length); } response = (HttpWebResponse)request.GetResponse(); //由于微信服务器的响应有时没有正确设置ContentLength,这里不检查ContentLength //if (response.ContentLength > 0) { ms = new MemoryStream(); responseStream = response.GetResponseStream(); int bufferLength = 2048; byte[] buffer = new byte[bufferLength]; int size = responseStream.Read(buffer, 0, bufferLength); while (size > 0) { ms.Write(buffer, 0, size); size = responseStream.Read(buffer, 0, bufferLength); } responseData = ms.ToArray(); } success = true; } finally { if (requestStream != null) requestStream.Close(); if (responseStream != null) responseStream.Close(); if (ms != null) ms.Close(); if (response != null) response.Close(); } return success; } /// <summary> /// 向微信服务器提交数据,并获取微信服务器响应的数据 /// </summary> /// <param name="url">服务器地址</param> /// <param name="responseData">返回响应数据</param> /// /// <param name="httpMethod">http方法</param> /// <param name="data">数据</param> /// <returns>返回是否提交成功</returns> public static bool Request(string url, out byte[] responseData, string httpMethod = WebRequestMethods.Http.Get, string data = null) { byte[] bytes = string.IsNullOrEmpty(data) ? null : RequestEncoding.GetBytes(data); return Request(url, out responseData, httpMethod, (byte[])bytes); } /// <summary> /// 向微信服务器提交数据,并获取微信服务器响应的内容 /// </summary> /// <param name="url">服务器地址</param> /// <param name="responseContent">返回响应内容</param> /// /// <param name="httpMethod">http方法</param> /// <param name="data">数据</param> /// <returns>返回是否提交成功</returns> public static bool Request(string url, out string responseContent, string httpMethod = WebRequestMethods.Http.Get, byte[] data = null) { byte[] responseData; responseContent = string.Empty; bool success = Request(url, out responseData, httpMethod, data); if (success && responseData != null && responseData.Length > 0) responseContent = ResponseEncoding.GetString(responseData); return success; } /// <summary> /// 向微信服务器提交数据,并获取微信服务器响应的内容 /// </summary> /// <param name="url">服务器地址</param> /// <param name="responseContent">返回响应内容</param> /// /// <param name="httpMethod">http方法</param> /// <param name="data">数据</param> /// <returns>返回是否提交成功</returns> public static bool Request(string url, out string responseContent, string httpMethod = WebRequestMethods.Http.Get, string data = null) { byte[] bytes = string.IsNullOrEmpty(data) ? null : RequestEncoding.GetBytes(data); return Request(url, out responseContent, httpMethod, (byte[])bytes); } /// <summary> /// 向微信服务器提交数据 /// </summary> /// <param name="url">服务器地址</param> /// /// <param name="httpMethod">http方法</param> /// <param name="data">数据</param> /// <returns>返回是否提交成功</returns> public static bool Request(string url, string httpMethod = WebRequestMethods.Http.Get, byte[] data = null) { bool success = false; Stream requestStream = null; HttpWebResponse response = null; try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = httpMethod; if (data != null && data.Length > 0) { request.ContentLength = data.Length; requestStream = request.GetRequestStream(); requestStream.Write(data, 0, data.Length); } response = (HttpWebResponse)request.GetResponse(); success = true; } finally { if (requestStream != null) requestStream.Close(); if (response != null) response.Close(); } return success; } /// <summary> /// 组合url,发送数据,然后返回响应字符串 /// </summary> /// <param name="urlFormat">url格式字符串,第一个参数为userName获取到的许可令牌,然后依次为parameters中的参数</param> /// <param name="userName">公众号</param> /// <param name="urlParameters">参数</param> /// <param name="httpMethod">执行请求的http方法</param> /// <param name="data">请求的内容</param> /// <returns>返回响应内容;如果请求失败,或者发生错误,返回空字符串</returns> public static string RequestResponseContent(string urlFormat, string userName, IEnumerable<object> urlParameters = null, string httpMethod = WebRequestMethods.Http.Get, string data = null) { string responseContent = string.Empty; AccessToken token = AccessToken.Get(userName); if (token == null) return responseContent; string url; if (urlParameters == null) url = string.Format(urlFormat, token.access_token); else { List<object> paramList = new List<object>(urlParameters); paramList.Insert(0, token.access_token); url = string.Format(urlFormat, paramList.ToArray()); } HttpHelper.Request(url, out responseContent, httpMethod, (string)data); return responseContent; } /// <summary> /// 组合url,发送数据,然后返回响应的错误消息。 /// 注:错误消息不一定代表失败或者错误。 /// </summary> /// <param name="urlFormat">url格式字符串,第一个参数为userName获取到的许可令牌,然后依次为parameters中的参数</param> /// <param name="userName">公众号</param> /// <param name="urlParameters">参数</param> /// <param name="httpMethod">执行请求的http方法</param> /// <param name="data">请求的内容</param> /// <returns>返回响应的错误消息</returns> public static ErrorMessage RequestErrorMessage(string urlFormat, string userName, IEnumerable<object> urlParameters = null, string httpMethod = WebRequestMethods.Http.Get, string data = null) { string responseContent = RequestResponseContent(urlFormat, userName, urlParameters, httpMethod, data); if (string.IsNullOrWhiteSpace(responseContent)) return new ErrorMessage(ErrorMessage.ExceptionCode, "请求失败。"); else if (ErrorMessage.IsErrorMessage(responseContent)) return ErrorMessage.Parse(responseContent); else return new ErrorMessage(ErrorMessage.ExceptionCode, "解析响应失败。"); } /// <summary> /// 组合url,发送数据,然后返回结果。 /// 注:结果为需要解析的类。 /// </summary> /// <typeparam name="T">返回结果的类型</typeparam> /// <param name="urlFormat">url格式字符串,第一个参数为userName获取到的许可令牌,然后依次为parameters中的参数</param> /// <param name="userName">公众号</param> /// <param name="errorMessage">返回请求是否成功</param> /// <param name="urlParameters">参数</param> /// <param name="httpMethod">执行请求的http方法</param> /// <param name="data">请求的内容</param> /// <returns>返回结果;如果请求失败,或者发生错误,返回null。</returns> public static T RequestParsableResult<T>(string urlFormat, string userName, out ErrorMessage errorMessage, IEnumerable<object> urlParameters = null, string httpMethod = WebRequestMethods.Http.Get, string data = null) where T : IParsable, new() { T result = default(T); errorMessage = new ErrorMessage(ErrorMessage.ExceptionCode, "请求失败。"); string responseContent = RequestResponseContent(urlFormat, userName, urlParameters, httpMethod, data); if (string.IsNullOrWhiteSpace(responseContent)) return result; if (ErrorMessage.IsErrorMessage(responseContent)) errorMessage = ErrorMessage.Parse(responseContent); else { try { result = Utility.Parse<T>(responseContent); if (result != null) errorMessage = new ErrorMessage(ErrorMessage.SuccessCode, "请求成功。"); else errorMessage = new ErrorMessage(ErrorMessage.ExceptionCode, "解析失败。"); } catch { errorMessage = new ErrorMessage(ErrorMessage.ExceptionCode, "解析失败。"); } } return result; } /// <summary> /// 组合url,发送数据,然后返回结果。 /// 注:结果为已知的简单值类型。 /// </summary> /// <typeparam name="T">返回结果的类型</typeparam> /// <param name="urlFormat">url格式字符串,第一个参数为userName获取到的许可令牌,然后依次为parameters中的参数</param> /// <param name="userName">公众号</param> /// <param name="propertyNameInJson">返回结果在json中的键名</param> /// <param name="errorMessage">返回请求是否成功</param> /// <param name="urlParameters">参数</param> /// <param name="httpMethod">执行请求的http方法</param> /// <param name="data">请求的内容</param> /// <returns>返回结果;如果请求失败,或者发生错误,返回default(T)。</returns> public static T RequestValueTypeResult<T>(string urlFormat, string userName, string propertyNameInJson, out ErrorMessage errorMessage, IEnumerable<object> urlParameters = null, string httpMethod = WebRequestMethods.Http.Get, string data = null) where T : struct { errorMessage = new ErrorMessage(ErrorMessage.ExceptionCode, "请求失败。"); string responseContent = RequestResponseContent(urlFormat, userName, urlParameters, httpMethod, data); return ConvertValueTypeResult<T>(responseContent, propertyNameInJson, out errorMessage); } /// <summary> /// 获取值类型的结果 /// </summary> /// <typeparam name="T">返回结果的类型</typeparam> /// <param name="responseContent">响应内容</param> /// <param name="propertyNameInJson">返回结果在json中的键名</param> /// <param name="errorMessage">返回请求是否成功</param> /// <returns>返回结果;如果请求失败,或者发生错误,返回default(T)。</returns> private static T ConvertValueTypeResult<T>(string responseContent, string propertyNameInJson, out ErrorMessage errorMessage) where T : struct { if (string.IsNullOrWhiteSpace(responseContent)) { errorMessage = new ErrorMessage(ErrorMessage.ExceptionCode, "请求失败。"); return default(T); } if (ErrorMessage.IsErrorMessage(responseContent)) errorMessage = ErrorMessage.Parse(responseContent); else errorMessage = new ErrorMessage(ErrorMessage.SuccessCode, "请求成功。"); JObject jo = JObject.Parse(responseContent); JToken jt; if (jo.TryGetValue(propertyNameInJson, out jt)) return ConvertValueTypeResult<T>((string)jt); else return default(T); } /// <summary> /// 获取值类型的结果 /// </summary> /// <typeparam name="T">返回结果的类型</typeparam> /// <param name="responseContent">响应内容</param> /// <param name="propertyNameInJson">返回结果在json中的键名</param> /// <param name="errorMessage">返回请求是否成功</param> /// <returns>返回结果;如果请求失败,或者发生错误,返回default(T)。</returns> private static T ConvertValueTypeResult<T>(string value) where T : struct { Type type = typeof(T); if (type.IsEnum) return (T)Enum.Parse(type, value); else if (type == typeof(sbyte)) return (T)(object)Convert.ToSByte(value); else if (type == typeof(byte)) return (T)(object)Convert.ToByte(value); else if (type == typeof(char)) return (T)(object)Convert.ToChar(value); else if (type == typeof(short)) return (T)(object)Convert.ToInt16(value); else if (type == typeof(ushort)) return (T)(object)Convert.ToUInt16(value); else if (type == typeof(int)) return (T)(object)Convert.ToInt32(value); else if (type == typeof(uint)) return (T)(object)Convert.ToUInt32(value); else if (type == typeof(long)) return (T)(object)Convert.ToInt64(value); else if (type == typeof(ulong)) return (T)(object)Convert.ToUInt64(value); else if (type == typeof(float)) return (T)(object)Convert.ToSingle(value); else if (type == typeof(double)) return (T)(object)Convert.ToDouble(value); else if (type == typeof(decimal)) return (T)(object)Convert.ToDecimal(value); else if (type == typeof(bool)) return (T)(object)Convert.ToBoolean(value); else throw new ArgumentException("不支持的值类型。"); } /// <summary> /// 向微信服务器提交数据 /// </summary> /// <param name="url">服务器地址</param> /// /// <param name="httpMethod">http方法</param> /// <param name="data">数据</param> /// <returns>返回是否提交成功</returns> public static bool Request(string url, string httpMethod = WebRequestMethods.Http.Get, string data = null) { byte[] bytes = string.IsNullOrEmpty(data) ? null : RequestEncoding.GetBytes(data); return Request(url, httpMethod, (byte[])bytes); } /// <summary> /// 上传文件 /// </summary> /// <param name="url">服务器地址</param> /// <param name="filename">文件名(不包含路径)</param> /// <param name="fileData">文件数据</param> /// <param name="formData">表单数据</param> /// <returns>返回服务器的响应字符串</returns> public static string Upload(string url, string filename, byte[] fileData, NameValueCollection formData = null) { string responseContent = string.Empty; if (string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(filename) || fileData == null || fileData.Length == 0) return responseContent; // 边界符 string boundary = "AaB03xAaB03x"; // 开始边界符 byte[] beginBoundary = Encoding.ASCII.GetBytes("--" + boundary + "\r\n"); // 结束符 byte[] endBoundary = Encoding.ASCII.GetBytes("--" + boundary + "--\r\n"); //换行 byte[] newLine = Encoding.ASCII.GetBytes("\r\n"); MemoryStream ms = null; Stream stream = null; HttpWebResponse response = null; StreamReader sr = null; try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = WebRequestMethods.Http.Post; request.ContentType = "multipart/form-data; boundary=" + boundary; // 写入文件 string header = string.Format("Content-Disposition: form-data; filename=\"{0}\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n", filename); byte[] headerbytes = Encoding.UTF8.GetBytes(header); ms = new MemoryStream(); ms.Write(beginBoundary, 0, beginBoundary.Length); ms.Write(headerbytes, 0, headerbytes.Length); ms.Write(fileData, 0, fileData.Length); // 写入表单数据 if (formData != null && formData.Count > 0) { var formItem = "\r\n--" + boundary + "\r\nContent-Disposition: form-data; name=\"{0}\"" + "\r\n\r\n{1}\r\n"; foreach (string key in formData.Keys) { string value = formData[key]; byte[] bytes = Encoding.UTF8.GetBytes(string.Format(formItem, key, value)); ms.Write(bytes, 0, bytes.Length); } } //写入结束边界符 ms.Write(newLine, 0, newLine.Length); ms.Write(endBoundary, 0, endBoundary.Length); request.ContentLength = ms.Length; stream = request.GetRequestStream(); stream.Write(ms.ToArray(), 0, (int)ms.Length); //获取响应 response = (HttpWebResponse)request.GetResponse(); sr = new StreamReader(response.GetResponseStream(), HttpHelper.ResponseEncoding); responseContent = sr.ReadToEnd(); } finally { if (ms != null) ms.Close(); if (stream != null) stream.Close(); if (sr != null) sr.Close(); if (response != null) response.Close(); } return responseContent; } /// <summary> /// 上传文件 /// </summary> /// <param name="url">服务器地址</param> /// <param name="pathname">包含路径的文件名</param> /// <param name="formData">表单数据</param> /// <returns>返回服务器的响应字符串</returns> public static string Upload(string url, string pathname, NameValueCollection formData = null) { string filename = Path.GetFileName(pathname); byte[] data = null; FileStream fs = null; MemoryStream ms = null; try { fs = new FileStream(pathname, FileMode.Open, FileAccess.Read); ms = new MemoryStream(); int bufferLength = 2048; byte[] buffer = new byte[bufferLength]; int size = fs.Read(buffer, 0, bufferLength); while (size > 0) { ms.Write(buffer, 0, size); size = fs.Read(buffer, 0, bufferLength); } data = ms.ToArray(); } finally { if (fs != null) fs.Close(); if (ms != null) ms.Close(); } return Upload(url, filename, data, formData); } }