【重要更新】Senparc.Weixin SDK v4.3.3升级说明

简介:   为了更好地适应微信越来越快的API更新速度和越来越多的API数量,本次Senparc.Weixin.dll v4.3.3对一些通用功能进行了深度的重构。   本次更新同时影响以下所有Senparc.

  为了更好地适应微信越来越快的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) });
            }
        }
    }
}
View Code

  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
        }
    }
}
View Code

  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内部的方法名也进行了调整,具体如下:

原方法名称 现方法名称
AccessTokenContainer.Register() 不变
AccessTokenContainer.GetFirstOrDefaultAppId() 不变
AccessTokenContainer.TryGetToken() AccessTokenContainer.TryGetAccessToken()
AccessTokenContainer.GetToken() AccessTokenContainer.GetAccessToken()
AccessTokenContainer.GetTokenResult() AccessTokenContainer.GetAccessTokenResult()
JsApiTicketContainer.TryGetTicket() AccessTokenContainer.TryGetJsApiTicket()
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提供了所有第三方平台需要用到的缓存信息以及获取方法,包括:ComponentVerifyTicketPreAuthCodeAuthorizerAccessToken

  其中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 }
View Code

 

  在第一次使用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负责储存单个授权方(微信公众号)所有相关的信息,包括:AuthorizerInfoResultJsApiTicketResult

  其中JsApiTicketResult相关的对象由原来的JsApiTicketContainer合并而来,JsApiTicketContainer现已删除(升级方法参考Senparc.Weixin.MP的JsApiTicketContainer)。

  经常可能需要用到的AuthorizerInfoAuthorizerInfo_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 }
View Code

  

 

Senparc.Weixin.QY.dll

  升级内容(尚未正式发布):

  1、重构Container。

  2、完善接口。

 

 

  整个升级过程尽量确保了最小幅度的改动,之前版本的其他接口功能都不受影响,更多教程见:

  系列教程索引:http://www.cnblogs.com/szw/archive/2013/05/14/weixin-course-index.html

 

 

 

QQ:498977166

http://szw.cnblogs.com/
研究、探讨.NET开发
转载请注明出处和作者,谢谢!

 


微信开发深度解析:微信公众号、小程序高效开发秘籍
Senparc官方教程《微信开发深度解析:微信公众号、小程序高效开发秘籍》,耗时2年精心打造的微信开发权威教程,点击这里,购买正版

 

目录
相关文章
|
开发工具
【错误记录】Flutter 插件报错 ( Methods marked with @UiThread must be executed on the main thread. | 更新最新 SDK )(一)
【错误记录】Flutter 插件报错 ( Methods marked with @UiThread must be executed on the main thread. | 更新最新 SDK )(一)
858 0
【错误记录】Flutter 插件报错 ( Methods marked with @UiThread must be executed on the main thread. | 更新最新 SDK )(一)
|
3月前
|
存储 Java 开发工具
【Azure 存储服务】Azure Blob Storage SDK 升级失败,遇见 Unsatisfied Dependency Exception 和 Unexpected Length Exception
【Azure 存储服务】Azure Blob Storage SDK 升级失败,遇见 Unsatisfied Dependency Exception 和 Unexpected Length Exception
|
6月前
|
开发工具 iOS开发 Perl
mac电脑升级到10.15.7含有支付宝SDK的组件更新报错:AlipaySDK.framework/AlipaySDK for architecture arm64
mac电脑升级到10.15.7含有支付宝SDK的组件更新报错:AlipaySDK.framework/AlipaySDK for architecture arm64
86 0
|
敏捷开发 运维 Linux
带你读《2022龙蜥社区全景白皮书》——5.3.5 敏捷开发场景下的调度器热升级SDK
带你读《2022龙蜥社区全景白皮书》——5.3.5 敏捷开发场景下的调度器热升级SDK
150 2
|
敏捷开发 运维 Linux
龙蜥白皮书精选:敏捷开发场景下的调度器热升级 SDK
传统技术无法实现整个子系统升级,不支持大型调度特性,停机时间长。而调度器热升级技术解决了该问题。
龙蜥白皮书精选:敏捷开发场景下的调度器热升级 SDK
|
消息中间件 存储 缓存
全面升级 —— Apache RocketMQ 5.0 SDK 的新面貌
长久以来,RocketMQ 易于部署、高性能、高可用的架构,支撑了数十年来集团内外海量的业务场景。时至今日,为了迎接如今云原生时代的新挑战,我们重磅推出了 RocketMQ 5.0 新架构。
300 0
全面升级 —— Apache RocketMQ 5.0 SDK 的新面貌
|
开发工具
Flutter SDK升级
Flutter SDK升级
144 0
Flutter SDK升级
|
消息中间件 存储 缓存
全面升级 —— Apache RocketMQ 5.0 SDK 的新面貌
长久以来,RocketMQ 易于部署、高性能、高可用的架构,支撑了数十年来集团内外海量的业务场景。时至今日,为了迎接如今云原生时代的新挑战,我们重磅推出了 RocketMQ 5.0 新架构。
2096 10
全面升级 —— Apache RocketMQ 5.0 SDK 的新面貌
|
开发工具
【错误记录】Flutter 插件报错 ( Methods marked with @UiThread must be executed on the main thread. | 更新最新 SDK )(二)
【错误记录】Flutter 插件报错 ( Methods marked with @UiThread must be executed on the main thread. | 更新最新 SDK )(二)
333 0
【错误记录】Flutter 插件报错 ( Methods marked with @UiThread must be executed on the main thread. | 更新最新 SDK )(二)
下一篇
无影云桌面