OData 的实际应用:通过开放数据协议构建富 Internet 应用程序


在 PDC09 期间,Microsoft WCF 数据服务团队(以前称为 ADO.NET 数据服务团队)首次推出 OData,即开放数据协议。这一消息是在会议第二天的主题演讲中宣布的,但实际上 OData 早就开始了。自从 Microsoft .NET Framework 3.5 SP1 中提供 ADO.NET 数据服务以来,熟悉 ADO.NET 数据服务的用户已经使用 OData 作为数据传输协议开发基于资源的应用程序。本文将介绍富 Internet 应用程序 (RIA) 的开发人员如何使用 OData 以及使用 OData 的优势。

  首先,我将回答自 11 月 OData 问世后,我经常被问到的头号问题。是什么问题呢?简单地说,OData 是一种基于资源的 Web 协议,用于查询和更新数据。OData 定义了使用 HTTP 动词(PUT、POST、UPDATE 和 DELETE)对资源的操作,并使用标准的 URI 语法识别这些资源。数据将使用 AtomPub 或 JSON 标准通过 HTTP 进行传输。对于 AtomPub,OData 协议在标准之上定义了一些约定,以支持查询和架构信息的交换。

  OData 生态系统

  在本文中,我将介绍几个使用或产生 OData 源的产品、框架和 Web 服务。该协议定义了可以操作的资源和方法,以及可以对这些资源执行的操作(GET、PUT、POST、MERGE 和 DELETE,分别对应着读取、创建、替换、合并和删除)。

  实际上,这表示任何可以使用 OData 协议的客户端都可以对任何生产者进行操作,没必要学习服务的编程模型即可针对该服务进行编程,只要选择编程的目标语言即可。

  例如,如果您是 Silverlight 开发人员,学习适用于该平台的 OData 库,则您可以针对任何 OData 源进行编程。除了适用于 Silverlight 的 OData 库,您还会发现适用于 Microsoft .NET Framework 客户端、AJAX、Java、PHP 和 Objective-C 的库,更多的库还在不断推出。此外,Microsoft PowerPivot for Excel 也支持将 OData 源作为将数据导入到其内存中分析引擎的选项。

  就像能使用 OData 协议的客户端可以对任何生产者进行操作一样,使用 OData 创建的服务或应用程序可以被任何启用了 OData 的客户端使用。在创建用于将关系数据公开为 OData 端点(或在 SharePoint 站点、Windows Azure 的表或其他您使用的服务中公开数据)的 Web 服务之后,您可以很轻松地在 .NET Framework 中构建富桌面客户端,或构建使用相同数据的富 AJAX 网站。

  OData 的长期目标是为每种主流技术、编程语言和平台都打造一个 OData 客户端库,以便每个客户端应用程序都可以使用丰富的 OData 源。OData 的生产者和使用者共同构成了 OData“生态系统”。

  WCF 数据服务的新功能

  作为 .NET Framework 组件的 WCF 数据服务是一个可提供交钥匙解决方案的框架,用于创建 OData Web 服务。它包括一个客户端库,您可以用来构建使用 OData 源的客户端。WCF 数据服务团队最近发布了 .NET Framework 3.5 SP1 的更新,引入了一系列将在 .NET Framework 4 中推出的新功能。这是数据服务框架的第二个版本。请访问 blogs.msdn.com/astoriateam/archive/2010/01/27/data-services-update-for-net-3-5-sp1-available-for-download.aspx,在这里可以找到相关介绍和下载链接。

  WCF 数据服务框架不仅仅是针对 RIA 应用程序的协议,而且还适用于高级服务开发人员。它有很多吸引人的功能,例如服务器分页限制、HTTP 缓存支持、无状态服务、流支持和可插入的提供程序模型。让我们来看看吸引大多数 RIA 开发人员的新功能。

  初始版本推出之后,用户最期望的功能之一就是能够请求集中的实体数量。新的“计数”功能从两方面满足了该需求。首先,它让您只请求计数,即一条查询可以返回的值的数量。其次,它添加了一个查询选项,告诉服务当查询结果是部分集时(例如启用了服务器分页),包括集中实体总数的计数。

  为了改善从 OData 服务绑定数据时的使用体验,一种新的类型 DataServiceCollection 已添加到 WCF 数据服务客户端库中。该类型实现对其包含的条目的变更跟踪(通过使用 INotifyPropertyChanged 和 INotifyCollectionChanged 接口)。如果将其绑定到控件(例如 Silverlight 中的 DataGrid),它将跟踪对象和集合本身的更改。这个新的集合大大简化了使用接口组件创建 OData 客户端的过程。

  另一项用户期待的功能是投影查询结果返回的实体的属性子集。为此已经添加了 LINQ 支持,这种支持通过 LINQ Select 语句实现。这有两方面的好处:可以减小 HTTP 查询响应的大小,还可以减小客户端方对象的内存占用量。当您针对别人的服务开发客户端应用程序,而且该服务中每个实体都有很多对客户端无意义的属性时,这就特别有用。我将在后文中演示如何使用有很多实体、每个实体又有无数属性的大型公共服务。示例中的投影非常有用,因为它只包括一个实体上的少数几个必需的属性。

  为了帮助您了解 OData 生态系统的价值,我们将创建一个 Web 应用程序,让访问者浏览一家虚拟的房地产公司 Contoso Ltd. 的网站,查看其管理的房产销售清单。

  关系数据

  Contoso.com 的 Home Finder 应用程序的主要数据源是一个 SQL Server 数据库,其中包含该公司管理的所有房产的信息,以及为这些房产发布的所有销售清单(在售的和已售出的)。

  借助 .NET Framework 3.5 SP1 中提供的 WCF 数据服务和 ADO.NET 实体框架,很容易将关系数据库作为 OData 源提供,所需的只是在关系数据上创建实体框架模型。OData 源是基于 HTTP 的,因此您需要一个网站或 Web 服务来托管该服务。

  若要在关系数据上创建 OData 源,第一步是在 Visual Studio 2010 中创建 ASP.NET Web 应用程序以托管 OData 服务。在 Visual Studio 中,选择“文件”|“新建”|“项目”|“ASP.NET Web 应用程序”。这将创建用来托管 OData 源的 Web 服务的框架。

  创建并配置 Web 服务后,我们接着创建 OData 源将提供的实体框架数据模型。Visual Studio 提供了“添加新项”向导,帮助您从现有的数据库自动生成模型,从而让操作变得很容易。图 1 显示了使用“添加新项”向导根据 SQL Server 数据(包含 Contoso 管理的房产和销售清单)创建的简单数据模型。


