使用 Web API 2 创建 OData v3 终结点

作者:Mike Wasson

下载已完成项目

开放数据协议 (OData) 是 Web 的数据访问协议。 OData 提供了一种统一的方式来通过 CRUD 操作来构建数据、查询数据和操作数据集, (创建、读取、更新和删除) 。 OData 支持 AtomPub (XML) 和 JSON 格式。 OData 还定义了一种公开有关数据的元数据的方法。 客户端可以使用元数据来发现数据集的类型信息和关系。

ASP.NET Web API可以轻松地为数据集创建 OData 终结点。 可以精确控制终结点支持的 OData 操作。 可以托管多个 OData 终结点以及非 OData 终结点。 你可以完全控制数据模型、后端业务逻辑和数据层。

本教程中使用的软件版本

ASP.NET 和 Web 工具 2012.2 更新中添加了 Web API OData 支持。 但是,本教程使用Visual Studio 2013中添加的基架。

在本教程中,将创建客户端可以查询的简单 OData 终结点。 还将为终结点创建 C# 客户端。 完成本教程后,下一组教程介绍如何添加更多功能,包括实体关系、操作和$expand/$select。

创建 Visual Studio 项目

在本教程中,你将创建支持基本 CRUD 操作的 OData 终结点。 终结点将公开单个资源,即产品列表。 后面的教程将添加更多功能。

启动 Visual Studio,然后从“开始”页中选择“ 新建项目 ”。 或者,从“ 文件 ”菜单中选择“ 新建 ”,然后选择“ 项目”。

“模板 ”窗格中,选择“ 已安装的模板 ”,然后展开“Visual C#”节点。 在 “Visual C#”下,选择“ Web”。 选择“ASP.NET Web 应用程序”模板。

“新建项目”窗口的屏幕截图,其中显示了模板窗格的路径,并突出显示了选择“A S P dot NET Web 应用程序”选项的方向。

“新建 ASP.NET 项目 ”对话框中,选择“ ”模板。 在“为...”添加文件夹和核心引用下,检查 Web API。 单击 “确定”

“A S P 点 NET 项目”对话框的屏幕截图,其中显示了模板选项框并突出显示了“空”选项。

添加实体模型

模型是表示应用程序中的数据的对象。 在本教程中,我们需要一个表示产品的模型。 模型对应于 OData 实体类型。

在解决方案资源管理器中,右键单击“模型”文件夹。 在上下文菜单中,依次选择“添加”、“类”。

解决方案资源管理器对话框的屏幕截图,其中显示了每个选项的菜单列表,同时突出显示每个选项,并引导类选项。

“添加新 项”对话框中,将类命名为“Product”。

“添加新项目”窗口的屏幕截图,其中显示了默认排序和类选项,并在下面的空字段中显示单词“product dot c”。

注意

按照约定,模型类放置在 Models 文件夹中。 你不必在自己的项目中遵循此约定,但我们将在本教程中使用它。

在 Product.cs 文件中,添加以下类定义:

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}

ID 属性将是实体键。 客户端可以按 ID 查询产品。 此字段也是后端数据库中的主键。

立即生成项目。 在下一步中,我们将使用一些 Visual Studio 基架,该基架使用反射来查找产品类型。

添加 OData 控制器

控制器是处理 HTTP 请求的类。 为 OData 服务中的每个实体集定义单独的控制器。 在本教程中,我们将创建单个控制器。

在“解决方案资源管理器”中,右键单击“控制器”文件夹。 依次选择“添加”、“控制器”。

解决方案资源管理器窗口的屏幕截图,其中突出显示了控制器选项,然后显示用于添加 O Data 控制器的菜单。

“添加基架 ”对话框中,选择“使用实体框架执行操作的 Web API 2 OData 控制器”。

“添加基架”屏幕的屏幕截图,其中显示了控制器选项菜单,并突出显示了 Web A P I 2 O 数据控制器。

