為了更好地適應微信越來越快的API更新速度和越來越多的API數量,本次Senparc.Weixin.dll v4.3.3對一些通用功能進行了深度的重構。
本次更新同時影響以下所有Senparc.Weixin相關版本的dll:
- Senparc.Weixin.dll 升級到 v4.3.3
- Senparc.Weixin.MP.dll 升級到 v13.3.0(重要)
- Senparc.Weixin.MP.MvcExtension.dll 升級到 v1.4.1
- Senparc.Weixin.Open 升級到 v1.4.1(重要)
- Senparc.Weixin.QY.dll 升級到 v3.1.1(尚處內測中,近期發布,重要)
下面詳細介紹一下本次更新的內容,及小於上述版本的dll的項目升級方式(已經盡量確保方法的兼容性,基本上只需要批量替換)。
Senparc.Weixin.dll
升級內容:
1、為JSON字符串輸出過程中,過濾值為null的屬性,提供了解決方案。
涉及到的文件如下圖所示:
其中:SerializerHelper.cs為升級,Conventers文件夾為新增的兩個用於支持SerializerHelper的轉換器。
ExpandoJsonConverter.cs用於提供ExpandoObject類型到JSON字符串的轉換:

/*---------------------------------------------------------------- Copyright (C) 2015 Senparc 文件名:ExpandoJsonConverter.cs 文件功能描述:Expando-JSON字符串轉換 創建標識:Senparc - 20151002 ----------------------------------------------------------------*/ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Dynamic; using System.Web.Script.Serialization; namespace Senparc.Weixin.Helpers { /// <summary> /// Allows JSON serialization of Expando objects into expected results (e.g., "x: 1, y: 2") instead of the default dictionary serialization. /// </summary> public class ExpandoJsonConverter : JavaScriptConverter { public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) { // See source code link for this extension method at the bottom of this post (/Helpers/IDictionaryExtensions.cs) return dictionary.ToExpando(); } public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) { var result = new Dictionary<string, object>(); var dictionary = obj as IDictionary<string, object>; foreach (var item in dictionary) result.Add(item.Key, item.Value); return result; } public override IEnumerable<Type> SupportedTypes { get { return new ReadOnlyCollection<Type>(new Type[] { typeof(ExpandoObject) }); } } } }
WeixinJsonConventer.cs用於提供微信中部分字段為null時,整個屬性都不輸出的配置方法:

/*---------------------------------------------------------------- Copyright (C) 2015 Senparc 文件名:WeixinJsonConventer.cs 文件功能描述:微信JSON字符串轉換 創建標識:Senparc - 20150930 ----------------------------------------------------------------*/ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Web.Script.Serialization; using Senparc.Weixin.Entities; namespace Senparc.Weixin.Helpers { /// <summary> /// JSON輸出設置 /// </summary> public class JsonSetting { /// <summary> /// 是否忽略當前類型以及具有IJsonIgnoreNull接口,且為Null值的屬性。如果為true,符合此條件的屬性將不會出現在Json字符串中 /// </summary> public bool IgnoreNulls { get; set; } /// <summary> /// 需要特殊忽略null值的屬性名稱 /// </summary> public List<string> PropertiesToIgnore { get; set; } /// <summary> /// 指定類型(Class,非Interface)下的為null屬性不生成到Json中 /// </summary> public List<Type> TypesToIgnore { get; set; } /// <summary> /// JSON輸出設置 構造函數 /// </summary> /// <param name="ignoreNulls">是否忽略當前類型以及具有IJsonIgnoreNull接口,且為Null值的屬性。如果為true,符合此條件的屬性將不會出現在Json字符串中</param> /// <param name="propertiesToIgnore">需要特殊忽略null值的屬性名稱</param> /// <param name="typesToIgnore">指定類型(Class,非Interface)下的為null屬性不生成到Json中</param> public JsonSetting(bool ignoreNulls = false, List<string> propertiesToIgnore = null, List<Type> typesToIgnore = null) { IgnoreNulls = ignoreNulls; PropertiesToIgnore = propertiesToIgnore ?? new List<string>(); TypesToIgnore = typesToIgnore ?? new List<Type>(); } } /// <summary> /// 微信JSON轉換器 /// </summary> public class WeixinJsonConventer : JavaScriptConverter { private readonly JsonSetting _jsonSetting; private readonly Type _type; public WeixinJsonConventer(Type type, JsonSetting jsonSetting = null) { this._jsonSetting = jsonSetting ?? new JsonSetting(); this._type = type; } public override IEnumerable<Type> SupportedTypes { get { var typeList = new List<Type>(new[] { typeof(IJsonIgnoreNull)/*,typeof(JsonIgnoreNull)*/ }); if (_jsonSetting.TypesToIgnore.Count > 0) { typeList.AddRange(_jsonSetting.TypesToIgnore); } if (_jsonSetting.IgnoreNulls) { typeList.Add(_type); } return new ReadOnlyCollection<Type>(typeList); } } public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer) { var result = new Dictionary<string, object>(); if (obj == null) { return result; } var properties = obj.GetType().GetProperties(); foreach (var propertyInfo in properties) { if (!this._jsonSetting.PropertiesToIgnore.Contains(propertyInfo.Name)) { bool ignoreProp = propertyInfo.IsDefined(typeof(ScriptIgnoreAttribute), true); if ((this._jsonSetting.IgnoreNulls || ignoreProp) && propertyInfo.GetValue(obj, null) == null) { continue; } result.Add(propertyInfo.Name, propertyInfo.GetValue(obj, null)); } } return result; } public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer) { throw new NotImplementedException(); //Converter is currently only used for ignoring properties on serialization } } }
WeixinJsonConventer提供了多種過濾的方式:
- 過濾當前數據對象類型(或任一指定類型)下,所有為null的屬性(根據JsonSetting.IgnoreNulls配置)
- 過濾當前數據對象類型(或任一指定類型)下,所有為null,且指定名稱的屬性(根據JsonSetting.PropertiesToIgnore配置)
- 過濾指定類型下(可多個),所有為null的屬性(根據JsonSetting.TypesToIgnore配置)
- 過濾實現了IJsonIgnoreNull接口的類型下,,所有為null的屬性(自動)
以上通過配置或自動的過濾規則只要滿足其中一條即被過濾)
目前已經發現的需要過濾null的地方不多(猜測是微信的bug),通常情況下此方法不需要在SDK外部使用,目前SDK內部部分接口已經在使用,如創建卡券的接口:
1 /// <summary> 2 /// 創建卡券 3 /// </summary> 4 /// <param name="accessTokenOrAppId"></param> 5 /// <param name="cardInfo">創建卡券需要的數據,格式可以看CardCreateData.cs</param> 6 /// <param name="timeOut">代理請求超時時間(毫秒)</param> 7 /// <returns></returns> 8 public static CardCreateResultJson CreateCard(string accessTokenOrAppId, BaseCardInfo cardInfo, int timeOut = Config.TIME_OUT) 9 { 10 return ApiHandlerWapper.TryCommonApi(accessToken => 11 { 12 var urlFormat = string.Format("https://api.weixin.qq.com/card/create?access_token={0}", accessToken); 13 14 CardCreateInfo cardData = null; 15 CardType cardType = cardInfo.GetCardType(); 16 17 switch (cardType) 18 { 19 ... 20 } 21 22 var jsonSetting = new JsonSetting(true, null, 23 new List<Type>() 24 { 25 //typeof (Modify_Msg_Operation), 26 //typeof (CardCreateInfo), 27 typeof (Card_BaseInfoBase)//過濾Modify_Msg_Operation主要起作用的是這個 28 }); 29 30 var result = CommonJsonSend.Send<CardCreateResultJson>(null, urlFormat, cardData, timeOut: timeOut, 31 //針對特殊字段的null值進行過濾 32 jsonSetting: jsonSetting); 33 return result; 34 35 }, accessTokenOrAppId); 36 }
第一步:定義JsonSetting,確定需要過濾的規則。
第二步:將JsonSetting對象傳入CommonJsonSend.Send()方法。
2、增加Containers基礎功能:用於作為其他子庫中所有Container(如AccessTokenContainer)的基類,並提供配套的ContainerBag(信息包)基類。
如下圖所示:
BaseContainerBag.cs 用於提供信息報的基類:
/*---------------------------------------------------------------- Copyright (C) 2015 Senparc 文件名:BaseContainerBag.cs 文件功能描述:微信容器接口中的封裝Value(如Ticket、AccessToken等數據集合) 創建標識:Senparc - 20151003 ----------------------------------------------------------------*/ namespace Senparc.Weixin.Containers { /// <summary> /// IBaseContainerBag /// </summary> public interface IBaseContainerBag { string Key { get; set; } } /// <summary> /// BaseContainer容器中的Value類型 /// </summary> public class BaseContainerBag : IBaseContainerBag { /// <summary> /// 通常為AppId /// </summary> public string Key { get; set; } } }
BaseContainer.cs用於提供所有Container容器的基類:
/*---------------------------------------------------------------- Copyright (C) 2015 Senparc 文件名:WeixinContainer.cs 文件功能描述:微信容器(如Ticket、AccessToken) 創建標識:Senparc - 20151003 ----------------------------------------------------------------*/ using System; using System.Collections.Generic; using System.Linq; namespace Senparc.Weixin.Containers { /// <summary> /// 微信容器接口(如Ticket、AccessToken) /// </summary> /// <typeparam name="T"></typeparam> public abstract class BaseContainer<T> where T : IBaseContainerBag, new() { /// <summary> /// 所有數據集合的列表 /// </summary> private static readonly Dictionary<Type, Dictionary<string, T>> _collectionList = new Dictionary<Type, Dictionary<string, T>>(); /// <summary> /// 獲取當前容器的數據項集合 /// </summary> /// <returns></returns> protected static Dictionary<string, T> ItemCollection { get { if (!_collectionList.ContainsKey(typeof(T))) { _collectionList[typeof(T)] = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase); } return _collectionList[typeof(T)]; } } /// <summary> /// 獲取完整的數據集合的列表(建議不要進行任何修改操作) /// </summary> /// <returns></returns> public static Dictionary<Type, Dictionary<string, T>> GetCollectionList() { return _collectionList; } /// <summary> /// 獲取所有容器內已經注冊的項目 /// (此方法將會遍歷Dictionary,當數據項很多的時候效率會明顯降低) /// </summary> /// <returns></returns> public static List<T> GetAllItems() { return ItemCollection.Select(z => z.Value).ToList(); } /// <summary> /// 嘗試獲取某一項Bag /// </summary> /// <param name="key"></param> /// <returns></returns> public static T TryGetItem(string key) { if (ItemCollection.ContainsKey(key)) { return ItemCollection[key]; } return default(T); } /// <summary> /// 嘗試獲取某一項Bag中的具體某個屬性 /// </summary> /// <param name="key"></param> /// <param name="property">具體某個屬性</param> /// <returns></returns> public static K TryGetItem<K>(string key, Func<T, K> property) { if (ItemCollection.ContainsKey(key)) { var item = ItemCollection[key]; return property(item); } return default(K); } /// <summary> /// 更新數據項 /// </summary> /// <param name="key"></param> /// <param name="value">為null時刪除該項</param> public static void Update(string key, T value) { if (value == null) { ItemCollection.Remove(key); } else { ItemCollection[key] = value; } } /// <summary> /// 更新數據項 /// </summary> /// <param name="key"></param> /// <param name="partialUpdate">為null時刪除該項</param> public static void Update(string key, Action<T> partialUpdate) { if (partialUpdate == null) { ItemCollection.Remove(key);//移除對象 } else { if (!ItemCollection.ContainsKey(key)) { ItemCollection[key] = new T() { Key = key//確保這一項Key已經被記錄 }; } partialUpdate(ItemCollection[key]);//更新對象 } } /// <summary> /// 檢查Key是否已經注冊 /// </summary> /// <param name="key"></param> /// <returns></returns> public static bool CheckRegistered(string key) { return ItemCollection.ContainsKey(key); } } }
以上兩個基類將被用於其他所有子庫(MP、Open、QY等)的各種Container中,為其提供統一數據管理能力和集中的緩存存儲支持。
Senparc.Weixin.MP.dll
升級內容:
1、刪除JsApiTicketContainer,合並入AccessTokenContainer,同時以BaseContainer為基類重構AccessTokenContainer。
之前我們需要這樣分別注冊兩次AccessTokenContainer和JsApiTicketContainer,分別注冊兩個容器:
//全局只需注冊一次 AccessTokenContainer.Register(_appId, _appSecret); //全局只需注冊一次 JsApiTicketContainer.Register(_appId, _appSecret);
現在只需要第一行就行了:
//全局只需注冊一次 AccessTokenContainer.Register(_appId, _appSecret);
隨着JsApiTicketContainer的合並,AccessTokenContainer內部的方法名也進行了調整,具體如下:
# | 原方法名稱 | 現方法名稱 |
1 | AccessTokenContainer.Register() | 不變 |
2 | AccessTokenContainer.GetFirstOrDefaultAppId() | 不變 |
3 | AccessTokenContainer.TryGetToken() | AccessTokenContainer.TryGetAccessToken() |
4 | AccessTokenContainer.GetToken() | AccessTokenContainer.GetAccessToken() |
5 | AccessTokenContainer.GetTokenResult() | AccessTokenContainer.GetAccessTokenResult() |
6 | JsApiTicketContainer.TryGetTicket() | AccessTokenContainer.TryGetJsApiTicket() |
7 | JsApiTicketContainer.GetTicket() | AccessTokenContainer.GetJsApiTicket() |
8 | JsApiTicketContainer.GetTicketResult() | AccessTokenContainer.GetJsApiTicketResult() |
所有相關的升級只需要替換上述對應的方法名稱即可,參數不需要變化。其中最常用的應該是#4和#7。
升級方法:
第一步:升級SDK(方法略,建議用nuget)
第二步:編譯。會出一些錯,隨便找到一個,如之前使用了AccessTokenContainer.GetToken()的方法,按Ctrl+F進行替換:
第三步:替換完成、保存:
對於JsApiTicketContainer相關的替換也參考以上步驟:
2、對接口進行常規升級,包括JSON字符串過濾null值的處理。
Senparc.Weixin.MP.MvcExtension.dll
升級內容:
fixbug(重要)。
Senparc.Weixin.Open.dll
升級內容:
1、創建ComponentContainer。
ComponentContainer是用於存放第三方平台信息的容器,Key為ComponentId,信息包為ComponentBag。
ComponentContainer提供了所有第三方平台需要用到的緩存信息以及獲取方法,包括:ComponentVerifyTicket、PreAuthCode、AuthorizerAccessToken。
其中PreAuthCode相關的對象由原來的PreAuthCodeContainer合並而來,PreAuthCodeContainer現已刪除(升級方法參考Senparc.Weixin.MP的JsApiTicketContainer)。
ComponentContainer.cs代碼如下:

1 /*---------------------------------------------------------------- 2 Copyright (C) 2015 Senparc 3 4 文件名:ComponentContainer.cs 5 文件功能描述:通用接口ComponentAccessToken容器,用於自動管理ComponentAccessToken,如果過期會重新獲取 6 7 8 創建標識:Senparc - 20150430 9 10 修改標識:Senparc - 20151004 11 修改描述:v1.4.1 改名為ComponentContainer.cs,合並多個ComponentApp相關容器 12 13 ----------------------------------------------------------------*/ 14 15 using System; 16 using Senparc.Weixin.Containers; 17 using Senparc.Weixin.Exceptions; 18 using Senparc.Weixin.Open.Entities; 19 using Senparc.Weixin.Open.Exceptions; 20 21 namespace Senparc.Weixin.Open.CommonAPIs 22 { 23 /// <summary> 24 /// 第三方APP信息包 25 /// </summary> 26 public class ComponentBag : BaseContainerBag 27 { 28 /// <summary> 29 /// 第三方平台AppId 30 /// </summary> 31 public string ComponentAppId { get; set; } 32 /// <summary> 33 /// 第三方平台AppSecret 34 /// </summary> 35 public string ComponentAppSecret { get; set; } 36 37 /// <summary> 38 /// 第三方平台ComponentVerifyTicket(每隔10分鍾微信會主動推送到服務器,IP必須在白名單內) 39 /// </summary> 40 public string ComponentVerifyTicket { get; set; } 41 42 /// <summary> 43 /// ComponentAccessTokenResult 44 /// </summary> 45 public ComponentAccessTokenResult ComponentAccessTokenResult { get; set; } 46 /// <summary> 47 /// ComponentAccessToken過期時間 48 /// </summary> 49 public DateTime ComponentAccessTokenExpireTime { get; set; } 50 51 52 /// <summary> 53 /// PreAuthCodeResult 預授權碼結果 54 /// </summary> 55 public PreAuthCodeResult PreAuthCodeResult { get; set; } 56 /// <summary> 57 /// 預授權碼過期時間 58 /// </summary> 59 public DateTime PreAuthCodeExpireTime { get; set; } 60 61 62 public string AuthorizerAccessToken { get; set; } 63 64 /// <summary> 65 /// 只針對這個AppId的鎖 66 /// </summary> 67 public object Lock = new object(); 68 69 /// <summary> 70 /// ComponentBag 71 /// </summary> 72 public ComponentBag() 73 { 74 ComponentAccessTokenResult = new ComponentAccessTokenResult(); 75 ComponentAccessTokenExpireTime = DateTime.MinValue; 76 77 PreAuthCodeResult = new PreAuthCodeResult(); 78 PreAuthCodeExpireTime = DateTime.MaxValue; 79 } 80 } 81 82 /// <summary> 83 /// 通用接口ComponentAccessToken容器,用於自動管理ComponentAccessToken,如果過期會重新獲取 84 /// </summary> 85 public class ComponentContainer : BaseContainer<ComponentBag> 86 { 87 private const string UN_REGISTER_ALERT = "此appId尚未注冊,ComponentContainer.Register完成注冊(全局執行一次即可)!"; 88 89 /// <summary> 90 /// 檢查AppId是否已經注冊,如果沒有,則創建 91 /// </summary> 92 /// <param name="componentAppId"></param> 93 /// <param name="componentAppSecret"></param> 94 /// <param name="getNewToken"></param> 95 private static void TryRegister(string componentAppId, string componentAppSecret, bool getNewToken = false) 96 { 97 if (!CheckRegistered(componentAppId) || getNewToken) 98 { 99 Register(componentAppId, componentAppSecret, null); 100 } 101 } 102 103 /// <summary> 104 /// 獲取ComponentVerifyTicket的方法 105 /// </summary> 106 public static Func<string> GetComponentVerifyTicketFunc = null; 107 108 /// <summary> 109 /// 注冊應用憑證信息,此操作只是注冊,不會馬上獲取Token,並將清空之前的Token, 110 /// </summary> 111 /// <param name="componentAppId"></param> 112 /// <param name="componentAppSecret"></param> 113 /// <param name="getComponentVerifyTicketFunc">獲取ComponentVerifyTicket的方法</param> 114 public static void Register(string componentAppId, string componentAppSecret, Func<string> getComponentVerifyTicketFunc) 115 { 116 if (GetComponentVerifyTicketFunc == null) 117 { 118 GetComponentVerifyTicketFunc = getComponentVerifyTicketFunc; 119 } 120 121 Update(componentAppId, new ComponentBag() 122 { 123 ComponentAppId = componentAppId, 124 ComponentAppSecret = componentAppSecret, 125 }); 126 } 127 128 /// <summary> 129 /// 檢查是否已經注冊 130 /// </summary> 131 /// <param name="componentAppId"></param> 132 /// <returns></returns> 133 public static bool CheckRegistered(string componentAppId) 134 { 135 return ItemCollection.ContainsKey(componentAppId); 136 } 137 138 139 #region component_verify_ticket 140 141 /// <summary> 142 /// 獲取ComponentVerifyTicket 143 /// </summary> 144 /// <param name="componentAppId"></param> 145 /// <returns>如果不存在,則返回null</returns> 146 public static string TryGetComponentVerifyTicket(string componentAppId) 147 { 148 if (!CheckRegistered(componentAppId)) 149 { 150 throw new WeixinOpenException(UN_REGISTER_ALERT); 151 } 152 153 var componentVerifyTicket = TryGetItem(componentAppId, bag => bag.ComponentVerifyTicket); 154 if (componentVerifyTicket == default(string)) 155 { 156 if (GetComponentVerifyTicketFunc == null) 157 { 158 throw new WeixinOpenException("GetComponentVerifyTicketFunc必須在注冊時提供!", TryGetItem(componentAppId)); 159 } 160 componentVerifyTicket = GetComponentVerifyTicketFunc(); //獲取最新的componentVerifyTicket 161 } 162 return componentVerifyTicket; 163 } 164 165 /// <summary> 166 /// 更新ComponentVerifyTicket信息 167 /// </summary> 168 /// <param name="componentAppId"></param> 169 /// <param name="componentVerifyTicket"></param> 170 public static void UpdateComponentVerifyTicket(string componentAppId, string componentVerifyTicket) 171 { 172 Update(componentAppId, bag => 173 { 174 bag.ComponentVerifyTicket = componentVerifyTicket; 175 }); 176 } 177 178 #endregion 179 180 #region component_access_token 181 182 /// <summary> 183 /// 使用完整的應用憑證獲取Token,如果不存在將自動注冊 184 /// </summary> 185 /// <param name="componentAppId"></param> 186 /// <param name="componentAppSecret"></param> 187 /// <param name="getNewToken"></param> 188 /// <returns></returns> 189 public static string TryGetComponentAccessToken(string componentAppId, string componentAppSecret, bool getNewToken = false) 190 { 191 TryRegister(componentAppId, componentAppSecret, getNewToken); 192 return GetComponentAccessToken(componentAppId); 193 } 194 195 /// <summary> 196 /// 獲取可用AccessToken 197 /// </summary> 198 /// <param name="componentAppId"></param> 199 /// <param name="getNewToken">是否強制重新獲取新的Token</param> 200 /// <returns></returns> 201 public static string GetComponentAccessToken(string componentAppId, bool getNewToken = false) 202 { 203 return GetComponentAccessTokenResult(componentAppId, getNewToken).component_access_token; 204 } 205 206 /// <summary> 207 /// 獲取可用AccessToken 208 /// </summary> 209 /// <param name="componentAppId"></param> 210 /// <param name="getNewToken">是否強制重新獲取新的Token</param> 211 /// <returns></returns> 212 public static ComponentAccessTokenResult GetComponentAccessTokenResult(string componentAppId, bool getNewToken = false) 213 { 214 if (!CheckRegistered(componentAppId)) 215 { 216 throw new WeixinOpenException(UN_REGISTER_ALERT); 217 } 218 219 var accessTokenBag = ItemCollection[componentAppId]; 220 lock (accessTokenBag.Lock) 221 { 222 if (getNewToken || accessTokenBag.ComponentAccessTokenExpireTime <= DateTime.Now) 223 { 224 //已過期,重新獲取 225 var componentVerifyTicket = TryGetComponentVerifyTicket(componentAppId); 226 227 accessTokenBag.ComponentAccessTokenResult = CommonApi.GetComponentAccessToken(accessTokenBag.ComponentAppId, accessTokenBag.ComponentAppSecret, componentVerifyTicket); 228 229 accessTokenBag.ComponentAccessTokenExpireTime = DateTime.Now.AddSeconds(accessTokenBag.ComponentAccessTokenResult.expires_in); 230 } 231 } 232 return accessTokenBag.ComponentAccessTokenResult; 233 } 234 #endregion 235 236 #region pre_auth_code 237 238 /// <summary> 239 /// 使用完整的應用憑證獲取Token,如果不存在將自動注冊 240 /// </summary> 241 /// <param name="componentAppId"></param> 242 /// <param name="componentAppSecret"></param> 243 /// <param name="componentVerifyTicket"></param> 244 /// <param name="getNewToken"></param> 245 /// <returns></returns> 246 public static string TryGetPreAuthCode(string componentAppId, string componentAppSecret, string componentVerifyTicket, bool getNewToken = false) 247 { 248 TryRegister(componentAppId, componentAppSecret, getNewToken); 249 return GetGetPreAuthCode(componentAppId); 250 } 251 252 /// <summary> 253 /// 獲取可用Token 254 /// </summary> 255 /// <param name="componentAppId"></param> 256 /// <param name="getNewToken">是否強制重新獲取新的Token</param> 257 /// <returns></returns> 258 public static string GetGetPreAuthCode(string componentAppId, bool getNewToken = false) 259 { 260 return GetPreAuthCodeResult(componentAppId, getNewToken).pre_auth_code; 261 } 262 263 /// <summary> 264 /// 獲取可用Token 265 /// </summary> 266 /// <param name="componentAppId"></param> 267 /// <param name="getNewToken">是否強制重新獲取新的Token</param> 268 /// <returns></returns> 269 public static PreAuthCodeResult GetPreAuthCodeResult(string componentAppId, bool getNewToken = false) 270 { 271 if (!CheckRegistered(componentAppId)) 272 { 273 throw new WeixinOpenException(UN_REGISTER_ALERT); 274 } 275 276 var accessTokenBag = ItemCollection[componentAppId]; 277 lock (accessTokenBag.Lock) 278 { 279 if (getNewToken || accessTokenBag.PreAuthCodeExpireTime <= DateTime.Now) 280 { 281 //已過期,重新獲取 282 var componentVerifyTicket = TryGetComponentVerifyTicket(componentAppId); 283 284 accessTokenBag.PreAuthCodeResult = CommonApi.GetPreAuthCode(accessTokenBag.ComponentAppId, accessTokenBag.ComponentAppSecret, componentVerifyTicket); 285 286 accessTokenBag.PreAuthCodeExpireTime = DateTime.Now.AddSeconds(accessTokenBag.PreAuthCodeResult.expires_in); 287 } 288 } 289 return accessTokenBag.PreAuthCodeResult; 290 } 291 #endregion 292 293 } 294 }
在第一次使用ComponentContainer之前,和諸如AccessTokenContainer一樣,需要進行一次全局的注冊:
//全局只需注冊一次 ComponentContainer.Register(componentAppId, componentAppSecret, _componentAppId =>{ //獲取ComponentVerifyTicket的方法,通常是從數據庫或者日志文件中獲取 });
注意在上面的方法中,最后一個參數是getComponentVerifyTicketFunc,里面需要寫入用於獲取ComponentVerifyTicket的方法,通常是從數據庫或者日志文件中獲取(微信每10分鍾會推送一次最新的ComponentVerifyTicket到第三方服務器)。
下面是一種可能的實現:
1 //注冊第三方平台 2 //定義ComponentVerifyTicketFunc 3 Func<string, string> getComponentVerifyTicketFunc = componentAppId => 4 { 5 var file = Core.Utility.Server.GetMapPath( 6 "~/App_Data/OpenTicket/ComponentVerifyTicket/{0}.txt".With(componentAppId)); 7 using (var fs = new FileStream(file, FileMode.Open)) 8 { 9 using (var sr = new StreamReader(fs)) 10 { 11 var ticket = sr.ReadToEnd(); 12 return ticket; 13 } 14 } 15 }; 16 //執行注冊 17 ComponentContainer.Register( 18 ConfigurationManager.AppSettings["Component_Appid"], 19 ConfigurationManager.AppSettings["Component_Secret"], getComponentVerifyTicketFunc);
2、創建AuthorizerContainer。
AuthorizerContainer負責儲存單個授權方(微信公眾號)所有相關的信息,包括:AuthorizerInfoResult、JsApiTicketResult。
其中JsApiTicketResult相關的對象由原來的JsApiTicketContainer合並而來,JsApiTicketContainer現已刪除(升級方法參考Senparc.Weixin.MP的JsApiTicketContainer)。
經常可能需要用到的AuthorizerInfo和AuthorizerInfo_AuthorizationInfo都在AuthorizerInfoResult對象內。
注意:AuthorizerContainer不需要外部執行Register()方法。因為它是依賴於ComponentContainer的,所以在執行AuthorizerContainer相關方法之前,請確保已經對ComponentContainer進行注冊。
3、增加WeixinOpenException異常類型,用於捕捉更加精確的Senaprc.Weixin.Open相關的異常。

1 /*---------------------------------------------------------------- 2 Copyright (C) 2015 Senparc 3 4 文件名:WeixinOpenException.cs 5 文件功能描述:微信開放平台異常處理類 6 7 8 創建標識:Senparc - 20151004 9 10 ----------------------------------------------------------------*/ 11 12 using System; 13 using Senparc.Weixin.Exceptions; 14 using Senparc.Weixin.Open.CommonAPIs; 15 16 namespace Senparc.Weixin.Open.Exceptions 17 { 18 public class WeixinOpenException : WeixinException 19 { 20 public ComponentBag ComponentBag { get; set; } 21 22 public WeixinOpenException(string message, ComponentBag componentBag = null, Exception inner=null) 23 : base(message, inner) 24 { 25 ComponentBag = ComponentBag; 26 } 27 } 28 }
Senparc.Weixin.QY.dll
升級內容(尚未正式發布):
1、重構Container。
2、完善接口。
整個升級過程盡量確保了最小幅度的改動,之前版本的其他接口功能都不受影響,更多教程見: