以前在開發博客園新聞客戶端的時候,須要獲取博客園的新聞數據,最先開發出來的版本使用的是手機版的 html 解釋的方式。效果算是作出來了,可是感受獲取到的數據太冗餘,因而便查了一下有沒有相應的接口。皇天不負有心人,博客園果真開放了些接口供咱們使用。html
博客服務接口:http://wcf.open.cnblogs.com/blog/helpgit
新聞服務接口:http://wcf.open.cnblogs.com/news/helpgithub
博客服務:app
Uri | Method | Description |
48HoursTopViewPosts/{itemCount} | GET | 48小時閱讀排行 |
bloggers/recommend/{pageIndex}/{pageSize} | GET | 分頁獲取推薦博客列表 |
bloggers/recommend/count | GET | 獲取推薦博客總數 |
bloggers/search | GET | 根據做者名搜索博主 |
post/{postId}/comments/{pageIndex}/{pageSize} | GET | 獲取文章評論 |
post/body/{postId} | GET | 獲取文章內容 |
sitehome/paged/{pageIndex}/{pageSize} | GET | 分頁獲取首頁文章列表 |
sitehome/recent/{itemcount} | GET | 獲取首頁文章列表 |
TenDaysTopDiggPosts/{itemCount} | GET | 10天內推薦排行 |
u/{blogapp}/posts/{pageIndex}/{pageSize} | GET | 分頁獲取我的博客文章列表 |
新聞服務:異步
Uri | Method | Description |
GetData | GET | 獲取新聞列表 |
hot/{itemcount} | GET | 獲取熱門新聞列表 |
item/{contentId} | GET | 獲取新聞內容 |
item/{contentId}/comments/{pageIndex}/{pageSize} | GET | 獲取新聞評論 |
recent/{itemcount} | GET | 獲取最新新聞列表 |
recent/paged/{pageIndex}/{pageSize} | GET | 分頁獲取最新新聞列表 |
recommend/paged/{pageIndex}/{pageSize} | GET | 分頁獲取推薦新聞列表 |
能夠看見,博客園官方團隊還算是挺厚道的,基本的接口都開放出來了。(除了博客文章按分類獲取-_-|||博主個人碎碎念)async
因爲須要儘量使咱們封裝好的類庫儘量在多個平臺使用,因而乎就想到了使用可移植類庫(PCL)來開發。ide
建起項目post
固然是統統選上,哪知道之後哪天會不會心血來潮再搞個什麼平臺的客戶端。測試
由於就分兩大類,因而果斷碼上 BlogService 和 NewsService 兩個靜態類。this
做爲相應服務的入口。
接下來,看見新聞的接口比較少,先寫這部分。
測試 GetData 接口。
啥玩意?!2012年的數據!那這個就不理他了。。。
接下來就是 hot(獲取熱門新聞列表)這個接口。
測試下:http://wcf.open.cnblogs.com/news/hot/10
工做良好,因而開始寫這個接口的封裝。
在 NewsService 中寫上
public static async Task<IEnumerable<News>> HotAsync(int itemCount) { // TODO }
會發覺編譯不經過(廢話)。
因而新建一個News類先。修改方法。
編譯!
不經過!!!
看錯誤列表:
不是4.5了麼?怎麼不能用 async?
解決方法:http://www.cnblogs.com/h82258652/p/4119118.html(注:本文爲總結性文章,因此時間線的跳躍大家要跟上)
裝好巨硬給咱們的異步補丁包後就能夠編譯經過了。
接下來觀察接口返回的xml文檔,能夠發現每個entry節點就至關於一條新聞。
根據entry分析,完善News類:
1 using System; 2 3 namespace SoftwareKobo.CnblogsAPI.Model 4 { 5 /// <summary> 6 /// 新聞。 7 /// </summary> 8 public class News 9 { 10 /// <summary> 11 /// Id。 12 /// </summary> 13 public int Id 14 { 15 get; 16 internal set; 17 } 18 19 /// <summary> 20 /// 標題。 21 /// </summary> 22 public string Title 23 { 24 get; 25 internal set; 26 } 27 28 /// <summary> 29 /// 摘要。 30 /// </summary> 31 public string Summary 32 { 33 get; 34 internal set; 35 } 36 37 /// <summary> 38 /// 發表時間。 39 /// </summary> 40 public DateTime Published 41 { 42 get; 43 internal set; 44 } 45 46 /// <summary> 47 /// 更新時間。 48 /// </summary> 49 public DateTime Updated 50 { 51 get; 52 internal set; 53 } 54 55 /// <summary> 56 /// 新聞連接。 57 /// </summary> 58 public Uri Link 59 { 60 get; 61 internal set; 62 } 63 64 /// <summary> 65 /// 推薦數。 66 /// </summary> 67 public int Diggs 68 { 69 get; 70 internal set; 71 } 72 73 /// <summary> 74 /// 查看數。 75 /// </summary> 76 public int Views 77 { 78 get; 79 internal set; 80 } 81 82 /// <summary> 83 /// 評論數。 84 /// </summary> 85 public int Comments 86 { 87 get; 88 internal set; 89 } 90 91 /// <summary> 92 /// 主題。 93 /// </summary> 94 public string Topic 95 { 96 get; 97 internal set; 98 } 99 100 /// <summary> 101 /// 主題圖標。 102 /// </summary> 103 public Uri TopicIcon 104 { 105 get; 106 internal set; 107 } 108 109 /// <summary> 110 /// 轉載自。 111 /// </summary> 112 public string SourceName 113 { 114 get; 115 internal set; 116 } 117 } 118 }
PS:Q:爲何set方法都寫爲internal?A:爲何外部要修改?Q:好吧,你贏了。
接下來就開始寫咱們的 HotAsync 方法了。
HotAsync 方法有一個參數——itemCount,那麼固然要進行驗證。(不發送這些無謂的請求一方面能夠不讓用戶等待、一方面減輕博客園的壓力)
if (itemCount < 1) { throw new ArgumentOutOfRangeException(nameof(itemCount)); }
nameof,還沒跟上時代節奏的小夥伴就趕忙跟上了,這裏不解釋。
接下來固然是拼接 url。
var url = string.Format(CultureInfo.InvariantCulture, HotUrlTemplate, itemCount); var uri = new Uri(url, UriKind.Absolute);
HotUrlTemplate 的定義:
private const string HotUrlTemplate = "http://wcf.open.cnblogs.com/news/hot/{0}";
常量最好不要出如今方法中這是好習慣哦。
準備WebRequest,而且調用GetResponse。
var request = WebRequest.Create(uri); using (var response = await request.GetResponseAsync()) { // TODO }
因爲返回的是一個xml,因此直接使用 XDocument 加載(PS:我特討厭XmlDocument那套API)
var document = XDocument.Load(response.GetResponseStream());
接下來就是將XDocument轉換爲News實體類的列表,這裏咱們寫到別的方法去,由於下面幾個服務接口可能也會用到。
新建NewsHelper類。
根據entry節點的結果,寫出如下代碼:
1 internal static class NewsHelper 2 { 3 internal static IEnumerable<News> Deserialize(XDocument document) 4 { 5 var root = document?.Root; 6 if (root == null) 7 { 8 return null; 9 } 10 11 var ns = root.GetDefaultNamespace(); 12 var news = from entry in root.Elements(ns + "entry") 13 where entry.HasElements 14 let temp = Deserialize(entry) 15 where temp != null 16 select temp; 17 return news; 18 } 19 20 internal static News Deserialize(XElement element) 21 { 22 if (element == null) 23 { 24 return null; 25 } 26 27 var ns = element.GetDefaultNamespace(); 28 var id = element.Element(ns + "id"); 29 var title = element.Element(ns + "title"); 30 var summary = element.Element(ns + "summary"); 31 var published = element.Element(ns + "published"); 32 var updated = element.Element(ns + "updated"); 33 var href = element.Element(ns + "link")?.Attribute("href"); 34 var diggs = element.Element(ns + "diggs"); 35 var views = element.Element(ns + "views"); 36 var comments = element.Element(ns + "comments"); 37 var topic = element.Element(ns + "topic"); 38 var topicIcon = element.Element(ns + "topicIcon"); 39 var sourceName = element.Element(ns + "sourceName"); 40 41 if (id == null 42 || title == null 43 || summary == null 44 || published == null 45 || updated == null 46 || href == null 47 || diggs == null 48 || views == null 49 || comments == null 50 || topic == null 51 || topicIcon == null 52 || sourceName == null) 53 { 54 return null; 55 } 56 57 return new News 58 { 59 Id = int.Parse(id.Value, CultureInfo.InvariantCulture), 60 Title = WebUtility.HtmlDecode(title.Value), 61 Summary = WebUtility.HtmlDecode(summary.Value), 62 Published = DateTime.Parse(published.Value, CultureInfo.InvariantCulture), 63 Updated = DateTime.Parse(updated.Value, CultureInfo.InvariantCulture), 64 Link = new Uri(href.Value, UriKind.Absolute), 65 Diggs = int.Parse(diggs.Value, CultureInfo.InvariantCulture), 66 Views = int.Parse(views.Value, CultureInfo.InvariantCulture), 67 Comments = int.Parse(comments.Value, CultureInfo.InvariantCulture), 68 Topic = topic.Value, 69 TopicIcon = topicIcon.IsEmpty ? null : new Uri(topicIcon.Value, UriKind.Absolute), 70 SourceName = sourceName.Value 71 }; 72 } 73 }
因爲咱們須要的是IEnumerable<News>,因此直接返回Linq的結果就好了,不用ToList或者啥的。
注意PCL中是沒有HtmlDecode、HtmlEncode的,nuget上有一個PCL用的,惋惜人家做者沒更新了,版本過舊,引用不了,沒辦法,本身動手豐衣足食。
項目地址:https://github.com/h82258652/SoftwareKobo.Net.WebUtility
Nuget地址:https://www.nuget.org/packages/SoftwareKobo.Net.WebUtility/
也是本身隨便寫的,反正能處理一下常見的字符就算了。(=_=)(巨硬的源碼我實在是看不懂。。還打算照抄的……)
接下來回到 HotAsync 補充最後一句:
return NewsHelper.Deserialize(document);
完事。
其餘什麼 Recent(最新的)、Recommend(推薦)如法炮製。(Recent有兩個接口,選擇分頁那個好了,反正感受博客園內部實現也是重載)
接下來看新聞內容這個接口:
寫上 DetailAsync 方法:
public static async Task<NewsDetail> DetailAsync(int newsId)
固然也有建上NewsDetail類。
1 /// <summary> 2 /// 新聞內容。 3 /// </summary> 4 public class NewsDetail 5 { 6 /// <summary> 7 /// Id。 8 /// </summary> 9 public int Id 10 { 11 get; 12 internal set; 13 } 14 15 /// <summary> 16 /// 標題。 17 /// </summary> 18 public string Title 19 { 20 get; 21 internal set; 22 } 23 24 /// <summary> 25 /// 轉載自。 26 /// </summary> 27 public string SourceName 28 { 29 get; 30 internal set; 31 } 32 33 /// <summary> 34 /// 發表時間。 35 /// </summary> 36 public DateTime SubmitDate 37 { 38 get; 39 internal set; 40 } 41 42 /// <summary> 43 /// 內容。 44 /// </summary> 45 public string Content 46 { 47 get; 48 internal set; 49 } 50 51 /// <summary> 52 /// 新聞中用到的圖片的路徑。 53 /// </summary> 54 public ReadOnlyCollection<Uri> ImageUrl 55 { 56 get; 57 internal set; 58 } 59 60 /// <summary> 61 /// 上一條新聞的 Id。 62 /// </summary> 63 public int? PrevNews 64 { 65 get; 66 internal set; 67 } 68 69 /// <summary> 70 /// 下一條新聞的 Id。 71 /// </summary> 72 public int? NextNews 73 { 74 get; 75 internal set; 76 } 77 78 /// <summary> 79 /// 評論數。 80 /// </summary> 81 public int CommentCount 82 { 83 get; 84 internal set; 85 } 86 }
注意:
一、ImageUrl 用了 ReadOnlyCollection,緣由同上,外部不必修改。
二、PreNews 和 NextNews 使用可空類型,由於不必定有上一條或下一條。(都最新了,還能有下一條麼)
接下來也是寫個Helper,將Xml的內容轉換爲對象列表。完事。
剩下來獲取評論也是差很少。
博客方面的接口封裝基本上也是這麼搞,注意的是新聞的評論和博客文章的評論能夠用同一個模型來表達。
都寫完後,感受獲取新聞獲取評論不方便啊,得先訪問Id,再調NewsService。天生懶,沒辦法,寫個擴展方法。
1 /// <summary> 2 /// 新聞擴展。 3 /// </summary> 4 public static class NewsExtension 5 { 6 /// <summary> 7 /// 獲取新聞評論。 8 /// </summary> 9 /// <param name="news">新聞。</param> 10 /// <param name="pageIndex">第幾頁,從 1 開始。</param> 11 /// <param name="pageSize">每頁條數。</param> 12 /// <returns>新聞評論。</returns> 13 /// <exception cref="ArgumentNullException">新聞爲 null。</exception> 14 public static async Task<IEnumerable<Comment>> CommentAsync(this News news, int pageIndex, int pageSize) 15 { 16 if (news == null) 17 { 18 throw new ArgumentNullException(nameof(news)); 19 } 20 return await NewsService.CommentAsync(news.Id, pageIndex, pageSize); 21 } 22 23 /// <summary> 24 /// 獲取新聞內容。 25 /// </summary> 26 /// <param name="news">新聞。</param> 27 /// <returns>新聞內容。</returns> 28 /// <exception cref="ArgumentNullException">新聞爲 null。</exception> 29 public static async Task<NewsDetail> DetailAsync(this News news) 30 { 31 if (news == null) 32 { 33 throw new ArgumentNullException(nameof(news)); 34 } 35 return await NewsService.DetailAsync(news.Id); 36 } 37 }
Q:爲何不在News類裏寫?A:沒什麼,保持模型純正而已。-_-|||也好管理。
完事了,Release編譯,弄到nuget包裏,發佈。測試添加引用,沒註釋!!!
回到項目,修改項目屬性,生成XML,勾上!!
把XML一同弄到nuget包裏,再發布,測試,好了。
At The End:
項目地址:https://github.com/h82258652/SoftwareKobo.CnblogsAPI
Nuget地址:https://www.nuget.org/packages/SoftwareKobo.CnblogsAPI/
博主我(h82258652)的話:有什麼建議歡迎在下面評論提,畢竟博主我也是菜鳥。
↑重點固然要大隻字!