创建 OData v4 终结点使用 ASP.NET Web APICreate an OData v4 Endpoint Using ASP.NET Web API

开放数据协议 (OData) 是一种用于 web 的数据访问协议。The Open Data Protocol (OData) is a data access protocol for the web. OData 提供统一的方式来查询和操作数据集通过 CRUD 操作 (创建、 读取、 更新和删除)。OData provides a uniform way to query and manipulate data sets through CRUD operations (create, read, update, and delete).

ASP.NET Web API 支持 v3 和 v4 协议。ASP.NET Web API supports both v3 and v4 of the protocol. 你甚至可以让运行的并行的 v4 终结点与 v3 终结点。You can even have a v4 endpoint that runs side-by-side with a v3 endpoint.

本教程演示如何创建支持 CRUD 操作的 OData v4 终结点。This tutorial shows how to create an OData v4 endpoint that supports CRUD operations.

在本教程中使用的软件版本Software versions used in the tutorial

  • Web API 5.2Web API 5.2
  • OData v4OData v4
  • Visual Studio 2017 (下载 Visual Studio 2017此处)Visual Studio 2017 (download Visual Studio 2017 here)
  • Entity Framework 6Entity Framework 6
  • .NET 4.7.2.NET 4.7.2

教程版本Tutorial versions

OData 版本 3,请参阅创建 OData v3 终结点For the OData Version 3, see Creating an OData v3 Endpoint.

创建 Visual Studio 项目Create the Visual Studio Project

在 Visual Studio 中,从文件菜单中,选择新建 > 项目In Visual Studio, from the File menu, select New > Project.

展开已安装 > Visual C# > Web,然后选择ASP.NET Web 应用程序 (.NET Framework) 模板。Expand Installed > Visual C# > Web, and select the ASP.NET Web Application (.NET Framework) template. 将项目命名"ProductService"。Name the project "ProductService".

选择 确定Select OK.

选择模板。Select the Empty template. 添加文件夹和核心引用:,选择Web APIUnder Add folders and core references for:, select Web API. 选择 确定Select OK.

OData 包安装Install the OData packages

从“工具”菜单中,选择“NuGet 程序包管理器”>“包管理器控制台”。From the Tools menu, select NuGet Package Manager > Package Manager Console. 在包管理器控制台窗口中,键入:In the Package Manager Console window, type:

Install-Package Microsoft.AspNet.Odata

此命令将安装最新的 OData NuGet 包。This command installs the latest OData NuGet packages.

添加模型类Add a model class

一个模型是一个对象,表示在应用程序中的数据实体。A model is an object that represents a data entity in your application.

在解决方案资源管理器,右键单击模型文件夹。In Solution Explorer, right-click the Models folder. 从上下文菜单中,选择 > From the context menu, select Add > Class.

Note

按照约定,模型类都位于 Models 文件夹中,但无需遵循此约定在您自己的项目中。By convention, model classes are placed in the Models folder, but you don't have to follow this convention in your own projects.

将此类命名为 ProductName the class Product. 在 Product.cs 文件中,将替换以下为样板代码:In the Product.cs file, replace the boilerplate code with the following:

namespace ProductService.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }
}

Id属性是实体键。The Id property is the entity key. 客户端可以通过键查询实体。Clients can query entities by key. 例如,若要获取 id 为 5 的产品,URI 是/Products(5)For example, to get the product with ID of 5, the URI is /Products(5). Id属性也是后端数据库中的主键。The Id property will also be the primary key in the back-end database.

启用实体框架Enable Entity Framework

对于本教程,我们将使用 Entity Framework (EF) Code First 创建后端数据库。For this tutorial, we'll use Entity Framework (EF) Code First to create the back-end database.

Note

Web API OData 不需要 EF。Web API OData does not require EF. 使用可以转换为模型的数据库实体的任何数据访问层。Use any data-access layer that can translate database entities into models.