“添加控制器 ”对话框中,将控制器命名为“ProductsController”。 选中“使用异步控制器操作”复选框。 在 “模型 ”下拉列表中,选择“Product”类。

“添加控制器”对话框的屏幕截图,其中显示了控制器名称、模型类下拉列表和数据上下文类的字段。

单击“ 新建数据上下文...” 按钮。 保留数据上下文类型的默认名称,然后单击“ 添加”。

“新建数据上下文”窗口的屏幕截图,其中显示了“新数据上下文类型”的字段,并显示了数据上下文类型的默认名称。

在“添加控制器”对话框中单击“添加”以添加控制器。

“添加控制器”对话框的屏幕截图,其中显示了不同的字段要求,其中包含用于“使用异步控制器操作”的复选框。

注意:如果收到一条错误消息,指出“获取类型时出错...”,请确保在添加 Product 类后生成 Visual Studio 项目。 基架使用反射来查找 类。

Microsoft Visual Studio 的屏幕截图,其中显示了红色圆圈“X”,后跟“error”一词和错误的详细消息。

基架将两个代码文件添加到项目中:

  • Products.cs 定义实现 OData 终结点的 Web API 控制器。
  • ProductServiceContext.cs 提供使用 Entity Framework 查询基础数据库的方法。

项目窗口的屏幕截图,其中显示了产品服务菜单,并在控制器和模型下盘旋两个新添加的文件。

添加 EDM 和 Route

在 解决方案资源管理器 中,展开 App_Start 文件夹并打开名为 WebApiConfig.cs 的文件。 此类保存 Web API 的配置代码。 将此代码替换为以下代码:

using ProductService.Models;
using System.Web.Http;
using System.Web.Http.OData.Builder;

namespace ProductService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Product>("Products");
            config.Routes.MapODataRoute("odata", "odata", builder.GetEdmModel());
        }
    }
}

此代码执行两项操作:

  • 为 OData 终结点创建实体数据模型 (EDM) 。
  • 添加终结点的路由。

EDM 是数据的抽象模型。 EDM 用于创建元数据文档并定义服务的 URI。 ODataConventionModelBuilder 使用一组默认命名约定 EDM 创建 EDM。 此方法需要最少的代码。 如果想要对 EDM 进行更多控制,可以使用 ODataModelBuilder 类通过显式添加属性、键和导航属性来创建 EDM。

EntitySet 方法将实体集添加到 EDM:

modelBuilder.EntitySet<Product>("Products");

字符串“Products”定义实体集的名称。 控制器的名称必须与实体集的名称匹配。 在本教程中,实体集名为“Products”,控制器名为 ProductsController。 如果将实体集命名为“ProductSet”,则会将控制器 ProductSetController命名为 。 请注意,一个终结点可以有多个实体集。 为每个 实体集调用 EntitySet<T> ,然后定义相应的控制器。

MapODataRoute 方法为 OData 终结点添加路由。

config.Routes.MapODataRoute("ODataRoute", "odata", model);

第一个参数是路由的友好名称。 服务的客户端看不到此名称。 第二个参数是终结点的 URI 前缀。 给定此代码后,Products 实体集的 URI 为 http:// hostname/odata/Products。 应用程序可以有多个 OData 终结点。 对于每个终结点,调用 MapODataRoute 并提供唯一的路由名称和唯一的 URI 前缀。

为数据库设定种子 (可选)

在此步骤中,将使用实体框架为数据库设定一些测试数据的种子。 此步骤是可选的,但它允许你立即测试 OData 终结点。

“工具 ”菜单中选择“ NuGet 包管理器”,然后选择“ 包管理器控制台”。 在“Package Manager Console”窗口中,输入以下命令:

Enable-Migrations

这会添加一个名为 Migrations 的文件夹和一个名为 Configuration.cs 的代码文件。

解决方案资源管理器的“产品服务”菜单的屏幕截图,该菜单盘旋着新添加的名为 migrations 的文件夹,并显示其中的文件。

打开此文件并将以下代码添加到 Configuration.Seed 方法。

