在30分钟内为StackOverflow创建一个包含XML 和JSON的OData API
[原文发表地址] Creating an OData API for StackOverflow including XML and JSON in 30 minutes
[原文发表时间] 2010-03-28 17:29
昨晚我给Jeff Atwood发了一封只有一行的电子邮件。 “你应当使用Odata制作StackOverflow API。”然后我意识到了,正如Linus说的,“嘴上说很容易,请展示出你的代码”。因此在飞机上我使用Odata创建了StackOverflow API的一个最初的原型。我本来分配了整个12小时的飞行时间。不幸的是它只花了我30分钟,因此在剩余的时间里我在看电影。
你可以跟着(看)下去,并且如果你愿意的话可以自己亲手做。
准备工作
在我上飞机前,我下载了两个东西.
首先,我下载了Sam Saffron的“So Slow” StackOverflow SQL Server Importer . 这是Sam的一个小spike,它从StackOverflow每月的转储中提取3gigs of XML Dump Files并把它输入到SQL Server里。
第二,我下载了StackOverflow Monthly Dump。我用uTorrent下载并解压它,以为飞行做准备。
输入到SQL Server
我进入Visual Studio 2010(尽管我本可以使用2008版本,我非常喜欢2010版本中的Entity Framework改进,它使这项工作更加地简单。)我右击了Server Explorer中的Data Connections节点并在SQL Express中创建了一个数据库,叫做,嗯,"StackOverflow。"
接下来,在Visual Studio 中我从Sam的project里打开他的RecreateDB.sql文件(尽管可以,但我避免使用SQL Server Management Studio)并连接到".\SQLEXPRESS",选择新的StackOverflow数据库并点击“执行”。
Sam的SQL文件的一个nit是,它虽然能创建很好地与转储排列在一起的表格,但它却不包含任何referential integrity。表格互不能识别且也没有基数设置。在没有Google Bing的前提下,我已经改写了那些我知道该怎么做的事情,我想我会稍侯再做处理。你也会这么做的。
接下来,我打开了Sam的SoSlow应用程序并运行它。它是一个漂亮的、如广告里那样的很小的应用程序,具有一个十分直观的用户界面。我很可能将“输入”按钮命名为如“放了那条猎犬!”一类的东西了,但是这就是我。
在这里我有一个漂亮的几百兆大小的数据库,它装满了StackOverflow公共数据。
创建一个Web Project及一个Web Project
现在,从Visual Studio里,我选择了File | New Project | ASP.NET Web Application。然后我右击了随之生成的project并选择了Add | New Item,然后点击Data,ADO.NET Entity Data Model。
那个又是怎么回事呢?Hanselman。你知道StackOverflow是使用LINQ to SQL的吗?你最终售罄了吗,并且是不是试图在这个巧妙伪装的博客帖子里把Entity Framework偷偷地强加给我们?
不。出于一些原因我使用了EF。首先,在Visual Studio 2010里它的速度足够快(运行时和设计时都快),以至于我都注意不到差别了。其二,我知道formal referential integrity的缺少将会是一个问题(还记得吗?我之前提到过它)并且LINQ to SQL是physical/logical 1:1 ,而EF能提供灵活的映射,我估计使用EF将更为简易。其三,"WCF Data Services"(这个data services之前称为ADO.NET Data Services 或 "Astoria")能够很好地映射到EF。
我把它命名为StackOverflowEntities.edmx并选取了"Update Model from Database", 然后选取了所有表格以便进行启动。当designer被打开后,我注意到并没有reference lines,只有独自存在的表格。
因此关于SQL Server中表格之间没有联系这一点我是对的。如果我更加聪明的话,我就应当把SQL衔接起来使之包含这些关系,但我想我仍可以在这里把它们添加上去以及其他一些可以使我们的OData Service用起来更加愉快的一些东西。
我首先查看帖子,有可能在看这个API里的一篇帖子,我想要看评论。因此,我右击了一篇帖子并点击了Add | Association。 那个对话框花了我一些时间来读懂(我以前从未见过它)然后我意识到它正在底部创建一个英语句子,因此我就只是关注使那个句子正确 。
在这种情况下,“帖子可以有*(许多)评论实例。使用Post.Comments来处理评论实例。评论可以有1(一)个帖子实例。使用Comment.Post来处理帖子实例。” 正是我想要的。同时我也有了外键属性,因此我未核取那个并点击了确定。
在这儿--Designer里,难倒了我。注意带1...*的行,以及帖子上的Comments Navigation Property和评论上的Post Navigation Property。这都来自那个对话框。
接下来,考虑到我没有让它自动生成外键属性,我必须得自己映射它们。我双击了Association Line。我把Post选做Principal并把它的Id映射到Comments里的PostId属性上。
搞清楚了这个后,我就只为很明显的事情多次做了同样的事情,就像在这个图表中看到的一样:Users有Badges,Posts有Votes,等等。
现在,让我们来做一个服务。
创建一个OData Service
在Solution Explorer中右击Project并选择Add | New Item | Web | WCF Data Service。 我把我的命名为Service.svc。要拥有一个完整的、工作的OData service在技术上你所要做的只是在尖括号(DataService<YourTypeHere>)间添加一个class,并为config.EntitySetAccessRule准备一行。这是我最初的最小的class。在我尝试获取所有的帖子后我添加了SetEntitySetPageSize。;)
1: public class Service : DataService<StackOverflowEntities>
2: {
3: // This method is called only once to initialize service-wide policies.
4: public static void InitializeService(DataServiceConfiguration config)
5: {
6: config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
7:
8: //Set a reasonable paging site
9: config.SetEntitySetPageSize("*", 25);
10:
11: config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
12: }
13: }
扩展了这个class后,我添加了caching,以及一个Service Operation样本,还有WCF Data Services support for JSONP。请注意Service Operation只是在那儿的一个样本用来向StackOverflow显示它们可以有完全的控制。使用Odata并不意味着检查一个box并把你的数据库放到web上。它意味着可根据你的喜好以任意的间隔尺度来暴露具体的entities。你可以拦截查询,作出自定义行为(就像JSONP那个一样),创建定制Service Operations(当然,它们可以包含查询字符串),等等。Odata天生就支持JSON,并且当一个accept: header被设置后它就会返回JSON,但是我添加了JSONP support来允许跨域服务的使用以及允许URL里的格式参数,人工操作比较亲睐于这个,因为它更简单。
1: namespace StackOveflow
2: {
3: [JSONPSupportBehavior]
4: public class Service : DataService<StackOverflowEntities>
5: {
6: // This method is called only once to initialize service-wide policies.
7: public static void InitializeService(DataServiceConfiguration config)
8: {
9: config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
10:
11: //This could be "*" and could also be ReadSingle, etc, etc.
12: config.SetServiceOperationAccessRule("GetPopularPosts", ServiceOperationRights.AllRead);
13:
14: //Set a reasonable paging site
15: config.SetEntitySetPageSize("*", 25);
16:
17: config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
18: }
19:
20: protected override void OnStartProcessingRequest(ProcessRequestArgs args)
21: {
22: base.OnStartProcessingRequest(args);
23: //Cache for a minute based on querystring
24: HttpContext context = HttpContext.Current;
25: HttpCachePolicy c = HttpContext.Current.Response.Cache;
26: c.SetCacheability(HttpCacheability.ServerAndPrivate);
27: c.SetExpires(HttpContext.Current.Timestamp.AddSeconds(60));
28: c.VaryByHeaders["Accept"] = true;
29: c.VaryByHeaders["Accept-Charset"] = true;
30: c.VaryByHeaders["Accept-Encoding"] = true;
31: c.VaryByParams["*"] = true;
32: }
33:
34: [WebGet]
35: public IQueryable<Post> GetPopularPosts()
36: {
37: var popularPosts = (from p in this.CurrentDataSource.Posts
38: orderby p.ViewCount
39: select p).Take(20);
40:
41: return popularPosts;
42: }
43: }
44: }
但是这使我们得到什么呢?又怎么样呢
用Odata处理StackOverflow的数据
恩,如果我点击<https://mysite/service.svc我就会看到这项服务。请注意相关的HREFs>。
如果我点击https://173.46.159.103/service.svc/Posts我就会得到这些帖子(分成页的,就像我已经提到的那样)。请看其中关键的地方。注意到内容前面的<link>了吗?注意到相关的href="Posts(23)"了吗?
还记得我之前设置的所有那些联系吗?现在我可以看到:
- 获取一个帖子:https://173.46.159.103/service.svc/Posts(23)/
- 在一个帖子上投票:https://173.46.159.103/service.svc/Posts(23)/Votes
- 获取一个帖子并同时获取所有的评论:https://173.46.159.103/service.svc/Posts(23)?$expand=Comments
- 我甚至可以在一个评论或帖子之外定位一个用户的细节:https://173.46.159.103/service.svc/Posts(23)/Comments(55049)/User
但那只是导航。我还可以做查询。 去下载LINQPad Beta for .NET 4。点击Add Connection并放进我的小Add Connection server里去。
免责声明:这是一个测试server—Orcsweb随时可能出问题。同时请注意,你也可以在https://www.vs2010host.com注册你自己的或者在ASP.NET里找到一个主机或者在cloud里host你自己的OData。
我把这个放了进去并点击确定。
现在我正在网上根据StackOverflow编写LINQ queries。没有任何Twitter式的API,JSON或其它什么东西可以做这个。StackOverflow是为StackOverflow准备的。我对这鼓弄的越多,越加意识到这是真的。
这个LINQ query事实上转换成了这个URL。同样,你不需要.NET,它只是HTTP:
用accept: application/json的一个accept header来尝试同样的事情或者仅仅添加 $format=json
它就会自动返回与JSON 或 Atom同样的数据,如你喜欢的那样。
如果你已经有了Visual Studio,只需快速创建一个控制台应用程序. File | New Console App,然后右击references并点击Add Service Reference。放进https://173.46.159.103/service.svc里并点击确定。
尝试一些诸如这样的东西。我把URIs放进评论里以证明并没有什么欺骗。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: StackOverflowEntities so = new StackOverflowEntities(new Uri("https://173.46.159.103/service.svc"));
6:
7: //{https://173.46.159.103/service.svc/Users()?$filter=substringof('Hanselman',DisplayName)}
8: var user = from u in so.Users
9: where u.DisplayName.Contains("Hanselman")
10: select u;
11:
12: //{https://173.46.159.103/service.svc/Posts()?$filter=OwnerUserId eq 209}
13: var posts =
14: from p in so.Posts
15: where p.OwnerUserId == user.Single().Id
16: select p;
17:
18: foreach (Post p in posts)
19: {
20: Console.WriteLine(p.Body);
21: }
22:
23: Console.ReadLine();
24: }
25: }
我可以用PHP, JavaScript等里面的例子继续下去,但是你已经明白了。
结语
StackOverflow在他们的数据上一直都是非常地开放、量大。我建议OData endpint应当比一个定制OData endpint和/或JSON API给予我们更大的灵活性来处理它们的数据,因此它们需要不断被还原。
有了专有的API,人们将急于创建多种语言的StackOverflow clients,但是这项工作已经用OData完成了,包括iPhone, PHP 和 Java的libraries。可被用作向这样的一项服务发起对话的OData SDKs正在不断地增多。如果我愿意的话我也可以使用PowerPivot把它载入Excel里去。
此外,这项服务可以完全被扩展至这个简单的GET例子以外。你可以用OData来做完整的CRUD,它不以任何方式绑向.NET。或许是TweetDeck for StackOverflow?
我建议我们鼓励把比30分钟更多的时间(我之前已经投入进去的)投入到StackOverflow,并为它们的数据提供一个合适的OData服务,而不是一个定制的API。我自愿提供帮助。如果不这样的话,我们可以用他们的转储数据(如果他们能加点紧,或许能每周一次)以及一个云实例来自己做。
有什么想法吗?