图 1 关系数据的实体框架数据模型

  现在让我们来创建一个 WCF 数据服务,将此数据模型作为 OData 源提供。Visual Studio 在“添加新项”向导中提供了 WCF 数据服务选项,可帮助您轻松完成此操作。选择此选项后,Visual Studio 提供了一个代码文件(在此示例中,此文件称为 Listings.svc.cs),用于配置数据服务。

  图 2 中的代码演示了如何定义 WCF 数据服务。Listings 类是提供数据服务的服务类,实现了泛型 DataService<T>。图 2 中用于定义 DataService<T> 的类型是 ListingsEntities 类型,就是图 1 中创建的实体框架上下文。由于这个类将接受实体框架上下文,因此这种方法可以快速简便地启动并运行用来提供关系数据的 WCF 数据服务。但是 DataService 类并未局限在只是处理实体框架上下文,该类将接受实现 IQueryable 接口的任何 CLR 对象集合。在 .NET Framework 4 中,已添加了针对 WCF 数据服务的新的自定义提供程序模型,以便使用几乎所有数据源来创建服务。

  图 2 定义 WCF 数据服务

// The ListingsEntities is the Entity Framework Context that the Service exposes 
public class Listings : DataService< ListingsEntities > 
{ 
 public static void InitializeService(DataServiceConfiguration config) 
 { 
  // These lines set the access rights to "Read Only" for both entity sets 
  config.SetEntitySetAccessRule("Listings", EntitySetRights.AllRead); 
  config.SetEntitySetAccessRule("Properties", EntitySetRights.AllRead); 
 
  // There are currently no service operations in the service 
  config.SetServiceOperationAccessRule("MyServiceOperation", 
   ServiceOperationRights.All); 
 
  config.DataServiceBehavior.MaxProtocolVersion = 
   DataServiceProtocolVersion.V2; 
 } 
}

  让我们再仔细看看图 2 中的 InitalizeService 方法还进行了什么操作。此方法为服务将提供的两个实体集调用 SetEntitySetAccessRule,并且设置对 AllRead 的访问权限。这告诉服务让两个实体集完全可读,但不允许进行插入、更新或删除。这是控制对服务的访问的极佳方法。WCF 数据服务还支持名为“查询侦听器”的方法,允许服务作者在每个实体集的基础上为服务配置更精细的访问控制。将 Listings.svc 文件设置为项目起始页,并运行项目。这将打开一个浏览器窗口并显示服务文档,如图 3 所示。


