一个多月之后,由浅入深表达式系列的最后一篇终于要问世了。想对所有关注的朋友说声:“对不起,我来晚了!” 希望最后一篇的内容对得起这一个月时间的等待。在学习完表达式树的创建和遍历之后,我们要利用它的特性来写一个我们自己的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