首先,安装 EF 的 NuGet 包。First, install the NuGet package for EF. 从“工具”菜单中,选择“NuGet 程序包管理器”>“包管理器控制台”。From the Tools menu, select NuGet Package Manager > Package Manager Console. 在包管理器控制台窗口中,键入:In the Package Manager Console window, type:

Install-Package EntityFramework

打开 Web.config 文件中,并添加以下节内的配置元素后面configSections元素。Open the Web.config file, and add the following section inside the configuration element, after the configSections element.

<configuration>
  <configSections>
    <!-- ... -->
  </configSections>

  <!-- Add this: -->
  <connectionStrings>
    <add name="ProductsContext" connectionString="Data Source=(localdb)\mssqllocaldb; 
        Initial Catalog=ProductsContext; Integrated Security=True; MultipleActiveResultSets=True; 
        AttachDbFilename=|DataDirectory|ProductsContext.mdf"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

此设置将添加 LocalDB 数据库的连接字符串。This setting adds a connection string for a LocalDB database. 在本地运行应用时,将使用此数据库。This database will be used when you run the app locally.

接下来,添加一个名为类ProductsContext到 Models 文件夹:Next, add a class named ProductsContext to the Models folder:

using System.Data.Entity;
namespace ProductService.Models
{
    public class ProductsContext : DbContext
    {
        public ProductsContext() 
                : base("name=ProductsContext")
        {
        }
        public DbSet<Product> Products { get; set; }
    }
}

在构造函数"name=ProductsContext"提供连接字符串的名称。In the constructor, "name=ProductsContext" gives the name of the connection string.

配置 OData 终结点Configure the OData endpoint

打开文件应用_Start/WebApiConfig.cs。Open the file App_Start/WebApiConfig.cs. 添加以下使用语句:Add the following using statements:

using ProductService.Models;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;

然后将以下代码添加到注册方法:Then add the following code to the Register method:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // New code:
        ODataModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Product>("Products");
        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: builder.GetEdmModel());
    }
}

此代码执行两项操作:This code does two things:

  • 创建实体数据模型 (EDM)。Creates an Entity Data Model (EDM).
  • 添加一个路由。Adds a route.

EDM 是抽象的数据模型。An EDM is an abstract model of the data. EDM 用于创建服务元数据文档。The EDM is used to create the service metadata document. ODataConventionModelBuilder类使用默认命名约定创建 EDM。The ODataConventionModelBuilder class creates an EDM by using default naming conventions. 此方法要求最少的代码。This approach requires the least code. 如果你想更好地控制 EDM,则可以使用ODataModelBuilder类,以通过添加属性、 键和导航属性显式创建 EDM。If you want more control over the EDM, you can use the ODataModelBuilder class to create the EDM by adding properties, keys, and navigation properties explicitly.

一个路由告知如何将 HTTP 请求路由到终结点的 Web API。A route tells Web API how to route HTTP requests to the endpoint. 若要创建 OData v4 路由,请调用MapODataServiceRoute扩展方法。To create an OData v4 route, call the MapODataServiceRoute extension method.

如果你的应用程序具有多个 OData 终结点,每个创建一个单独的路由。If your application has multiple OData endpoints, create a separate route for each. 为每个路由指定唯一的路由名称和前缀。Give each route a unique route name and prefix.

添加 OData 控制器Add the OData controller

一个控制器是处理 HTTP 请求的类。A controller is a class that handles HTTP requests. 创建 OData 服务中设置每个实体的单独控制器。You create a separate controller for each entity set in your OData service. 在本教程中,您将创建一个控制器为Product实体。In this tutorial, you will create one controller, for the Product entity.

在解决方案资源管理器,右键单击 Controllers 文件夹并选择 > In Solution Explorer, right-click the Controllers folder and select Add > Class. 将此类命名为 ProductsControllerName the class ProductsController.

Note