图 3 SharePoint 站点的服务文档

  OData URI 约定

  服务文档列出了服务提供的实体集。请记住,您可以使用强大的 URI 语法(是 OData 协议的可选部分)来访问此服务中的资源。让我们快速了解一下此服务的 URI 语法。若要访问每个实体集的源,您需要将实体集的名称附加到服务的基本 URI 中,例如 http://myhost/Listings.svc/Properties 将指向 Properties 实体集中的一组实体。

  还可以使用单个实体的键值来指向该实体,例如 URI http://myhost/ Listings.svc/Properties(0) 将指向 ID = 0 的房产。您可以将关系名称附加到 URI 末尾,以指向此实体与其他实体或实体集的关系,因此 http://myhost/ Listings.svc/Properties(0)/Listings 将指向与 ID = 0 的房产实体相关的一组销售清单。使用此语法,可以浏览关系的很多层级。

  URI 还定义了很多可以附加到 URI 的查询选项,以便在一定程度上修改基本查询,其中每个查询选项都定义为名称/值对。例如,附加查询选项 $top=10 后,就将查询限制为仅包含结果中的前 10 个实体。图 4 列出了 URI 语法中所有可用的查询选项。

  图 4 OData 查询选项

$top=n 将查询限制为前 n 个实体。
$skip=n 跳过集中的前 n 个实体。
$inlinecount=allpages 在结果中包含集中所有实体的计数。
$filter=<表达式> 可以用表达式限制查询返回的结果(例如:$filter=Status eq 'Available' 将结果限制为具有 Status 属性且此属性值为 Available 的实体)。
$orderby=<表达式> 按照实体的一组属性对结果进行排序
$select=<表达式> 指定要返回的实体的属性子集。
$format 指定要返回的源的格式(ATOM 或 JSON)。此选项在 WCF 数据服务中不受支持。

  从 SharePoint 提供数据

  在前述部分中,我为您展示了如何提供关系数据库中存储的数据,以及房地产网站的房产和销售清单信息。让我们假设我还有负责出售房产的代理人信息,但这些数据存储在 SharePoint 站点中。Microsoft SharePoint 2010 提供了相应功能,可以将所有列表和列表中的文档作为 OData 源提供。这非常适合该房地产网站,因为这意味着该公司员工输入的代理人信息可作为 OData 源使用,可以包含在我构建的销售清单应用程序中。使用 SharePoint 接口负责输入和更新此数据的用户不必更改其工作流程来适应我的应用程序。输入到公司 SharePoint 站点的数据在即将创建的 Listings 应用程序中实时可用。

  图 5 显示了简单的 SharePoint 门户,房地产代理人可用来记录和更新其联系信息。


