access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。正常情况下access_token有效期为7200秒,重复获取将导致上次获取的access_token失效。由于获取access_token的api调用次数非常有限,建议开发者全局存储与更新access_token,频繁刷新access_token会导致api调用受限,影响自身业务。
http://mp.weixin.qq.com/wiki/index.php?title=%E8%8E%B7%E5%8F%96access_token
按照官方给我们的说法,所有后续的操作,都要用到Access_Token,于是决定先解决这个问题。官方给的API如下:
这个过程,只要一个返回值,所以,代码也先简单写一下。只为Get到代码嘛。
继续在WxController中新建一个GetToken,返回值依然用String,便于测试。
private bool CheckHTTPS(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; } public string GetToken() { string strResult = ""; string strAppId = "wxfb90abc********"; string strAppsecret = "51b12f574e2c918571***********"; string url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"; url+= "&appid="+strAppId; url += "&secret=" + strAppsecret; ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckHTTPS); HttpWebRequest req = (HttpWebRequest)WebRequest.CreateDefault(new Uri(url)); req.ServicePoint.Expect100Continue = false; req.Method = "GET"; req.KeepAlive = true; req.UserAgent = "Wxop4Net"; req.Timeout = 100000; req.ContentType = "application/x-www-form-urlencoded;charset=utf-8"; HttpWebResponse rsp = (HttpWebResponse)req.GetResponse(); Encoding encoding = Encoding.UTF8; System.IO.Stream stream = null; StreamReader reader = null; try { // 以字符流的方式读取HTTP响应 stream = rsp.GetResponseStream(); reader = new StreamReader(stream, encoding); strResult = reader.ReadToEnd(); } finally { // 释放资源 if (reader != null) reader.Close(); if (stream != null) stream.Close(); if (rsp != null) rsp.Close(); } return strResult; }
这里多了一个方法,因为鹅厂用的是HTTPS的协议,为了减少验证的麻烦,回调方法直接返回ture,方便使用。此时,发布一下代码,上传服务器,访问一下。
结果返回了一个Token,这样,验证的基础方法就获得了,但此时要考虑的问题好像该多了一些。
第一,获取Token需要保存数据,进行网络存储,以备调用接口时使用。
第二,请求过程中,请求代码过于复杂,没有结构,该梳理一下。
基于以上,先解决第二个,需要把请求代码,和数据格式进得重新整理,于是决定将微信请求的API方法提取出来,单独一个类库(注:网上有很多现成的类库,但个人喜欢能看到内部,所以自己尝试着写一写)。
项目由原有的一个MVC项目,增加一个类库项目,定名WxpSdk。
第一步,建立网络工具类,WebUtils.cs,这个,各种项目经常用到,直接分离出一个就可以。
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Web; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; namespace Wxop.Api.Util { /// <summary> /// 网络工具类。 /// </summary> public sealed class WebUtils { private int _timeout = 100000; /// <summary> /// 请求与响应的超时时间 /// </summary> public int Timeout { get { return this._timeout; } set { this._timeout = value; } } /// <summary> /// 执行HTTP POST请求。 /// </summary> /// <param name="url">请求地址</param> /// <param name="parameters">请求参数</param> /// <returns>HTTP响应</returns> public string DoPost(string url, IDictionary<string, string> parameters) { HttpWebRequest req = GetWebRequest(url, "POST"); req.ContentType = "application/x-www-form-urlencoded;charset=utf-8"; byte[] postData = Encoding.UTF8.GetBytes(BuildQuery(parameters)); System.IO.Stream reqStream = req.GetRequestStream(); reqStream.Write(postData, 0, postData.Length); reqStream.Close(); HttpWebResponse rsp = (HttpWebResponse)req.GetResponse(); Encoding encoding = Encoding.GetEncoding(rsp.CharacterSet); return GetResponseAsString(rsp, encoding); } /// <summary> /// 执行HTTP GET请求。 /// </summary> /// <param name="url">请求地址</param> /// <param name="parameters">请求参数</param> /// <returns>HTTP响应</returns> public string DoGet(string url, IDictionary<string, string> parameters) { if (parameters != null && parameters.Count > 0) { if (url.Contains("?")) { url = url + "&" + BuildQuery(parameters); } else { url = url + "?" + BuildQuery(parameters); } } HttpWebRequest req = GetWebRequest(url, "GET"); req.ContentType = "application/x-www-form-urlencoded;charset=utf-8"; HttpWebResponse rsp = (HttpWebResponse)req.GetResponse(); Encoding encoding = Encoding.UTF8; return GetResponseAsString(rsp, encoding); } public bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; } public HttpWebRequest GetWebRequest(string url, string method) { HttpWebRequest req = null; if (url.Contains("https")) { ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult); req = (HttpWebRequest)WebRequest.CreateDefault(new Uri(url)); } else { req = (HttpWebRequest)WebRequest.Create(url); } req.ServicePoint.Expect100Continue = false; req.Method = method; req.KeepAlive = true; req.UserAgent = "Wxop4Net"; req.Timeout = this._timeout; return req; } /// <summary> /// 把响应流转换为文本。 /// </summary> /// <param name="rsp">响应流对象</param> /// <param name="encoding">编码方式</param> /// <returns>响应文本</returns> public string GetResponseAsString(HttpWebResponse rsp, Encoding encoding) { System.IO.Stream stream = null; StreamReader reader = null; try { // 以字符流的方式读取HTTP响应 stream = rsp.GetResponseStream(); reader = new StreamReader(stream, encoding); return reader.ReadToEnd(); } finally { // 释放资源 if (reader != null) reader.Close(); if (stream != null) stream.Close(); if (rsp != null) rsp.Close(); } } /// <summary> /// 组装GET请求URL。 /// </summary> /// <param name="url">请求地址</param> /// <param name="parameters">请求参数</param> /// <returns>带参数的GET请求URL</returns> public string BuildGetUrl(string url, IDictionary<string, string> parameters) { if (parameters != null && parameters.Count > 0) { if (url.Contains("?")) { url = url + "&" + BuildQuery(parameters); } else { url = url + "?" + BuildQuery(parameters); } } return url; } /// <summary> /// 组装普通文本请求参数。 /// </summary> /// <param name="parameters">Key-Value形式请求参数字典</param> /// <returns>URL编码后的请求数据</returns> public static string BuildQuery(IDictionary<string, string> parameters) { StringBuilder postData = new StringBuilder(); bool hasParam = false; IEnumerator<KeyValuePair<string, string>> dem = parameters.GetEnumerator(); while (dem.MoveNext()) { string name = dem.Current.Key; string value = dem.Current.Value; // 忽略参数名或参数值为空的参数 if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(value)) { if (hasParam) { postData.Append("&"); } postData.Append(name); postData.Append("="); postData.Append(HttpUtility.UrlEncode(value, Encoding.UTF8)); hasParam = true; } } return postData.ToString(); } } }
第二步,因为微信平台上,各种错误都会返回ErrCode,于是,先建立响应类的基类WxopResponse.cs
using System; using System.Xml.Serialization; namespace Wxop.Api { [Serializable] public abstract class WxopResponse { /// <summary> /// 错误码 {"errcode":40013,"errmsg":"invalid appid"} /// </summary> [XmlElement("errcode")] public string ErrCode { get; set; } /// <summary> /// 错误信息 /// </summary> [XmlElement("errmsg")] public string ErrMsg { get; set; } /// <summary> /// 响应原始内容 /// </summary> public string Body { get; set; } /// <summary> /// HTTP GET请求的URL /// </summary> public string ReqUrl { get; set; } /// <summary> /// 响应结果是否错误 /// </summary> public bool IsError { get { return !string.IsNullOrEmpty(this.ErrCode); } } } }
建立AccessToken的响应类AccessTokenGetResponse.cs
using System; using System.Xml.Serialization; using Wxop.Api.Domain; namespace Wxop.Api.Response { /// <summary> /// UserBuyerGetResponse. /// </summary> public class AccessTokenGetResponse : WxopResponse { [XmlElement("access_token")] public string AccessToken { get; set; } [XmlElement("expires_in")] public int ExpiresIn { get; set; } } }
接下来,建立一个请求类的接口 IWxopRequest.cs
using System.Collections.Generic; namespace Wxop.Api.Request { /// <summary> /// WXOP请求接口。 /// </summary> public interface IWxopRequest<T> where T : WxopResponse { /// <summary> /// 获取所有的Key-Value形式的文本请求参数字典。其中: /// Key: 请求参数名 /// Value: 请求参数文本值 /// </summary> /// <returns>文本请求参数字典</returns> IDictionary<string, string> GetParameters(); } }
接下来,建立一个请求客户端的接口IWxopClient.css,一个默认客户端 DefaultWxopClient.cs
using System; using Wxop.Api.Request; namespace Wxop.Api { /// <summary> /// WXOP客户端。 /// </summary> public interface IWxopClient { /// <summary> /// 执行WXOP公开API请求。 /// </summary> /// <typeparam name="T">领域对象</typeparam> /// <param name="request">具体的WXOP API请求</param> /// <returns>领域对象</returns> T Execute<T>(IWxopRequest<T> request) where T : WxopResponse; } }
using System; using System.Collections; using System.Collections.Generic; using System.Xml; using Jayrock.Json.Conversion; using Wxop.Api.Parser; using Wxop.Api.Request; using Wxop.Api.Util; namespace Wxop.Api { /// <summary> /// 基于REST的WXOP客户端。 /// </summary> public class DefaultWxopClient : IWxopClient { public const string FORMAT_XML = "xml"; public const string GRANT_TYPE="grant_type"; public const string APP_ID="appid"; public const string APP_SECRET="secret"; private string serverUrl; private string appKey; private string appSecret; private string format = FORMAT_XML; private WebUtils webUtils; private bool disableParser = false; // 禁用响应结果解释 #region DefaultWxopClient Constructors public DefaultWxopClient(string serverUrl, string appKey, string appSecret) { this.appKey = appKey; this.appSecret = appSecret; this.serverUrl = serverUrl; this.webUtils = new WebUtils(); } public DefaultWxopClient(string serverUrl, string appKey, string appSecret, string format) : this(serverUrl, appKey, appSecret) { this.format = format; } #endregion #region IWxopClient Members public T Execute<T>(IWxopRequest<T> request) where T : WxopResponse { try { return DoExecute<T>(request); } catch (Exception e) { throw e; } } #endregion private T DoExecute<T>(IWxopRequest<T> request) where T : WxopResponse { // 添加协议级请求参数 WxopDictionary txtParams = new WxopDictionary(request.GetParameters()); txtParams.Add(GRANT_TYPE, "client_credential"); txtParams.Add(APP_ID, appKey); txtParams.Add(APP_SECRET, appSecret); string body = webUtils.DoGet(this.serverUrl, txtParams); // 解释响应结果 T rsp; if (disableParser) { rsp = Activator.CreateInstance<T>(); rsp.Body = body; } else { if (FORMAT_XML.Equals(format)) { IWxopParser tp = new WxopXmlParser(); rsp = tp.Parse<T>(body); } else { IWxopParser tp = new WxopJsonParser(); rsp = tp.Parse<T>(body); } } return rsp; } } }
这里边用到了Json解析,直接使用的Jayrock.Json组件。建立了一个JSON通用解释器。IWxopParser.cs IWxopReader.cs WxopJsonParser.cs
using System; namespace Wxop.Api.Parser { /// <summary> /// WXOP API响应解释器接口。响应格式可以是XML, JSON等等。 /// </summary> public interface IWxopParser { /// <summary> /// 把响应字符串解释成相应的领域对象。 /// </summary> /// <typeparam name="T">领域对象</typeparam> /// <param name="body">响应字符串</param> /// <returns>领域对象</returns> T Parse<T>(string body) where T : WxopResponse; } }
using System; using System.Collections; namespace Wxop.Api.Parser { public delegate object DWxopConvert(IWxopReader reader, Type type); /// <summary> /// WXOP API响应读取器接口。响应格式可以是XML, JSON等等。 /// </summary> public interface IWxopReader { /// <summary> /// 判断响应中是否包含指定的属性。 /// </summary> /// <param name="name">属性名称</param> /// <returns>true/false</returns> bool HasReturnField(object name); /// <summary> /// 获取值类型属性的值。 /// </summary> /// <param name="name">属性名称</param> /// <returns>值对象</returns> object GetPrimitiveObject(object name); /// <summary> /// 获取引用类型的值。 /// </summary> /// <param name="name">属性名称</param> /// <param name="type">引用类型</param> /// <param name="convert">转换器</param> /// <returns>引用对象</returns> object GetReferenceObject(object name, Type type, DWxopConvert convert); /// <summary> /// 获取列表类型的值。 /// </summary> /// <param name="listName">列表属性名称</param> /// <param name="itemName">列表项名称</param> /// <param name="type">引用类型</param> /// <param name="convert">转换器</param> /// <returns>列表对象</returns> IList GetListObjects(string listName, string itemName, Type type, DWxopConvert convert); } }
using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Xml.Serialization; using Jayrock.Json.Conversion; namespace Wxop.Api.Parser { /// <summary> /// WXOP JSON响应通用解释器。 /// </summary> public class WxopJsonParser : IWxopParser { private static readonly Dictionary<string, Dictionary<string, WxopAttribute>> attrs = new Dictionary<string, Dictionary<string, WxopAttribute>>(); #region IWxopParser Members public T Parse<T>(string body) where T : WxopResponse { return Parse<T>(body, false); } /// <summary> /// JSON解释器,将JSON结果转为相关类 本方法为添加重载方法 Lasko 2013.11.22 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="body"></param> /// <param name="removeRoot"></param> /// <returns></returns> public T Parse<T>(string body, bool removeRoot) where T : WxopResponse { T rsp = null; IDictionary json = JsonConvert.Import(body) as IDictionary; if (json != null) { IDictionary data = null; if (removeRoot) { //忽略根节点的名称 foreach (object key in json.Keys) { data = json[key] as IDictionary; break; } } else { data = json; } if (data != null) { IWxopReader reader = new WxopJsonReader(data); if (removeRoot){ rsp = (T)FromJson(reader, typeof(T)); } else { rsp = (T)FromJson(reader, typeof(T)); } } } if (rsp == null) { rsp = Activator.CreateInstance<T>(); } if (rsp != null) { rsp.Body = body; } return rsp; } #endregion private Dictionary<string, WxopAttribute> GetWxopAttributes(Type type) { Dictionary<string, WxopAttribute> tas = null; bool inc = attrs.TryGetValue(type.FullName, out tas); if (inc && tas != null) // 从缓存中获取类属性元数据 { return tas; } else // 创建新的类属性元数据缓存 { tas = new Dictionary<string, WxopAttribute>(); } PropertyInfo[] pis = type.GetProperties(); foreach (PropertyInfo pi in pis) { WxopAttribute ta = new WxopAttribute(); ta.Method = pi.GetSetMethod(); // 获取对象属性名称 XmlElementAttribute[] xeas = pi.GetCustomAttributes(typeof(XmlElementAttribute), true) as XmlElementAttribute[]; if (xeas != null && xeas.Length > 0) { ta.ItemName = xeas[0].ElementName; } // 获取列表属性名称 if (ta.ItemName == null) { XmlArrayItemAttribute[] xaias = pi.GetCustomAttributes(typeof(XmlArrayItemAttribute), true) as XmlArrayItemAttribute[]; if (xaias != null && xaias.Length > 0) { ta.ItemName = xaias[0].ElementName; } XmlArrayAttribute[] xaas = pi.GetCustomAttributes(typeof(XmlArrayAttribute), true) as XmlArrayAttribute[]; if (xaas != null && xaas.Length > 0) { ta.ListName = xaas[0].ElementName; } if (ta.ListName == null) { continue; } } // 获取属性类型 if (pi.PropertyType.IsGenericType) { Type[] types = pi.PropertyType.GetGenericArguments(); ta.ListType = types[0]; } else { ta.ItemType = pi.PropertyType; } tas.Add(pi.Name, ta); } attrs[type.FullName] = tas; return tas; } public object FromJson(IWxopReader reader, Type type) { object rsp = null; Dictionary<string, WxopAttribute> pas = GetWxopAttributes(type); Dictionary<string, WxopAttribute>.Enumerator em = pas.GetEnumerator(); while (em.MoveNext()) { KeyValuePair<string, WxopAttribute> kvp = em.Current; WxopAttribute ta = kvp.Value; string itemName = ta.ItemName; string listName = ta.ListName; if (!reader.HasReturnField(itemName) && (string.IsNullOrEmpty(listName) || !reader.HasReturnField(listName))) { continue; } object value = null; if (ta.ListType != null) { value = reader.GetListObjects(listName, itemName, ta.ListType, FromJson); } else { if (typeof(string) == ta.ItemType) { object tmp = reader.GetPrimitiveObject(itemName); if (tmp != null) { value = tmp.ToString(); } } else if (typeof(long) == ta.ItemType) { object tmp = reader.GetPrimitiveObject(itemName); if (tmp != null) { value = ((IConvertible)tmp).ToInt64(null); } } else if (typeof(int) == ta.ItemType) { object tmp = reader.GetPrimitiveObject(itemName); if (tmp != null) { value = ((IConvertible)tmp).ToInt32(null); } } else if (typeof(bool) == ta.ItemType) { value = reader.GetPrimitiveObject(itemName); } else { value = reader.GetReferenceObject(itemName, ta.ItemType, FromJson); } } if (value != null) { if (rsp == null) { rsp = Activator.CreateInstance(type); } ta.Method.Invoke(rsp, new object[] { value }); } } return rsp; } public object FromJson(IWxopReader reader, Type type, bool haveChildNode) { object rsp = null; Dictionary<string, WxopAttribute> pas = GetWxopAttributes(type); Dictionary<string, WxopAttribute>.Enumerator em = pas.GetEnumerator(); while (em.MoveNext()) { KeyValuePair<string, WxopAttribute> kvp = em.Current; WxopAttribute ta = kvp.Value; string itemName = ta.ItemName; string listName = ta.ListName; if (!reader.HasReturnField(itemName) && (string.IsNullOrEmpty(listName) || !reader.HasReturnField(listName))) { continue; } object value = null; if (ta.ListType != null) { value = reader.GetListObjects(listName, itemName, ta.ListType, FromJson); } else { if (typeof(string) == ta.ItemType) { object tmp = reader.GetPrimitiveObject(itemName); if (tmp != null) { value = tmp.ToString(); } } else if (typeof(long) == ta.ItemType) { object tmp = reader.GetPrimitiveObject(itemName); if (tmp != null) { value = ((IConvertible)tmp).ToInt64(null); } } else if (typeof(int) == ta.ItemType) { object tmp = reader.GetPrimitiveObject(itemName); if (tmp != null) { value = ((IConvertible)tmp).ToInt32(null); } } else if (typeof(bool) == ta.ItemType) { value = reader.GetPrimitiveObject(itemName); } else { value = reader.GetReferenceObject(itemName, ta.ItemType, FromJson); } } if (value != null) { if (rsp == null) { rsp = Activator.CreateInstance(type); } ta.Method.Invoke(rsp, new object[] { value }); } } return rsp; } } }
建立AccessToken 请求类 AccessTokenGetRequest.css
using System; using System.Collections.Generic; using Wxop.Api.Response; using Wxop.Api.Util; namespace Wxop.Api.Request { /// <summary> /// WXOP API: Get Access Token /// </summary> public class AccessTokenGetRequest : IWxopRequest<AccessTokenGetResponse> { private IDictionary<string, string> otherParameters; #region IWxopRequest Members public IDictionary<string, string> GetParameters() { WxopDictionary parameters = new WxopDictionary(); //parameters.Add("fields", this.Fields); parameters.AddAll(this.otherParameters); return parameters; } #endregion public void AddOtherParameter(string key, string value) { if (this.otherParameters == null) { this.otherParameters = new WxopDictionary(); } this.otherParameters.Add(key, value); } } }
好了 ,准备工作都做好了,在原有的MVC项目里,引入刚整理好的类库。 GetToken的方法,得写为如下代码:
public string GetToken() { string strResult = String.Empty; string url = "https://api.weixin.qq.com/cgi-bin/token"; string appkey = "wxfb90abc********"; string appsecret = "51b12f574e2c9185********"; IWxopClient client = new DefaultWxopClient(url, appkey, appsecret, "json"); AccessTokenGetRequest req = new AccessTokenGetRequest(); AccessTokenGetResponse response = client.Execute(req); if (!String.IsNullOrEmpty(response.AccessToken)) { strResult = response.AccessToken; } return strResult; }
再次执行,返回的就只有一个Token了。
至此,还需要解决的一个问题就是Token的储存了。我先想想解决方案。