最近在做Discuz!NT论坛与网站整合的东西,于是便用到了Discuz提供的Discuz! Toolkit
看了看源码,应该说这是个不错的工具库,提供了关于注册、登录、 文章、积分等论坛操作的一篮子功能,而且配备了对应的Wiki
只可惜,Discuz!NT终归是异构的系统,响应速度和突发异常并非如自己的代码一样可控,使用同步方式调用API就显得有那么些不智了
好在Toolkit是开源的,可以DIY,看看它提供的同步访问Web Service方法:
Discuz.Toolkit.Util.GetResponseBytespublic static byte[] GetResponseBytes(string apiUrl, string method_name, string postData) { HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(apiUrl); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = postData.Length; request.Timeout = 20000; HttpWebResponse response = null; try { StreamWriter swRequestWriter = new StreamWriter(request.GetRequestStream()); swRequestWriter.Write(postData); if (swRequestWriter != null) swRequestWriter.Close(); response = (HttpWebResponse)request.GetResponse(); using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) { return Encoding.UTF8.GetBytes(reader.ReadToEnd()); } } finally { if (response != null) response.Close(); } }
要进行异步的调用,原本的同步流程会被切成两部分:
1、调用开始等待响应
2、响应触发调用完成。
演化成语言:一个方法以委托的方式,在这两部分之间传递,第1部分会将方法指针(委托)塞入请求里,在流程执行到第2部分时,方法被取出回调。
先设计委托原型:
public delegate void GetResponseGeneric<T>(T objects);
异步请求数据结构:
RequestStatepublic class RequestState { public HttpWebRequest request; public HttpWebResponse response; }
扩展一下Util工具类,加上异步调用接口方法:
异步方法 /// <summary> /// 异步获取响应开始 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="fun">泛型回调方法</param> /// <param name="method_name"></param> /// <param name="parameters"></param> #region void GetResponseBegin<T>(GetResponseGeneric<T> fun,string method_name, params DiscuzParam[] parameters) public void GetResponseBegin<T>(GetResponseGeneric<T> fun,string method_name, params DiscuzParam[] parameters) { DiscuzParam[] signed = Sign(method_name, parameters); StringBuilder builder = new StringBuilder(); for (int i = 0; i < signed.Length; i++) { if (i > 0) builder.Append("&"); builder.Append(signed[i].ToEncodedString()); } //异步获取字节流 GetResponseBytesBegin<T>(fun,Url, method_name, builder.ToString()); } #endregion /// <summary> /// 异步获取响应完成 /// </summary> #region void GetReponseEnd<T>(GetResponseGeneric<T> fun,byte[] response_bytes) public void GetReponseEnd<T>(GetResponseGeneric<T> fun, byte[] response_bytes) { XmlSerializer response_serializer = GetSerializer(typeof(T)); try { T response = (T)response_serializer.Deserialize(new MemoryStream(response_bytes)); //回调 调用方 接收方法 fun(response); } catch { Error error = (Error)ErrorSerializer.Deserialize(new MemoryStream(response_bytes)); throw new DiscuzException(error.ErrorCode, error.ErrorMsg); } } #endregion /// <summary> /// 异步获取字节流开始 /// </summary> /// <param name="callback">回调方法</param> /// <param name="apiUrl"></param> /// <param name="method_name"></param> /// <param name="postData"></param> #region void GetResponseBytesBegin<T>(GetResponseGeneric<T> fun,string apiUrl, string method_name, string postData) private void GetResponseBytesBegin<T>(GetResponseGeneric<T> fun, string apiUrl, string method_name, string postData) { HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(apiUrl); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = postData.Length; request.Timeout = 20000; StreamWriter swRequestWriter = new StreamWriter(request.GetRequestStream()); swRequestWriter.Write(postData); if (swRequestWriter != null) swRequestWriter.Close(); RequestState myRequestState = new RequestState(); myRequestState.request = request; //异步调用 request.BeginGetResponse(ar => GetResponseBytesEnd<T>(fun, ar), myRequestState); } #endregion /// <summary> /// 异步获取字节流完成 /// </summary> /// <param name="asynchronousResult"></param> #region void GetResponseBytesEnd<T>(GetResponseGeneric<T> fun,IAsyncResult asynchronousResult) private void GetResponseBytesEnd<T>(GetResponseGeneric<T> fun, IAsyncResult asynchronousResult) { RequestState myRequestState = (RequestState)asynchronousResult.AsyncState; HttpWebRequest myHttpWebRequest = myRequestState.request; try { //接收完成 myRequestState.response = (HttpWebResponse)myHttpWebRequest.EndGetResponse(asynchronousResult); using (StreamReader reader = new StreamReader(myRequestState.response.GetResponseStream(), Encoding.UTF8)) { byte[] response_bytes = Encoding.UTF8.GetBytes(reader.ReadToEnd()); //回调 异步获取响应完成 方法 GetReponseEnd<T>(fun, response_bytes); } } catch (WebException ex) { DiscuzLogger.WriteLog(typeof(Util), ex); } finally { if (myRequestState.response != null) myRequestState.response.Close(); } } #endregion
至此,与Discuz!NT的异步交互功能提供完成,怎么使用呢?
假设需要异步获取某一版块指定数量的贴子,可以在DiscuzSession添加一个这样的方法:
Discuz.Toolkit.DiscuzSession.GetTopicListBegin/// <summary> /// 异步 获取主题列表 开始 /// </summary> /// <param name="fid"></param> /// <param name="page_size"></param> /// <param name="page_index"></param> /// <returns></returns> public void GetTopicListBegin(GetResponseGeneric<TopicGetListResponse> fun, int fid, int page_size, int page_index, string typeIdList) { List<DiscuzParam> param_list = new List<DiscuzParam>(); if (session_info != null && !string.IsNullOrEmpty(session_info.SessionKey)) { param_list.Add(DiscuzParam.Create("session_key", session_info.SessionKey)); } param_list.Add(DiscuzParam.Create("type_id_list", typeIdList)); param_list.Add(DiscuzParam.Create("fid", fid)); param_list.Add(DiscuzParam.Create("page_size", page_size)); param_list.Add(DiscuzParam.Create("page_index", page_index)); //异步开始 util.GetResponseBegin<TopicGetListResponse>(fun, "topics.getList", param_list.ToArray()); }
调用方使用起来只需要定义好接收方法,剩下就是收数据(CacheObjectCollection.HomepageTopicList是一个缓存项的getter/setter):
调用方 /// <summary> /// 异步获取论坛指定板块的帖子列表开始 /// </summary> /// <param name="fid">板块ID</param> /// <param name="pageSize">数量</param> /// <param name="pageIndex">索引</param> /// <param name="typeIdList">主题ID 以 , 为分隔符</param> #region static void HomepageTopicGetBegin(int fid, int pageSize, int pageIndex, string typeIdList) public static void HomepageTopicGetBegin(int fid, int pageSize, int pageIndex, string typeIdList) { if (CacheObjectConllection.HomepageTopicList == null) { try { DiscuzSession session = GetSession(); session.GetTopicListBegin(HomepageTopicGetEnd, fid, pageSize, pageIndex, typeIdList); } catch (DiscuzException ex) { Logger.WriteLog(typeof(DiscuzHelper), string.Format("DISCUZ!NT异步获取论坛指定板块的帖子列表错误:\r\n{0}" , ex.ToString())); } } } #endregion /// <summary> /// 异步获取论坛指定板块的帖子列表完成 /// </summary> #region static void HomepageTopicGetEnd(TopicGetListResponse result) public static void HomepageTopicGetEnd(TopicGetListResponse result) { CacheObjectConllection.HomepageTopicList = result; } #endregion
到此,异步调用框架完成,个人感觉很好。只是,需求变化成了生成静态页面,它便成了鸡肋……