图 5 代理人信息的 SharePoint 站点

  在 SharePoint 系统中安装适用于 .NET Framework 3.5 SP1 的 ADO.NET 数据服务更新后,对于每个将清单数据作为 OData 源提供的站点来说,将有一个新的 HTTP 端点可以使用。由于 OData 源要使用 HTTP 进行访问,因此使用 Internet Explorer 即可查看。图 6 显示了 SharePoint 中代理人清单的源。


图 6 SharePoint 代理人服务的代理人源

  使用来自 OGDI 的参考数据

  默认情况下,OData 源将为源返回 ATOM 表示形式,当从 Web 浏览器访问时,结果将是 ATOM 源。如果请求的接受标头改为“application/json”,结果将是 JSON 源形式的相同数据。

  图 6 中的源以代表一组实体的 <feed> 元素开始。每个源中包含了一组 <entry> 元素,每个元素都代表源中的一个实体(前三个实体元素已折叠,以便在一个屏幕中显示整个源)。

  在此示例中,实体有一个已定义的并发标记,因此源中的每个实体都有一个 etag 属性。当对所请求的实体进行变更时,数据服务使用 etag 标记来强制执行并发检查。每个实体都使用 <entry> 标记进行了格式化,并且由一组链接组成。这些链接包含编辑实体时用到的链接以及实体的关系。每个关系链接都指向另一个实体或一组实体(分别称为引用属性和导航属性)。每个 <entry> 元素还包括一个 <m:properties> 元素,其中包含实体的基元类型和复杂类型属性;属性值由实体的属性名称和该属性的值组成。

  Open Government Data Initiative (OGDI) 是构建在 Microsoft Windows Azure 平台上的一项服务,方便政府部门发布各种各样的公开数据。OGDI 项目提供了一个入门工具包,政府部门可以使用它来提供其数据。例如埃得蒙顿市已使用该入门工具包来提供其政府数据,ogdisdk.cloudapp.net 的一项服务提供了包含华盛顿特区的各种数据的数据集。另一个示例是 Microsoft Codename“Dallas”项目,旨在帮助任何拥有数据集的用户简便地将数据作为 Web 服务提供。此项目也构建在 Windows Azure 平台上,使用 OData 来提供数据。这些都是要提供大量参考数据以便进一步由 Web 应用程序使用的高端服务示例。正如我将演示的,当这些服务使用 OData 提供其数据时,通过各种应用程序来使用这些数据是非常简单的。

  如前所述,OGDI 网站提供有关华盛顿特区的可用公开数据。Contoso 的房地产应用程序用于浏览该区域的销售清单,当用户查看特定的区域时,如果可以使用针对该区域的此类参考数据,将非常有帮助。当我为示例应用程序创建客户端时,我将演示如何包含来自 OGDI 网站的 OData 源,作为应用程序的数据源之一。

  其他 OData 生产者

  截至目前,我已经展示了从 SQL Server、SharePoint 和网络上一般的 OData 服务获得数据的示例,但还有更多选择存在。基于云计算的 Windows Azure 平台具有一个表服务,可以提供 Windows Azure 表中存储的数据,而其 API 是使用 OData 构建的。如前所述,Microsoft Dallas 项目是一个数据市场,可查找和查询 Dallas 服务提供的数据,而该服务使用 OData 协议提供其数据。OData 的生产者并不仅限于 Microsoft 产品,IBM 最近宣布其 WebSphere eXtreme Scale 7.0 产品现在也支持 OData 协议。

  Silverlight 客户端

  Contoso 的房地产查找应用程序现在使用 ASP.NET Web 服务来提供 SQL Server 中的关系数据(有关该公司管理的房地产销售清单和房产的数据);使用 SharePoint 站点来管理有关公司代理人的数据;使用政府 Web 服务来提供公司推广的房产所在地区的数据。我要将所有这些数据源合并到一个 Silverlight 应用程序中,用有意义的方式来处理这些数据。

  在 Silverlight 3 中,Silverlight SDK 中包含了一个 WCF 数据服务客户端库,使得 Silverlight 应用程序可以更容易地与启用了 OData 的服务通信。为此,在 Visual Studio 的 Silverlight 项目中,右键单击该项目,选择“添加服务引用”。这将引导您完成创建服务引用的过程。服务引用的主要输入是要从 Silverlight 应用程序引用的服务的 URI。图 7 显示了向 OGDI 示例服务添加服务引用的示例。