protected override void Seed(ProductService.Models.ProductServiceContext context)
{
    // New code 
    context.Products.AddOrUpdate(new Product[] {
        new Product() { ID = 1, Name = "Hat", Price = 15, Category = "Apparel" },
        new Product() { ID = 2, Name = "Socks", Price = 5, Category = "Apparel" },
        new Product() { ID = 3, Name = "Scarf", Price = 12, Category = "Apparel" },
        new Product() { ID = 4, Name = "Yo-yo", Price = 4.95M, Category = "Toys" },
        new Product() { ID = 5, Name = "Puzzle", Price = 8, Category = "Toys" },
    });
}

在“包管理器控制台”窗口中,输入以下命令:

Add-Migration Initial
Update-Database

这些命令生成创建数据库的代码,然后执行该代码。

浏览 OData 终结点

在本部分中,我们将使用 Fiddler Web 调试代理 将请求发送到终结点并检查响应消息。 这将帮助你了解 OData 终结点的功能。

在 Visual Studio 中,按 F5 开始调试。 默认情况下,Visual Studio 会将浏览器打开到 http://localhost:*port*,其中 port 是在项目设置中配置的端口号。

可以在项目设置中更改端口号。 在“解决方案资源管理器”中,右键单击项目并选择“属性”。 在属性窗口中,选择“ Web”。 在 “项目 URL”下输入端口号。

服务文档

服务文档包含 OData 终结点的实体集列表。 若要获取服务文档,请向服务的根 URI 发送 GET 请求。

使用 Fiddler,在 “编辑器 ”选项卡中输入以下 URI: http://localhost:port/odata/,其中 port 是端口号。

服务文档窗口的屏幕截图,其中显示了选择了“已分析”选项卡的不同选项卡,并在 composer 字段中显示 URL 数据。

单击“执行”按钮。 Fiddler 向应用程序发送 HTTP GET 请求。 应在“Web 会话”列表中看到响应。 如果一切正常,则状态代码将为 200。

Web 会话列表的屏幕截图,其中显示了结果编号为 200 的 HTTP 协议,以及 URL 地址和主机。

双击“Web 会话”列表中的响应,在“检查器”选项卡中查看响应消息的详细信息。

Web 会话列表的检查器选项卡的屏幕截图,其中显示了请求标头响应和 X M L 信息。

原始 HTTP 响应消息应如下所示:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/atomsvc+xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 17:51:01 GMT
Content-Length: 364

<?xml version="1.0" encoding="utf-8"?>
<service xml:base="http://localhost:60868/odata" 
    xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom">
  <workspace>
    <atom:title type="text">Default</atom:title>
    <collection href="Products">
        <atom:title type="text">Products</atom:title>
    </collection>
    </workspace>
</service></pre>

默认情况下,Web API 以 AtomPub 格式返回服务文档。 若要请求 JSON,请将以下标头添加到 HTTP 请求:

Accept: application/json

Web 会话窗口的屏幕截图,其中显示了“请求标头”部分中的“A P I”的响应,并盘旋在何处写入 j son 请求。

现在,HTTP 响应包含 JSON 有效负载:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 22:59:28 GMT
Content-Length: 136

{
  "odata.metadata":"http://localhost:60868/odata/$metadata","value":[
    {
      "name":"Products","url":"Products"
    }
  ]
}

服务元数据文档

服务元数据文档使用称为概念架构定义语言的 XML 语言 (CSDL) 描述服务的数据模型。 元数据文档显示服务中数据的结构,可用于生成客户端代码。

若要获取元数据文档,请向 http://localhost:port/odata/$metadata发送 GET 请求。 下面是本教程中显示的终结点的元数据。

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 23:05:52 GMT
Content-Length: 1086

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
  <edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" 
    xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
    <Schema Namespace="ProductService.Models" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityType Name="Product">
        <Key>
          <PropertyRef Name="ID" />
        </Key>
        <Property Name="ID" Type="Edm.Int32" Nullable="false" />
        <Property Name="Name" Type="Edm.String" />
        <Property Name="Price" Type="Edm.Decimal" Nullable="false" />
        <Property Name="Category" Type="Edm.String" />
      </EntityType>
    </Schema>
    <Schema Namespace="Default" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
      <EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
        <EntitySet Name="Products" EntityType="ProductService.Models.Product" />
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

