一個多月之后,由淺入深表達式系列的最后一篇終于要問世了。想對所有關注的朋友說聲:“對不起,我來晚了!” 希望最后一篇的內容對得起這一個月時間的等待。在學習完表達式樹的創建和遍歷之后,我們要利用它的特性來寫一個我們自己的Linq Provider。人家都有Linq to Amazon為什么我們不能有Linq to cnblogs呢?今天我們就來一步一步的打造自己的Linq Provider,文章未尾已附上源碼下載地址。如果對于表達式樹的創建和遍歷還是熟悉的話,建議先看前面兩篇:
創建表達式樹
http://www.cnblogs.com/jesse2013/p/expressiontree-part1.html
遍歷表達式樹
http://www.cnblogs.com/jesse2013/p/expressiontree-part2.html
更新:之前沒有描述清楚本篇博客的意圖,導致很多朋友的誤解表示抱歉。本系列重在理解表達式目錄樹,以及Linq Provider。最后一篇是Linq Provider的實現,之所有會寫這么多的代碼去做一件簡單的事(拉取博客園首頁文章列表)完全是為了有一個生動的例子去展示如何實現自己的Linq Provider。和我們項目中的三層架構,或者直接序列化到本地是沒有可比性的。
當然,表達式目錄樹以及Linq Provider的強大也遠非這個小小的Demo能體現得了的,如果你真正知道Linq Provider和表達式樹目錄樹是什么,用來干什么的,也許你就能明白本篇博客的意圖了。如果不了解的,建議讀完前面兩篇之后再做評論。因為你在自己不理解的情況下就直接去評論其它的領域,你就失去了一個了解它的機會。:)
目錄
實現目標
我們實現的目標就像Linq to SQL一樣,可以用Linq查詢語句來查詢數據,我們這里面的數據用到了博客園官方的Service去查詢到最新的發布到首頁的博客信息。看下面的代碼:
var provider = new CnblogsQueryProvider(); var queryable = new Query<Post>(provider); var query = from p in queryable where p.Diggs >= 10 && p.Comments > 10 && p.Views > 10 && p.Comments < 20 select p; var list = query.ToList();
作為實時訪問遠程的Service,我們還應該具體以下幾點要求:
- 延遲加載,即到最后使用的時候才真正的去請求數據
- 只返回需要的數據,不能把所有的數據全下載過來再到本地過濾,那就沒有意義了
最后實現的結果:
數據準備
根據博客園公開的API顯示,獲取首頁文章列表非常容易,大家可以點下面的URL來體檢一把。我們最后給的參數是100000,當然真實返回肯定是沒有那么多的,我們是希望把能夠取回來的都取回來。
http://wcf.open.cnblogs.com/blog/sitehome/recent/100000
點擊完上面的URL之后呢,問題就來了,它只有一個參數。我并不能傳給它查詢條件,比如說根據標題來搜索,或者根據評論數,瀏覽量來過濾。難道我的計劃就此要泡湯了么,剛開始我很不開心,為什么博客園就不能提供靈活一點的Service呢?但是事實就是這樣,咋是程序員呀,需求擺在這,怎么著還得實現是不?沒有辦法,我給它封裝了一層。在它的基礎上做了一個自己的Service。
封裝博客園Service
我們如何在博客園公開Service的基礎上加一層實現條件查詢呢?主要思路是這樣的:
- 為文章建立實體類(Post)
- 將博客園Service返回的數據解析成Post的集合,我們可以加上自己的緩存機制,可以采用1分鐘才到博客園取一次數據
- 把我們上面創建的post集合當作數據庫,建立查詢Service
我們首先要做的就是為博客園的博客建立實體類。
public class Post { // Id public int Id { get; set; } // 標題 public string Title { get; set; } // 發布時間 public DateTime Published { get; set; } // 推薦數據 public int Diggs { get; set; } // 訪問人數 public int Views { get; set; } // 評論數據 public int Comments { get; set; } // 作者 public string Author { get; set; } // 博客鏈接 public string Href { get; set; } // 摘要 public string Summary { get; set; } }
接著,我們要將博客園返回給我們的XML數據轉換成我們要的post集合,所以我們要用到Linq to XML。
_list = new List<CnblogsLinqProvider.Post>(); var document = XDocument.Load( "http://wcf.open.cnblogs.com/blog/sitehome/recent/100000" ); var elements = document.Root.Elements(); var result = from entry in elements where entry.HasElements == true select new CnblogsLinqProvider.Post { Id = Convert.ToInt32(entry.Elements() .SingleOrDefault(x => x.Name.LocalName == "id").Value), Title = entry.Elements() .SingleOrDefault(x => x.Name.LocalName == "title").Value, Published = Convert.ToDateTime(entry.Elements() .SingleOrDefault(x => x.Name.LocalName == "published").Value), Diggs = Convert.ToInt32(entry.Elements() .SingleOrDefault(x => x.Name.LocalName == "diggs").Value), Views = Convert.ToInt32(entry.Elements() .SingleOrDefault(x => x.Name.LocalName == "views").Value), Comments = Convert.ToInt32(entry.Elements() .SingleOrDefault(x => x.Name.LocalName == "comments").Value), Summary = entry.Elements() .SingleOrDefault(x=>x.Name.LocalName=="summary").Value, Href = entry.Elements() .SingleOrDefault(x => x.Name.LocalName == "link") .Attribute("href").Value, Author = entry.Elements() .SingleOrDefault(x => x.Name.LocalName == "author") .Elements() .SingleOrDefault(x => x.Name.LocalName == "name").Value }; _list.AddRange(result);
然后就到了我們的查詢了,實際上我們有了IEnumrable<Post>的數據就可以直接在本地用Linq去查詢它了。但是這不是我們想要的,因為我們上面的步驟是把所有的數據一次性全部下載下來了,而不是根據我們的需求返回數據。另外我們這里面是在博客園Service的基礎上做一層封裝,實現通過Url直接查詢首頁的文章。為什么要通過Url來查詢?因為我們最后會通過我們自己的LinqProvider將Linq查詢語句直接翻譯成Url這樣就能夠實現遠程的返回數據了。來看看我們對Url參數的定義:
- 標題中包括模式的文章:http://linqtocnblogs.cloudapp.net?Title=模式
- 訪問人數大于5并且評論大于10的文章 http://linqtocnblogs.cloudapp.net?MinViews=5&MinComments=10
利用JsonResult 返回json數據來創建我們的Service
作為Service,我們返回Json或者XML格式的數據都是可以的。當然實現這個需求的方法有很多種,我們這里面有選了一種最簡單方便又比較適合我們需求方式。不需要WCF Service也不需要Web API,直接用MVC里面的Action返回JsonResult就可以了。
[HttpGet] public JsonResult Index(SearchCriteria criteria = null) { var result = PostManager.Posts; if (criteria != null) { if (!string.IsNullOrEmpty(criteria.Title)) result = result.Where( p => p.Title.IndexOf(criteria.Title, StringComparison.OrdinalIgnoreCase) >= 0); if (!string.IsNullOrEmpty(criteria.Author)) result = result.Where(p => p.Author.IndexOf(criteria.Author, StringComparison.OrdinalIgnoreCase) >= 0); if (criteria.Start.HasValue) result = result.Where(p => p.Published >= criteria.Start.Value); if (criteria.End.HasValue) result = result.Where(p => p.Published <= criteria.End.Value); if (criteria.MinComments > 0) result = result.Where(p => p.Comments >= criteria.MinComments); if (criteria.MinDiggs > 0) result = result.Where(p => p.Diggs >= criteria.MinDiggs); if (criteria.MinViews > 0) result = result.Where(p => p.Diggs >= criteria.MinViews); if (criteria.MaxComments > 0) result = result.Where(p => p.Comments <= criteria.MaxComments); if (criteria.MaxDiggs > 0) result = result.Where(p => p.Diggs <= criteria.MaxDiggs); if (criteria.MaxViews > 0) result = result.Where(p => p.Diggs <= criteria.MaxViews); } return Json(result, JsonRequestBehavior.AllowGet); }
利用Action來做這種Service還有一個好處就是我們不需要一個一個的聲明查詢參數,只需要把所有的參數放到一個model中就可以了。剩下的事就交給Model Binder吧。
public class SearchCriteria { public string Title { get; set; } public string Author { get; set; } public DateTime? Start { get; set; } public DateTime? End { get; set; } public int MinDiggs { get; set; } public int MaxDiggs { get; set; } public int MinViews { get; set; } public int MaxViews { get; set; } public int MinComments { get; set; } public int MaxComments { get; set; } }
如果大家想更熟悉這個Service的功能,可以參考上面的參數自己去體驗一下(用IE會直接下載.json的文件,用Chrom是可以直接在瀏覽器里面看數據的)。但是我沒有做任何安全性的措施,希望大俠高抬貴手,別把網站整掛了就行。
認識IQueryable和IQueryProvider接口
有了上面的Service之后,我們要做的事情就簡單多了,但是在我們真正開始動手寫自己的Linq Provider之前,先來看看IQueryable和IQueryProvider這兩個重要的接口。
IQueryable
IQueryable本身并沒有包含多少的東西,它只有三個屬性:
- ElementType 代表當然這個Query所對應的類型
- Expression 包含了我們當然Query所執行的所有查詢或者是其它的操作
- IQueryProvider則是負責處理上面的Expression的實現
更為重要的是,在IQueryable這個接口之上,.net為我們提供了很多的擴展方法:
我們平常用到的Where,Select,Max,Any都包括在其中,具體的方法大家可以到System.Linq.Queryable這個靜態類下去看。大家注意一下,傳給Where方法的正是我們現在學習的Expression。
在另外一個很重要的接口IEnumrable下,也有著同樣的擴展方法:
這些擴展方法來自System.Linq.Enumrable這個靜態類下。我們可以看到兩組擴展方法的不同之處在于IQueryable下傳入的Expression類型,而IEnumrable下傳入的是委托。這樣做的用意是什么呢?您請接著往下看。
IQueryProvider
我們上面講到了Enumrable和Queryable這兩個靜態類下的擴展方法,對于Enumrable下的擴展方法來說他們傳入的是委托,對于委托而言直接執行就可以了。
public static IEnumerable<T> Where<T>(this IEnumerable<T> list, Func<T, bool> predicate) { var result = new List<T>(); foreach (var element in list) { // 調用委托是驗證這個元素是否符合條件 if(predicate(element)) result.Add(element); } return result; }
上面的代碼給大家作一個參考,相信不難理解,所以Enumrable下的靜態方法都是操作本地數據的。而對于Queryable下的靜態方法而言,他們接收的是表達式,還記得表達式的最大特征嗎?可以在運行時去遍歷解釋然后執行,那么這樣就可以將表達式轉換成各種其它的方式去獲取數據,偉大的Linq to SQL就是這么實現的。而這背后的大功臣就是我們的Linq Provider了,而IQueryProvider就是LinqProvider的接口。
IQueryProvider只有兩個操作,CreateQuery和Execute分別有泛型版本和非泛型版本。 CreatQuery用于構造一個IQueryable<T>的對象,這個類其實沒有任何實現,只是繼承了IQueryable和IEnumrable接口。主要用于計算指定表達式目錄樹所表示的查詢,返回的結果是一個可枚舉的類型。 而Execute會執行指定表達式目錄樹所表示的查詢,返回指定的結果。所有的內幕就在這個Execute方法里面,拿我們要進行的Linq to cnblogs方法來舉例,我們將把傳入的表達式目錄樹翻譯成一個URL就是指向我們封裝好的Service的URL,通過發起web request到這個URL,拿到response進行解析,最終得到我們所要的數據,這就是我們Linq to cnblogs的思路。
Linq to cnblogs的實現
有了前面的數據準備和一些實現的大致思路以后,我們就可以著手開始實現我們的CnblogsQueryProvider了。我們的思路大致是這樣的:
- 實現自己的ExpressionVisitor類去訪問表達式目錄數,將其翻譯成可以訪問Service的Url
- 調用WebRequest去訪問這個Url
- 將上面返回的Response解析成我們要的對象
實現PostExpressionVisitor
關于表達式樹的訪問,我們在第二篇中已經有了比較詳細的介紹。如果對于表達式樹的遍歷不清楚的,可以去第二篇《遍歷表達式》中查閱。在這里,我們創建一個我們自己的ExpressionVisitor類,去遍歷表達式樹。我們暫時只需要生成一個SearchCriteria(我們上面已經定義好了,對于查詢條件建的模)對象即可。

1 public class PostExpressionVisitor 2 { 3 private SearchCriteria _criteria; 4 5 // 入口方法 6 public SearchCriteria ProcessExpression(Expression expression) 7 { 8 _criteria = new SearchCriteria(); 9 VisitExpression(expression); 10 return _criteria; 11 } 12 13 private void VisitExpression(Expression expression) 14 { 15 switch (expression.NodeType) 16 { 17 // 訪問 && 18 case ExpressionType.AndAlso: 19 VisitAndAlso((BinaryExpression)expression); 20 break; 21 // 訪問 等于 22 case ExpressionType.Equal: 23 VisitEqual((BinaryExpression)expression); 24 break; 25 // 訪問 小于和小于等于 26 case ExpressionType.LessThan: 27 case ExpressionType.LessThanOrEqual: 28 VisitLessThanOrEqual((BinaryExpression)expression); 29 break; 30 // 訪問大于和大于等于 31 case ExpressionType.GreaterThan: 32 case ExpressionType.GreaterThanOrEqual: 33 GreaterThanOrEqual((BinaryExpression)expression); 34 break; 35 // 訪問調用方法,主要有于解析Contains方法,我們的Title會用到 36 case ExpressionType.Call: 37 VisitMethodCall((MethodCallExpression)expression); 38 break; 39 // 訪問Lambda表達式 40 case ExpressionType.Lambda: 41 VisitExpression(((LambdaExpression)expression).Body); 42 break; 43 } 44 } 45 46 // 訪問 && 47 private void VisitAndAlso(BinaryExpression andAlso) 48 { 49 VisitExpression(andAlso.Left); 50 VisitExpression(andAlso.Right); 51 } 52 53 // 訪問 等于 54 private void VisitEqual(BinaryExpression expression) 55 { 56 // 我們這里面只處理在Author上的等于操作 57 // Views, Comments, 和 Diggs 我們都是用的大于等于,或者小于等于 58 if ((expression.Left.NodeType == ExpressionType.MemberAccess) && 59 (((MemberExpression)expression.Left).Member.Name == "Author")) 60 { 61 if (expression.Right.NodeType == ExpressionType.Constant) 62 _criteria.Author = 63 (String)((ConstantExpression)expression.Right).Value; 64 65 else if (expression.Right.NodeType == ExpressionType.MemberAccess) 66 _criteria.Author = 67 (String)GetMemberValue((MemberExpression)expression.Right); 68 69 else 70 throw new NotSupportedException("Expression type not supported for author: " + 71 expression.Right.NodeType.ToString()); 72 } 73 } 74 75 // 訪問大于等于 76 private void GreaterThanOrEqual(BinaryExpression expression) 77 { 78 // 處理 Diggs >= n 推薦人數 79 if ((expression.Left.NodeType == ExpressionType.MemberAccess) && 80 (((MemberExpression)expression.Left).Member.Name == "Diggs")) 81 { 82 if (expression.Right.NodeType == ExpressionType.Constant) 83 _criteria.MinDiggs = 84 (int)((ConstantExpression)expression.Right).Value; 85 86 else if (expression.Right.NodeType == ExpressionType.MemberAccess) 87 _criteria.MinDiggs = 88 (int)GetMemberValue((MemberExpression)expression.Right); 89 90 else 91 throw new NotSupportedException("Expression type not supported for Diggs:" 92 + expression.Right.NodeType.ToString()); 93 } 94 // 處理 Views>= n 訪問人數 95 else if ((expression.Left.NodeType == ExpressionType.MemberAccess) && 96 (((MemberExpression)expression.Left).Member.Name == "Views")) 97 { 98 if (expression.Right.NodeType == ExpressionType.Constant) 99 _criteria.MinViews = 100 (int)((ConstantExpression)expression.Right).Value; 101 102 else if (expression.Right.NodeType == ExpressionType.MemberAccess) 103 _criteria.MinViews = 104 (int)GetMemberValue((MemberExpression)expression.Right); 105 106 else 107 throw new NotSupportedException("Expression type not supported for Views: " 108 + expression.Right.NodeType.ToString()); 109 } 110 // 處理 comments >= n 評論數 111 else if ((expression.Left.NodeType == ExpressionType.MemberAccess) && 112 (((MemberExpression)expression.Left).Member.Name == "Comments")) 113 { 114 if (expression.Right.NodeType == ExpressionType.Constant) 115 _criteria.MinComments = 116 (int)((ConstantExpression)expression.Right).Value; 117 118 else if (expression.Right.NodeType == ExpressionType.MemberAccess) 119 _criteria.MinComments = 120 (int)GetMemberValue((MemberExpression)expression.Right); 121 122 else 123 throw new NotSupportedException("Expression type not supported for Comments: " 124 + expression.Right.NodeType.ToString()); 125 } 126 // 處理 發布時間>= 127 else if ((expression.Left.NodeType == ExpressionType.MemberAccess) && 128 (((MemberExpression)expression.Left).Member.Name == "Published")) 129 { 130 if (expression.Right.NodeType == ExpressionType.Constant) 131 _criteria.Start = 132 (DateTime)((ConstantExpression)expression.Right).Value; 133 134 else if (expression.Right.NodeType == ExpressionType.MemberAccess) 135 _criteria.Start = 136 (DateTime)GetMemberValue((MemberExpression)expression.Right); 137 138 else 139 throw new NotSupportedException("Expression type not supported for Published: " 140 + expression.Right.NodeType.ToString()); 141 } 142 } 143 144 // 訪問 小于和小于等于 145 private void VisitLessThanOrEqual(BinaryExpression expression) 146 { 147 // 處理 Diggs <= n 推薦人數 148 if ((expression.Left.NodeType == ExpressionType.MemberAccess) && 149 (((MemberExpression)expression.Left).Member.Name == "Diggs")) 150 { 151 if (expression.Right.NodeType == ExpressionType.Constant) 152 _criteria.MaxDiggs = 153 (int)((ConstantExpression)expression.Right).Value; 154 155 else if (expression.Right.NodeType == ExpressionType.MemberAccess) 156 _criteria.MaxDiggs = 157 (int)GetMemberValue((MemberExpression)expression.Right); 158 159 else 160 throw new NotSupportedException("Expression type not supported for Diggs: " 161 + expression.Right.NodeType.ToString()); 162 } 163 // 處理 Views<= n 訪問人數 164 else if ((expression.Left.NodeType == ExpressionType.MemberAccess) && 165 (((MemberExpression)expression.Left).Member.Name == "Views")) 166 { 167 if (expression.Right.NodeType == ExpressionType.Constant) 168 _criteria.MaxViews = 169 (int)((ConstantExpression)expression.Right).Value; 170 171 else if (expression.Right.NodeType == ExpressionType.MemberAccess) 172 _criteria.MaxViews = 173 (int)GetMemberValue((MemberExpression)expression.Right); 174 175 else 176 throw new NotSupportedException("Expression type not supported for Views: " 177 + expression.Right.NodeType.ToString()); 178 } 179 // 處理 comments <= n 評論數 180 else if ((expression.Left.NodeType == ExpressionType.MemberAccess) && 181 (((MemberExpression)expression.Left).Member.Name == "Comments")) 182 { 183 if (expression.Right.NodeType == ExpressionType.Constant) 184 _criteria.MaxComments = 185 (int)((ConstantExpression)expression.Right).Value; 186 187 else if (expression.Right.NodeType == ExpressionType.MemberAccess) 188 _criteria.MaxComments = 189 (int)GetMemberValue((MemberExpression)expression.Right); 190 191 else 192 throw new NotSupportedException("Expression type not supported for Comments: " 193 + expression.Right.NodeType.ToString()); 194 } 195 196 // 處理發布時間 <= 197 else if ((expression.Left.NodeType == ExpressionType.MemberAccess) && 198 (((MemberExpression)expression.Left).Member.Name == "Published")) 199 { 200 if (expression.Right.NodeType == ExpressionType.Constant) 201 _criteria.End = 202 (DateTime)((ConstantExpression)expression.Right).Value; 203 204 else if (expression.Right.NodeType == ExpressionType.MemberAccess) 205 _criteria.End = 206 (DateTime)GetMemberValue((MemberExpression)expression.Right); 207 208 else 209 throw new NotSupportedException("Expression type not supported for Published: " 210 + expression.Right.NodeType.ToString()); 211 } 212 } 213 214 // 訪問 方法調用 215 private void VisitMethodCall(MethodCallExpression expression) 216 { 217 if ((expression.Method.DeclaringType == typeof(Queryable)) && 218 (expression.Method.Name == "Where")) 219 { 220 VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand); 221 } 222 else if ((expression.Method.DeclaringType == typeof(String)) && 223 (expression.Method.Name == "Contains")) 224 { 225 // Handle Title.Contains("") 226 if (expression.Object.NodeType == ExpressionType.MemberAccess) 227 { 228 MemberExpression memberExpr = (MemberExpression)expression.Object; 229 if (memberExpr.Expression.Type == typeof(Post)) 230 { 231 if (memberExpr.Member.Name == "Title") 232 { 233 Expression argument; 234 argument = expression.Arguments[0]; 235 if (argument.NodeType == ExpressionType.Constant) 236 { 237 _criteria.Title = 238 (String)((ConstantExpression)argument).Value; 239 } 240 else if (argument.NodeType == ExpressionType.MemberAccess) 241 { 242 _criteria.Title = 243 (String)GetMemberValue((MemberExpression)argument); 244 } 245 else 246 { 247 throw new NotSupportedException("Expression type not supported: " 248 + argument.NodeType.ToString()); 249 } 250 } 251 } 252 } 253 } 254 else 255 { 256 throw new NotSupportedException("Method not supported: " 257 + expression.Method.Name); 258 } 259 } 260 261 // 獲取屬性值 262 private Object GetMemberValue(MemberExpression memberExpression) 263 { 264 MemberInfo memberInfo; 265 Object obj; 266 267 if (memberExpression == null) 268 throw new ArgumentNullException("memberExpression"); 269 270 271 if (memberExpression.Expression is ConstantExpression) 272 obj = ((ConstantExpression)memberExpression.Expression).Value; 273 else if (memberExpression.Expression is MemberExpression) 274 obj = GetMemberValue((MemberExpression)memberExpression.Expression); 275 else 276 throw new NotSupportedException("Expression type not supported: " 277 + memberExpression.Expression.GetType().FullName); 278 279 memberInfo = memberExpression.Member; 280 if (memberInfo is PropertyInfo) 281 { 282 PropertyInfo property = (PropertyInfo)memberInfo; 283 return property.GetValue(obj, null); 284 } 285 else if (memberInfo is FieldInfo) 286 { 287 FieldInfo field = (FieldInfo)memberInfo; 288 return field.GetValue(obj); 289 } 290 else 291 { 292 throw new NotSupportedException("MemberInfo type not supported: " 293 + memberInfo.GetType().FullName); 294 } 295 } 296 }
實現CnblogsQueryProvider
有了上面的訪問類之后,我們的CnblogsQueryProvider就非常簡單了。

1 public class CnblogsQueryProvider:QueryProvider 2 { 3 public override String GetQueryText(Expression expression) 4 { 5 SearchCriteria criteria; 6 7 // 翻譯查詢條件 8 criteria = new PostExpressionVisitor().ProcessExpression(expression); 9 10 // 生成URL 11 String url = PostHelper.BuildUrl(criteria); 12 13 return url; 14 } 15 16 public override object Execute(Expression expression) 17 { 18 String url = GetQueryText(expression); 19 IEnumerable<Post> results = PostHelper.PerformWebQuery(url); 20 return results; 21 } 22 }
我們里面只覆蓋了基類的兩個方法,GetQueryText和Execute。
- GetQueryText根據訪問類得到的SearchCriteria去和成訪問Service的Url
- Execute訪問則負責去請求這個Url拿到數據返回即可
PostHelper請求數據
我們這里面有一個幫助類PostHelper,就負責了根據criteria生成Url以及請求Url獲取數據的功能。

1 static internal string BuildUrl(SearchCriteria criteria,string url=null) 2 { 3 if (criteria == null) 4 throw new ArgumentNullException("criteria"); 5 6 var sbUrl = new StringBuilder(url ?? "http://linqtocnblogs.cloudapp.net/"); 7 var sbParameter = new StringBuilder(); 8 9 if (!String.IsNullOrEmpty(criteria.Title)) 10 AppendParameter(sbParameter, "Title", criteria.Title); 11 12 if (!String.IsNullOrEmpty(criteria.Author)) 13 AppendParameter(sbParameter, "Author", criteria.Author); 14 15 if (criteria.Start.HasValue) 16 AppendParameter(sbParameter, "Start", criteria.Start.Value.ToString()); 17 18 if (criteria.End.HasValue) 19 AppendParameter(sbParameter, "End", criteria.End.Value.ToString()); 20 21 if (criteria.MinDiggs > 0) 22 AppendParameter(sbParameter, "MinDiggs", criteria.MinDiggs.ToString()); 23 24 if (criteria.MinViews > 0) 25 AppendParameter(sbParameter, "MinViews", criteria.MinViews.ToString()); 26 27 if (criteria.MinComments> 0) 28 AppendParameter(sbParameter, "MinComments", 29 criteria.MinComments.ToString()); 30 31 if (criteria.MaxDiggs > 0) 32 AppendParameter(sbParameter, "MaxDiggs", criteria.MaxDiggs.ToString()); 33 34 if (criteria.MaxViews > 0) 35 AppendParameter(sbParameter, "MaxViews", criteria.MaxViews.ToString()); 36 37 if (criteria.MaxComments > 0) 38 AppendParameter(sbParameter, "MaxComments", 39 criteria.MaxComments.ToString()); 40 41 if (sbParameter.Length > 0) 42 sbUrl.AppendFormat("?{0}", sbParameter.ToString()); 43 44 return sbUrl.ToString(); 45 } 46 47 static internal void AppendParameter(StringBuilder sb,string name,string value) 48 { 49 if (sb.Length > 0) 50 sb.Append("&"); 51 52 sb.AppendFormat("{0}={1}",name,value); 53 } 54 55 static internal IEnumerable<Post> PerformWebQuery(string url) 56 { 57 var request = WebRequest.Create(url); 58 request.Credentials = CredentialCache.DefaultCredentials; 59 60 var response = (HttpWebResponse)request.GetResponse(); 61 using(var reader= new StreamReader(response.GetResponseStream())) 62 { 63 var body = reader.ReadToEnd(); 64 return JsonConvert.DeserializeObject<List<Post>>(body); 65 } 66 } 67 }
因為我們的Service是返回的Json數據,所以這里,我們借助了Json.Net將其轉成我們所要的List<Post>的數據。
就是這么簡單,我們的Linq to cnblogs就大功告成了。
點擊這里下載源碼:http://pan.baidu.com/s/1gd85l1T
文章列表