图 7 向 OGDI 示例服务添加服务引用

  服务引用向导将创建一个客户端上下文类,用于与数据服务进行交互。该客户端上下文从客户端编程模型提取出与 HTTP 和 URI 相关的细节,使客户端开发人员只需考虑 C# 类和 XAML。该客户端上下文还包括一个 LINQ 提供程序实现,因此也支持对代理执行 LINQ 查询。“添加服务引用”向导还将生成一组客户端代理类,用于镜像由被引用的服务提供的类型。创建 OGDI 服务引用后,我还将创建对已创建的 SharePoint 和 Listings 服务的服务引用。此代码显示了如何创建用于与这三个 OData 服务进行交互的上下文:

// OGDI service context 
OGDIService.dcDataService OGDIService = 
 new dcDataService(new Uri(OGDIServiceURI)); 
 
// SharePoint service context 
AgentsReference.AgentsDataContext agentsService = 
 new AgentsReference.AgentsDataContext(new Uri(sharepointServiceURI)); 
 
// Listings Service context 
ListingReference.ListingsEntities listingsService = 
 new ListingReference.ListingsEntities(new Uri(listingsServiceURI));

  图 8 显示了 Silverlight 房地产 Home Finder 应用程序的梗概。此应用程序将托管在 SharePoint 中,以方便习惯了使用 SharePoint 环境的现有用户。


图 8 Contoso Home Finder

  图 9 包含了用于查询销售清单服务的代码,以及将结果绑定到 Home Finder Silverlight 应用程序顶部表格中的代码。

  图 9 创建客户端代理上下文

private void getListings() 
{ 
 DataServiceCollection<Listing> listings = new 
  DataServiceCollection<Listing>(); 
 
 listingsGrid.ItemsSource = listings; 
  
 var query = from listing in 
       listingsService.Listings.Expand("Property") 
       select listing; 
 listings.LoadAsync(query); 
}

  图 9 中的代码创建了被跟踪的集合 DataServiceCollection,并将该集合绑定到主销售清单表格的 ItemsSource 属性。由于此集合实现了变更跟踪,因此对表格中任何条目的更改都将自动反映在销售清单集合的实体上。通过调用销售清单服务上下文的 BeginSaveChanges 方法,表格中的更改可保存到服务中。

  在 Silverlight 中,所有的网络调用都是异步的,因此使用 WCF 数据服务客户端库对服务执行的任何操作都需要对操作进行初始调用,然后编写独立的回调方法,并传递到该方法以处理异步调用的结果。为了改进这种异步调用,DataServiceCollection 类添加了一个方法 LoadAsync,由其执行所有处理异步回调函数的工作并将结果加载到集合中。

  在 图 9 的代码中,在调用 LoadAsync 之前将集合绑定到表格,因此在异步调用完成之前,值不会加载到集合中。当结果从服务返回时,集合将会引发集合更改事件,表格将捕获这些事件,并在异步调用完成时显示结果。

  从数据表格选择销售清单后,需要查询 SharePoint 站点以获取管理该清单的代理人的信息。在此应用程序体系结构中,需要进行第二次查询,因为销售清单类型和代理人类型的数据源是相互独立的,二者之间没有明显的关系(如果从模型的角度考虑,此示例涉及两个完全独立的模型,模型之间的关系是由客户端创建并施加的虚拟关系)。

  图 10 显示了在给定代理人姓名的情况下,如何查询 SharePoint 服务以获取代理人实体。从 OGDI 数据中查询 Home Finder 页面底部图表中的邻居信息时,使用了相似的代码序列。到目前为止的代码仅演示了 Silverlight 客户端的查询功能,但此客户端并不仅限于查询,而是提供了丰富的功能,可以将更改从客户端写回服务。

  图 10 执行异步查询