实体集

若要获取 Products 实体集,请向 http://localhost:port/odata/Products发送 GET 请求。

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 23:01:31 GMT
Content-Length: 459

{
  "odata.metadata":"http://localhost:60868/odata/$metadata#Products","value":[
    {
      "ID":1,"Name":"Hat","Price":"15.00","Category":"Apparel"
    },{
      "ID":2,"Name":"Socks","Price":"5.00","Category":"Apparel"
    },{
      "ID":3,"Name":"Scarf","Price":"12.00","Category":"Apparel"
    },{
      "ID":4,"Name":"Yo-yo","Price":"4.95","Category":"Toys"
    },{
      "ID":5,"Name":"Puzzle","Price":"8.00","Category":"Toys"
    }
  ]
}

实体

若要获取单个产品,请向 http://localhost:port/odata/Products(1)发送 GET 请求,其中“1”是产品 ID。

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
DataServiceVersion: 3.0
Date: Mon, 23 Sep 2013 23:04:29 GMT
Content-Length: 140

{
  "odata.metadata":"http://localhost:60868/odata/$metadata#Products/@Element","ID":1,
      "Name":"Hat","Price":"15.00","Category":"Apparel"
}

OData 序列化格式

OData 支持多种序列化格式:

  • Atom Pub (XML)
  • OData v3) 中引入的 JSON“light” (
  • JSON“verbose” (OData v2)

默认情况下,Web API 使用 AtomPubJSON“light”格式。

若要获取 AtomPub 格式,请将 Accept 标头设置为“application/atom+xml”。 下面是响应正文示例:

<?xml version="1.0" encoding="utf-8"?>
<entry xml:base="http://localhost:60868/odata" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
  <id>http://localhost:60868/odata/Products(1)</id>
  <category term="ProductService.Models.Product" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <link rel="edit" href="http://localhost:60868/odata/Products(1)" />
  <link rel="self" href="http://localhost:60868/odata/Products(1)" />
  <title />
  <updated>2013-09-23T23:42:11Z</updated>
  <author>
    <name />
  </author>
  <content type="application/xml">
    <m:properties>
      <d:ID m:type="Edm.Int32">1</d:ID>
      <d:Name>Hat</d:Name>
      <d:Price m:type="Edm.Decimal">15.00</d:Price>
      <d:Category>Apparel</d:Category>
    </m:properties>
  </content>
</entry>

可以看到 Atom 格式的一个明显缺点:它比 JSON 浅色要详细得多。 但是,如果你有一个可理解 AtomPub 的客户端,则客户端可能更喜欢该格式而不是 JSON。

下面是同一实体的 JSON 精简版本:

{
  "odata.metadata":"http://localhost:60868/odata/$metadata#Products/@Element",
  "ID":1,
  "Name":"Hat",
  "Price":"15.00",
  "Category":"Apparel"
}

OData 协议的版本 3 中引入了 JSON 浅色格式。 为了向后兼容,客户端可以请求较旧的“详细”JSON 格式。 若要请求详细 JSON,请将 Accept 标头设置为 application/json;odata=verbose。 下面是详细版本:

{
  "d":{
    "__metadata":{
      "id":"http://localhost:18285/odata/Products(1)",
      "uri":"http://localhost:18285/odata/Products(1)",
      "type":"ProductService.Models.Product"
    },"ID":1,"Name":"Hat","Price":"15.00","Category":"Apparel"
  }
}

此格式在响应正文中传递更多元数据,这可能会在整个会话中增加相当大的开销。 此外,它还通过将对象包装在名为“d”的属性中来添加间接级别。

后续步骤