Share an article based on .NET WeChat SDK
1. Foreword
I am a person who particularly dislikes trouble. I recently encountered WeChat development. I downloaded some WeChat development "frameworks" written by other people, but I was disgusted that the implementation was too bloated.
What I dislike the most is assembling the xml messages returned by WeChat into entity classes, so it will be bloated. Nowadays, lightweight is advocated, so is there any way to avoid a large number of entity classes? existence.
Of course, the packaging is relatively complicated. After reading the official API, looking at the "framework" makes people feel confused and not clear enough.
2. My implementation ideas
My WeChat SDK (I dare not call myself a framework), the most important thing is to achieve two goals:
1 .Lightweight means abandoning entity classes, declaring Entity as little as possible, and reducing the size of the SDK;
2. Simple and clear, that is, the division of SDK classes is consistent with the official API, making it easy to see I understand your intention.
The user sends a request by first POSTing to the WeChat server, and then the WeChat server POSTs to my server. The received message is xml. I guess why it is xml instead of the more lightweight json. It is For better compatibility, xml is more universal after all (if I am wrong, please point it out). When we actively call some APIs of WeChat, it returns json format. Damn it, it’s so impressive. Doesn’t your vice president Zhang Xiaolong know about this? Well, this is actually possible.
In fact, the working principle of calling WeChat is very simple. There is no need to come up with a framework or something. I believe that any qualified programmer can do it.
Our server only needs a GET and a POST to communicate with WeChat. From this point of view, the design is quite user-friendly. I like it. GET is used to connect to the verification and verification of the WeChat service; POST is used to receive messages from the WeChat server, and then assemble the Response and return it.
3. Upload the code
Okay, no more nonsense.
Since the WeChat server Post gives us an xml message and returns json, it needs to be transferred to each other. In this way, there are three types of formats. This is also the reason why the framework is not lightweight enough due to the large number of framework-defined entity classes.
To achieve the first goal, I mainly used the Dynamic feature of .net Framework4.0, and a DynamicXml.cs class that automatically converts xml strings into Dynamic Object, and A DynamicJson.cs class that automatically converts json strings into Dynamic Objects.
After searching hard, I finally found what I wanted.
1. The following is the DynamicXml.cs class, and the file header has the copyright information of the original author.
/*-------------------------------------------------------------------------- * https://www.captechconsulting.com/blog/kevin-hazzard/fluent-xml-parsing-using-cs-dynamic-type-part-1 * 博客园网友 夜の魔王 友情借用此代码,用于微信开发。 * http://www.cnblogs.com/deepleo/*--------------------------------------------------------------------------*/using System;using System.Collections.Generic;using System.Linq;using System.Dynamic;using System.Xml.Linq;using System.Collections;public class DynamicXml : DynamicObject, IEnumerable { private readonly List<XElement> _elements; public DynamicXml(string text) { var doc = XDocument.Parse(text); _elements = new List<XElement> { doc.Root }; } protected DynamicXml(XElement element) { _elements = new List<XElement> { element }; } protected DynamicXml(IEnumerable<XElement> elements) { _elements = new List<XElement>(elements); } public override bool TryGetMember(GetMemberBinder binder, out object result) { result = null; if (binder.Name == "Value") result = _elements[0].Value; else if (binder.Name == "Count") result = _elements.Count; else { var attr = _elements[0].Attribute(XName.Get(binder.Name)); if (attr != null) result = attr; else { var items = _elements.Descendants(XName.Get(binder.Name)); if (items == null || items.Count() == 0) return false; result = new DynamicXml(items); } } return true; } public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { int ndx = (int)indexes[0]; result = new DynamicXml(_elements[ndx]); return true; } public IEnumerator GetEnumerator() { foreach (var element in _elements) yield return new DynamicXml(element); } }
View Code
I didn’t look at this code carefully, but it works anyway , nothing went wrong.
2.The following is the DynamicJson.cs class. The file header has the copyright information of the original author
/*-------------------------------------------------------------------------- * DynamicJson * ver 1.2.0.0 (May. 21th, 2010) * * created and maintained by neuecc <ils@neue.cc> * licensed under Microsoft Public License(Ms-PL) * http://neue.cc/* http://dynamicjson.codeplex.com/ * 博客园网友 夜の魔王 友情借用此代码,用于微信开发。 * http://www.cnblogs.com/deepleo/*--------------------------------------------------------------------------*/using System;using System.Collections;using System.Collections.Generic;using System.Diagnostics;using System.Dynamic;using System.IO;using System.Linq;using System.Reflection;using System.Runtime.Serialization.Json;using System.Text;using System.Xml;using System.Xml.Linq;namespace Codeplex.Data { public class DynamicJson : DynamicObject { private enum JsonType { @string, number, boolean, @object, array, @null } // public static methods /// <summary>from JsonSring to DynamicJson</summary> public static dynamic Parse(string json) { return Parse(json, Encoding.Unicode); } /// <summary>from JsonSring to DynamicJson</summary> public static dynamic Parse(string json, Encoding encoding) { using (var reader = JsonReaderWriterFactory.CreateJsonReader(encoding.GetBytes(json), XmlDictionaryReaderQuotas.Max)) { return ToValue(XElement.Load(reader)); } } /// <summary>from JsonSringStream to DynamicJson</summary> public static dynamic Parse(Stream stream) { using (var reader = JsonReaderWriterFactory.CreateJsonReader(stream, XmlDictionaryReaderQuotas.Max)) { return ToValue(XElement.Load(reader)); } } /// <summary>from JsonSringStream to DynamicJson</summary> public static dynamic Parse(Stream stream, Encoding encoding) { using (var reader = JsonReaderWriterFactory.CreateJsonReader(stream, encoding, XmlDictionaryReaderQuotas.Max, _ => { })) { return ToValue(XElement.Load(reader)); } } /// <summary>create JsonSring from primitive or IEnumerable or Object({public property name:property value})</summary> public static string Serialize(object obj) { return CreateJsonString(new XStreamingElement("root", CreateTypeAttr(GetJsonType(obj)), CreateJsonNode(obj))); } // private static methods private static dynamic ToValue(XElement element) { var type = (JsonType)Enum.Parse(typeof(JsonType), element.Attribute("type").Value); switch (type) { case JsonType.boolean: return (bool)element; case JsonType.number: return (double)element; case JsonType.@string: return (string)element; case JsonType.@object: case JsonType.array: return new DynamicJson(element, type); case JsonType.@null: default: return null; } } private static JsonType GetJsonType(object obj) { if (obj == null) return JsonType.@null; switch (Type.GetTypeCode(obj.GetType())) { case TypeCode.Boolean: return JsonType.boolean; case TypeCode.String: case TypeCode.Char: case TypeCode.DateTime: return JsonType.@string; case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: case TypeCode.SByte: case TypeCode.Byte: return JsonType.number; case TypeCode.Object: return (obj is IEnumerable) ? JsonType.array : JsonType.@object; case TypeCode.DBNull: case TypeCode.Empty: default: return JsonType.@null; } } private static XAttribute CreateTypeAttr(JsonType type) { return new XAttribute("type", type.ToString()); } private static object CreateJsonNode(object obj) { var type = GetJsonType(obj); switch (type) { case JsonType.@string: case JsonType.number: return obj; case JsonType.boolean: return obj.ToString().ToLower(); case JsonType.@object: return CreateXObject(obj); case JsonType.array: return CreateXArray(obj as IEnumerable); case JsonType.@null: default: return null; } } private static IEnumerable<XStreamingElement> CreateXArray<T>(T obj) where T : IEnumerable { return obj.Cast<object>() .Select(o => new XStreamingElement("item", CreateTypeAttr(GetJsonType(o)), CreateJsonNode(o))); } private static IEnumerable<XStreamingElement> CreateXObject(object obj) { return obj.GetType() .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(pi => new { Name = pi.Name, Value = pi.GetValue(obj, null) }) .Select(a => new XStreamingElement(a.Name, CreateTypeAttr(GetJsonType(a.Value)), CreateJsonNode(a.Value))); } private static string CreateJsonString(XStreamingElement element) { using (var ms = new MemoryStream()) using (var writer = JsonReaderWriterFactory.CreateJsonWriter(ms, Encoding.Unicode)) { element.WriteTo(writer); writer.Flush(); return Encoding.Unicode.GetString(ms.ToArray()); } } // dynamic structure represents JavaScript Object/Array readonly XElement xml; readonly JsonType jsonType; /// <summary>create blank JSObject</summary> public DynamicJson() { xml = new XElement("root", CreateTypeAttr(JsonType.@object)); jsonType = JsonType.@object; } private DynamicJson(XElement element, JsonType type) { Debug.Assert(type == JsonType.array || type == JsonType.@object); xml = element; jsonType = type; } public bool IsObject { get { return jsonType == JsonType.@object; } } public bool IsArray { get { return jsonType == JsonType.array; } } /// <summary>has property or not</summary> public bool IsDefined(string name) { return IsObject && (xml.Element(name) != null); } /// <summary>has property or not</summary> public bool IsDefined(int index) { return IsArray && (xml.Elements().ElementAtOrDefault(index) != null); } /// <summary>delete property</summary> public bool Delete(string name) { var elem = xml.Element(name); if (elem != null) { elem.Remove(); return true; } else return false; } /// <summary>delete property</summary> public bool Delete(int index) { var elem = xml.Elements().ElementAtOrDefault(index); if (elem != null) { elem.Remove(); return true; } else return false; } /// <summary>mapping to Array or Class by Public PropertyName</summary> public T Deserialize<T>() { return (T)Deserialize(typeof(T)); } private object Deserialize(Type type) { return (IsArray) ? DeserializeArray(type) : DeserializeObject(type); } private dynamic DeserializeValue(XElement element, Type elementType) { var value = ToValue(element); if (value is DynamicJson) { value = ((DynamicJson)value).Deserialize(elementType); } return Convert.ChangeType(value, elementType); } private object DeserializeObject(Type targetType) { var result = Activator.CreateInstance(targetType); var dict = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.CanWrite) .ToDictionary(pi => pi.Name, pi => pi); foreach (var item in xml.Elements()) { PropertyInfo propertyInfo; if (!dict.TryGetValue(item.Name.LocalName, out propertyInfo)) continue; var value = DeserializeValue(item, propertyInfo.PropertyType); propertyInfo.SetValue(result, value, null); } return result; } private object DeserializeArray(Type targetType) { if (targetType.IsArray) // Foo[] { var elemType = targetType.GetElementType(); dynamic array = Array.CreateInstance(elemType, xml.Elements().Count()); var index = 0; foreach (var item in xml.Elements()) { array[index++] = DeserializeValue(item, elemType); } return array; } else // List<Foo> { var elemType = targetType.GetGenericArguments()[0]; dynamic list = Activator.CreateInstance(targetType); foreach (var item in xml.Elements()) { list.Add(DeserializeValue(item, elemType)); } return list; } } // Delete public override bool TryInvoke(InvokeBinder binder, object[] args, out object result) { result = (IsArray) ? Delete((int)args[0]) : Delete((string)args[0]); return true; } // IsDefined, if has args then TryGetMember public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { if (args.Length > 0) { result = null; return false; } result = IsDefined(binder.Name); return true; } // Deserialize or foreach(IEnumerable) public override bool TryConvert(ConvertBinder binder, out object result) { if (binder.Type == typeof(IEnumerable) || binder.Type == typeof(object[])) { var ie = (IsArray) ? xml.Elements().Select(x => ToValue(x)) : xml.Elements().Select(x => (dynamic)new KeyValuePair<string, object>(x.Name.LocalName, ToValue(x))); result = (binder.Type == typeof(object[])) ? ie.ToArray() : ie; } else { result = Deserialize(binder.Type); } return true; } private bool TryGet(XElement element, out object result) { if (element == null) { result = null; return false; } result = ToValue(element); return true; } public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { return (IsArray) ? TryGet(xml.Elements().ElementAtOrDefault((int)indexes[0]), out result) : TryGet(xml.Element((string)indexes[0]), out result); } public override bool TryGetMember(GetMemberBinder binder, out object result) { return (IsArray) ? TryGet(xml.Elements().ElementAtOrDefault(int.Parse(binder.Name)), out result) : TryGet(xml.Element(binder.Name), out result); } private bool TrySet(string name, object value) { var type = GetJsonType(value); var element = xml.Element(name); if (element == null) { xml.Add(new XElement(name, CreateTypeAttr(type), CreateJsonNode(value))); } else { element.Attribute("type").Value = type.ToString(); element.ReplaceNodes(CreateJsonNode(value)); } return true; } private bool TrySet(int index, object value) { var type = GetJsonType(value); var e = xml.Elements().ElementAtOrDefault(index); if (e == null) { xml.Add(new XElement("item", CreateTypeAttr(type), CreateJsonNode(value))); } else { e.Attribute("type").Value = type.ToString(); e.ReplaceNodes(CreateJsonNode(value)); } return true; } public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) { return (IsArray) ? TrySet((int)indexes[0], value) : TrySet((string)indexes[0], value); } public override bool TrySetMember(SetMemberBinder binder, object value) { return (IsArray) ? TrySet(int.Parse(binder.Name), value) : TrySet(binder.Name, value); } public override IEnumerable<string> GetDynamicMemberNames() { return (IsArray) ? xml.Elements().Select((x, i) => i.ToString()) : xml.Elements().Select(x => x.Name.LocalName); } /// <summary>Serialize to JsonString</summary> public override string ToString() { // <foo type="null"></foo> is can't serialize. replace to <foo type="null" /> foreach (var elem in xml.Descendants().Where(x => x.Attribute("type").Value == "null")) { elem.RemoveNodes(); } return CreateJsonString(new XStreamingElement("root", CreateTypeAttr(jsonType), xml.Elements())); } } }
View Code
I still haven’t looked at this code carefully, but it works anyway without any errors.
With this core obstacle solved, the rest will proceed naturally.
3. Basic support for API packaging
/*-------------------------------------------------------------------------- * BasicAPI.cs *Auth:deepleo * Date:2013.12.31 * Email:2586662969@qq.com *--------------------------------------------------------------------------*/using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Net.Http;using Codeplex.Data;using System.IO;namespace Deepleo.Weixin.SDK { /// <summary> /// 对应微信API的 "基础支持" /// </summary> public class BasicAPI { /// <summary> /// 检查签名是否正确: /// http://mp.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97 /// </summary> /// <param name="signature"></param> /// <param name="timestamp"></param> /// <param name="nonce"></param> /// <param name="token">AccessToken</param> /// <returns> /// true: check signature success /// false: check failed, 非微信官方调用! /// </returns> public static bool CheckSignature(string signature, string timestamp, string nonce, string token, out string ent) { var arr = new[] { token, timestamp, nonce }.OrderBy(z => z).ToArray(); var arrString = string.Join("", arr); var sha1 = System.Security.Cryptography.SHA1.Create(); var sha1Arr = sha1.ComputeHash(Encoding.UTF8.GetBytes(arrString)); StringBuilder enText = new StringBuilder(); foreach (var b in sha1Arr) { enText.AppendFormat("{0:x2}", b); } ent = enText.ToString(); return signature == enText.ToString(); } /// <summary> /// 获取AccessToken /// http://mp.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96access_token /// </summary> /// <param name="grant_type"></param> /// <param name="appid"></param> /// <param name="secrect"></param> /// <returns>access_toke</returns> public static dynamic GetAccessToken( string appid, string secrect) { var url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&appid={1}&secret={2}", "client_credential", appid, secrect); var client = new HttpClient(); var result = client.GetAsync(url).Result; if (!result.IsSuccessStatusCode) return string.Empty; var token = DynamicJson.Parse(result.Content.ReadAsStringAsync().Result); return token; } /// <summary> /// 上传多媒体文件 /// http://mp.weixin.qq.com/wiki/index.php?title=%E4%B8%8A%E4%BC%A0%E4%B8%8B%E8%BD%BD%E5%A4%9A%E5%AA%92%E4%BD%93%E6%96%87%E4%BB%B6 /// 1.上传的媒体文件限制: ///图片(image) : 1MB,支持JPG格式 ///语音(voice):1MB,播放长度不超过60s,支持MP4格式 ///视频(video):10MB,支持MP4格式 ///缩略图(thumb):64KB,支持JPG格式 ///2.媒体文件在后台保存时间为3天,即3天后media_id失效 /// </summary> /// <param name="token"></param> /// <param name="type"></param> /// <param name="file"></param> /// <returns>media_id</returns> public static string UploadMedia(string token, string type, string file) { var url = string.Format("http://api.weixin.qq.com/cgi-bin/media/upload?access_token={0}&type={1}&filename={2}", token, type, Path.GetFileName(file)); var client = new HttpClient(); var result = client.PostAsync(url, new StreamContent(new FileStream(file, FileMode.Open, FileAccess.Read))); if (!result.Result.IsSuccessStatusCode) return string.Empty; var media = DynamicJson.Parse(result.Result.Content.ReadAsStringAsync().Result); return media.media_id; } } }
View Code
4.发送消息包装
/*-------------------------------------------------------------------------- * SendMessageAPI.cs *Auth:deepleo * Date:2013.12.31 * Email:2586662969@qq.com *--------------------------------------------------------------------------*/using System;using System.Collections.Generic;using System.Linq;using System.Text;using Codeplex.Data;using System.Net;using System.Net.Http;namespace Deepleo.Weixin.SDK { /// <summary> /// 对应微信API的 "发送消息” /// </summary> public class SendMessageAPI { /// <summary> /// 被动回复消息 /// </summary> /// <param name="message">微信服务器推送的消息</param> /// <param name="executor">用户自定义的消息执行者</param> /// <returns></returns> public static string Relay(WeixinMessage message, IWeixinExecutor executor) { return executor.Execute(message); } /// <summary> /// 主动发送客服消息 /// http://mp.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E5%AE%A2%E6%9C%8D%E6%B6%88%E6%81%AF /// 当用户主动发消息给公众号的时候 /// 开发者在一段时间内(目前为24小时)可以调用客服消息接口,在24小时内不限制发送次数。 /// </summary> /// <param name="token"></param> /// <param name="msg">json格式的消息,具体格式请参考微信官方API</param> /// <returns></returns> public static bool Send(string token, string msg) { var client = new HttpClient(); var task = client.PostAsync(string.Format("https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={0}", token), new StringContent(msg)).Result; return task.IsSuccessStatusCode; } } }
View Code
5.其他代码就不一一贴出来了。可以在文章最后自行下载完整代码查阅。
6.处理与微信服务器通信的WeixinController.cs,WeixinExecutor.cs
using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;using System.Web;using System.Web.Mvc;using System.Xml.Linq;using Deepleo.Weixin.SDK;using Deepleo.NewTon.Web.Services;using System.Xml;using Deepleo.NewTon.Framework.Services.Admin;using Deepleo.Log;namespace Deepleo.NewTon.Web.Controllers { public class WeixinController : Controller { public WeixinController() { } /// <summary> /// 微信后台验证地址(使用Get),微信后台的“接口配置信息”的Url /// </summary> [HttpGet] [ActionName("Index")] public ActionResult Get(string signature, string timestamp, string nonce, string echostr) { var token = new SettingsService().Get().Token; if (string.IsNullOrEmpty(_token)) return Content("请先设置Token!"); var ent = ""; if (!BasicAPI.CheckSignature(signature, timestamp, nonce, _token, out ent)) { new WeixinLogService().Create(new Framework.Entities.WeixinLog("get(failed)", string.Format("(get weixin)signature:{0},timestamp:{1},nonce:{2},echostr:{3},ent:{4},token:{5}", signature, timestamp, nonce, echostr, ent, _token))); return Content("参数错误!"); } return Content(echostr); //返回随机字符串则表示验证通过 } /// <summary> /// 用户发送消息后,微信平台自动Post一个请求到这里,并等待响应XML。 /// </summary> [HttpPost] [ActionName("Index")] public ActionResult Post(string signature, string timestamp, string nonce, string echostr) { WeixinMessage message = null; using (var streamReader = new StreamReader(Request.InputStream)) { message = AcceptMessageAPI.Parse(streamReader.ReadToEnd()); } var response = new WeixinExecutor().Execute(message); return new ContentResult { Content = response, ContentType = "text/xml", ContentEncoding = System.Text.UTF8Encoding.UTF8 }; } } }
View Code
/*-------------------------------------------------------------------------- * WeixinExecutor.cs *Auth:deepleo * Date:2013.12.31 * Email:2586662969@qq.com *--------------------------------------------------------------------------*/using System;using System.Collections.Generic;using System.Linq;using System.Web;using Deepleo.Weixin.SDK;using System.Text;using System.Text.RegularExpressions;namespace Deepleo.Weixin { public class WeixinExecutor : IWeixinExecutor { public WeixinExecutor() { } public string Execute(WeixinMessage message) { var result = ""; string openId = message.Body.FromUserName.Value; var myUserName = message.Body.ToUserName.Value; switch (message.Type) { case WeixinMessageType.Text: string userMessage = message.Body.Content.Value; result = RepayText(openId, myUserName, "欢迎使用"); break; case WeixinMessageType.Event: string eventType = message.Body.Event.Value.ToLower(); string eventKey = message.Body.EventKey.Value; switch (eventType) { case "subscribe": result = RepayText(openId, myUserName, "欢迎订阅"); break; case "unsubscribe": result = RepayText(openId, myUserName, "欢迎再来"); break; case "scan": result = RepayText(openId, myUserName, "欢迎使用"); break; case "location"://用户进入应用时记录用户地理位置 #region location var lat = message.Body.Latitude.Value.ToString(); var lng = message.Body.Longitude.Value.ToString(); var pcn = message.Body.Precision.Value.ToString(); #endregion break; case "click": switch (eventKey)//refer to: Recources/menu.json { case "myaccount": #region 我的账户 result = RepayText(openId, myUserName, "我的账户."); #endregion break; default: result = string.Format("<xml><ToUserName><![CDATA[{0}]]></ToUserName>" + "<FromUserName><![CDATA[{1}]]></FromUserName>" + "<CreateTime>{2}</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>" + "<Content><![CDATA[{3}]]></Content>" + "</xml>", openId, myUserName, DateTime.Now.ToBinary(), "没有响应菜单事件"); break; } break; } break; default: result = string.Format("<xml><ToUserName><![CDATA[{0}]]></ToUserName>" + "<FromUserName><![CDATA[{1}]]></FromUserName>" + "<CreateTime>{2}</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>" + "<Content><![CDATA[{3}]]></Content>" + "</xml>", openId, myUserName, DateTime.Now.ToBinary(), string.Format("未处理消息类型:{0}", message.Type)); break; } return result; } private string RepayText(string toUserName, string fromUserName, string content) { return string.Format("<xml><ToUserName><![CDATA[{0}]]></ToUserName>" + "<FromUserName><![CDATA[{1}]]></FromUserName>" + "<CreateTime>{2}</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>" + "<Content><![CDATA[{3}]]></Content>" + "</xml>", toUserName, fromUserName, DateTime.Now.ToBinary(), content); } private string RepayNews(string toUserName, string fromUserName, List<WeixinNews> news) { var couponesBuilder = new StringBuilder(); couponesBuilder.Append(string.Format("<xml><ToUserName><![CDATA[{0}]]></ToUserName>" + "<FromUserName><![CDATA[{1}]]></FromUserName>" + "<CreateTime>{2}</CreateTime>" + "<MsgType><![CDATA[news]]></MsgType>" + "<ArticleCount>{3}</ArticleCount><Articles>", toUserName, fromUserName, DateTime.Now.ToBinary(), news.Count )); foreach (var c in news) { couponesBuilder.Append(string.Format("<item><Title><![CDATA[{0}]]></Title>" + "<Description><![CDATA[{1}]]></Description>" + "<PicUrl><![CDATA[{2}]]></PicUrl>" + "<Url><![CDATA[{3}]]></Url>" + "</item>", c.Title, c.Description, c.PicUrl, c.Url )); } couponesBuilder.Append("</Articles></xml>"); return couponesBuilder.ToString(); } } public class WeixinNews { public string Title { set; get; } public string Description { set; get; } public string PicUrl { set; get; } public string Url { set; get; } } }
The above is the detailed content of Share an article based on .NET WeChat SDK. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



The new Windows 11 SDK for build 22523 revealed that Microsoft is developing a new blur effect for Windows 11. This effect is called Tabbed, and is in addition to Acrylic and Mica. The new DWMWA_SYSTEMBACKDROP_TYPE in the 22523 SDK, Mica, Acrylic’s public Win32 API and their weird new “tab” mix: pic.twitter.com/dbsu7ZFiIi — It’s All Back (@StartIsBack) December 15, 2021 Available in the following SDK’s Sample application

Master the essential skills for secondary development of Java Hikvision SDK Introduction: With the rapid development of information technology, video surveillance systems have been widely used in various fields. As the leading domestic video surveillance solution provider, Hikvision’s products and technologies have always occupied an important position in the market. In order to meet the needs of different projects, Hikvision provides SDK for developers to carry out secondary development. This article will introduce some essential skills for mastering the secondary development of Java Hikvision SDK, and attach corresponding code examples. 1. Understand Hikvision

The WindowsAppSDK is a set of tools and APIs that developers can use in their Windows applications to provide "consistent" functionality across a variety of devices using Windows 10 (version 1809 and later) and Windows 11. It's really important to understand that it doesn't replace existing application types like .NET or Windows SDK, it just provides a unified API toolset that can be used to complement your existing applications. Today, Microsoft released version 1.2 of Windows App SDK with many new features. The highlight of this release may be third-party developers

The full name of sdk is "Software Development Kit", which means "software development kit" in Chinese. It is a set of tools provided by manufacturers of hardware platforms, operating systems (OS) or programming languages. SDKs assist software developers in creating applications for specific platforms, systems or programming languages. A basic SDK usually consists of a compiler, debugger, and application programming interface (API), but may also include other content, such as: documentation, libraries, runtime/development environment, testing/analysis tools, network protocols, etc.

PHP is an open source scripting language that is widely used in web development and server-side programming, especially in WeChat development. Today, more and more companies and developers are starting to use PHP for WeChat development because it has become a truly easy-to-learn and easy-to-use development language. In WeChat development, message encryption and decryption are a very important issue because they involve data security. For messages without encryption and decryption methods, hackers can easily obtain the data, posing a threat to users.

Installation and use of WeChat Mini Program PHPSDK With the rapid development of the mobile Internet, WeChat Mini Program has become a new way for more and more companies to conduct business and promote products. WeChat Mini Program PHPSDK provides developers with convenient and fast development tools, which can greatly improve development efficiency. This article will introduce the installation and use of WeChat applet PHPSDK. 1. Install SDK 1. Download the project file on GitHub. WeChat applet PHPSDK is an open source project. Developers can download it on GitHub.

The SDK in Linux is a folder that contains tools and resources such as compilers, debuggers, library files, header files, etc. SDK is the abbreviation of "software development kit", which means software development kit. It is an integrated environment provided for developers to develop and build applications, especially those running on the Linux operating system.

With the rapid development of the Internet, a large amount of data needs to be transmitted and processed. Therefore, the messaging system, as one of the classic applications of data transmission and processing, has become an indispensable part of the Internet architecture. Kafka is widely used in enterprise data architecture as a high-performance, distributed, scalable messaging system that supports real-time data processing. When using Kafka, an important issue is how to call Kafka's API. The development team provides open source clients in multiple languages for this purpose, and the open source KafkaS implemented in PHP