private void GetAgentData(string agentName) 
{ 
  var query = agentsService.Agents.Where(a => a.FullName == agentName) as 
    DataServiceQuery; 
 
  query.BeginExecute( 
    AgentQueryCallBack, query); 
} 
 
private void AgentQueryCallBack(IAsyncResult result) 
{ 
  Dispatcher.BeginInvoke(() => 
  { 
    var queryResult = result.AsyncState as DataServiceQuery<AgentsItem>; 
    if (queryResult != null) 
    { 
      var agents = queryResult.EndExecute(result); 
      this.grdAgent.DataContext = agents.First(); 
    } 
  }); 
}

  PowerPivot 中的 OData

  PowerPivot 是新的内存中商业智能工具,是 Microsoft Excel 2010 的加载项。有关详细信息,请访问 powerpivot.com。该工具支持从数据源导入大型数据集,并执行复杂的数据分析和报告。PowerPivot 可从多种不同的数据源导入数据,包括直接从 OData 源导入。PowerPivot 的“从数据源”选项(如图 11 所示)可以使用 OData 服务端点作为要导入的源的位置。


图 11 PowerPivot 从 OData 源导入

  图 12 显示了根据 OGDI 的华盛顿特区数据源中的犯罪率统计信息摘要绘制的图表。


图 12 来自 OData 源的 PowerPivot 图表

  图 12 中的图表与前述示例中房地产应用程序使用的数据集相同,显示了各个区所有数据的摘要。我建议您下载 PowerPivot for Excel 2010,并从位于 ogdi.cloudapp.net/v1/dc 的 OGDI 站点导入数据,体验一下如何快捷地对这些数据进行丰富的数据分析。

  Open Data Protocol Visualizer

  对于创建应用程序的外部开发人员来说,尽管其应用程序使用由 OGDI 数据服务提供的数据,但该服务从本质上讲是一个“黑箱”。幸运的是 OGDI 服务使用 OData 协议提供其数据,因此不需要了解该服务的内部细节,就能与此服务进行交互。该服务的编程模型是 OData 协议。服务端点描述了数据形式,正如我在上文所述,这就是您与该服务交互所需的全部内容。但是,查看服务中数据的形式并更好地了解服务各部分之间的关系,通常会很有帮助。Open Data Protocol Visualizer 由此应运而生。在 Visual Studio 2010 中,从“工具”|“扩展管理器”菜单项可以调用此程序。图 13 显示了该可视化工具的两个视图,视图中显示的是 OGDI 服务的结构。

  图 13 上部的视图显示了整个服务,下部的视图进行了放大,只显示了四个框。该可视化工具将实体集表现为框,实体之间的关系用连接框的线条表示。从图 13 上部的视图中可以清楚地看到,OGDI 服务完全是扁平的,不包含任何关系,因为各个框之间没有连接的线条。这只是 OGDI 服务的特点,大多数 OData 服务都不是这样的。下部的视图特别显示了服务中的四个实体集。只需查看该视图,您就能确定服务提供了有关消防站、小学入学率、透析诊所和政府机关位置的数据,以及这些类型的属性和键。


图 13 OGDI 示例服务的 Open Data Visualizer 视图


« 
» 
快速导航

Copyright © 2016 phpStudy | 豫ICP备2021030365号-3