本教程,了解 OData v3 使用新版添加控制器基架。The version of this tutorial for OData v3 uses the Add Controller scaffolding. 目前,OData v4 没有基架。Currently, there is no scaffolding for OData v4.

ProductsController.cs 中的样板代码替换为以下。Replace the boilerplate code in ProductsController.cs with the following.

using ProductService.Models;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.OData;
namespace ProductService.Controllers
{
    public class ProductsController : ODataController
    {
        ProductsContext db = new ProductsContext();
        private bool ProductExists(int key)
        {
            return db.Products.Any(p => p.Id == key);
        } 
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

控制器使用ProductsContext类,以使用 EF 访问数据库。The controller uses the ProductsContext class to access the database using EF. 请注意,在控制器重写Dispose方法来释放ProductsContextNotice that the controller overrides the Dispose method to dispose of the ProductsContext.

这是在控制器的起始点。This is the starting point for the controller. 接下来,我们将添加所有 CRUD 操作的方法。Next, we'll add methods for all of the CRUD operations.

查询实体集Query the entity set

添加以下方法向ProductsControllerAdd the following methods to ProductsController.

[EnableQuery]
public IQueryable<Product> Get()
{
    return db.Products;
}
[EnableQuery]
public SingleResult<Product> Get([FromODataUri] int key)
{
    IQueryable<Product> result = db.Products.Where(p => p.Id == key);
    return SingleResult.Create(result);
}

无参数版本的Get方法将返回整个产品集合。The parameterless version of the Get method returns the entire Products collection. Get方法替换密钥根据键查找产品参数 (在这种情况下,Id属性)。The Get method with a key parameter looks up a product by its key (in this case, the Id property).

[EnableQuery] 属性使客户端能够通过使用查询选项,例如 $filter、 $sort 和 $page 修改查询。The [EnableQuery] attribute enables clients to modify the query, by using query options such as $filter, $sort, and $page. 有关详细信息,请参阅支持 OData 查询选项For more information, see Supporting OData Query Options.

将实体添加到实体集Add an entity to the entity set

若要允许客户端向数据库添加一个新的产品,添加以下方法ProductsControllerTo enable clients to add a new product to the database, add the following method to ProductsController.

public async Task<IHttpActionResult> Post(Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    db.Products.Add(product);
    await db.SaveChangesAsync();
    return Created(product);
}

更新实体Update an entity

OData 支持用于更新实体,PATCH 和 PUT 的两个不同的语义。OData supports two different semantics for updating an entity, PATCH and PUT.

  • 修补程序执行部分更新。PATCH performs a partial update. 客户端指定只是要更新的属性。The client specifies just the properties to update.
  • PUT 会替换整个实体。PUT replaces the entire entity.

PUT 的缺点是客户端必须在实体中,其中包括未更改的值发送的所有属性的值。The disadvantage of PUT is that the client must send values for all of the properties in the entity, including values that are not changing. OData 规范状态修补程序是首选。The OData spec states that PATCH is preferred.

在任何情况下,下面是修补程序和 PUT 方法的代码:In any case, here is the code for both PATCH and PUT methods:

public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    var entity = await db.Products.FindAsync(key);
    if (entity == null)
    {
        return NotFound();
    }
    product.Patch(entity);
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(entity);
}
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    if (key != update.Id)
    {
        return BadRequest();
    }
    db.Entry(update).State = EntityState.Modified;
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(update);
}

控制器使用修补程序,对于增量<T> 类型以跟踪所做的更改。In the case of PATCH, the controller uses the Delta<T> type to track the changes.

删除实体Delete an entity

若要使客户端能够从数据库中删除某个产品,添加以下方法ProductsControllerTo enable clients to delete a product from the database, add the following method to ProductsController.

public async Task<IHttpActionResult> Delete([FromODataUri] int key)
{
    var product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
    db.Products.Remove(product);
    await db.SaveChangesAsync();
    return StatusCode(HttpStatusCode.NoContent);
}