使用 ASP.NET Web API 2.2 的 OData v4 中的操作和函数

作者:Mike Wasson

在 OData 中,操作和函数是添加服务器端行为的一种方法,这些行为不容易定义为实体上的 CRUD 操作。 本教程演示如何使用 Web API 2.2 向 OData v4 终结点添加操作和函数。 本教程基于使用 ASP.NET Web API 2 创建 OData v4 终结点教程

本教程中使用的软件版本

  • Web API 2.2
  • OData v4
  • Visual Studio 2013 (在此处下载 Visual Studio 2017)
  • .NET 4.5

教程版本

对于 OData 版本 3,请参阅 ASP.NET Web API 2 中的 OData 操作

操作函数的区别在于操作可以产生副作用,而函数没有副作用。 操作和函数都可以返回数据。 操作的一些用途包括:

  • 复杂事务。
  • 同时操作多个实体。
  • 仅允许对实体的某些属性进行更新。
  • 发送不是实体的数据。

函数可用于返回与实体或集合不直接对应的信息。

操作 (或函数) 可以面向单个实体或集合。 在 OData 术语中,这是 绑定。 还可以具有“未绑定”操作/函数,这些操作/函数在服务上称为静态操作。

示例:添加操作

让我们定义一个操作来对产品进行评分。

首先,添加一个 ProductRating 模型来表示评级。

namespace ProductService.Models
{
    public class ProductRating
    {
        public int ID { get; set; }
        public int Rating { get; set; }
        public int ProductID { get; set; }
        public virtual Product Product { get; set; }  
    }
}

此外,将 DbSet 添加到 ProductsContext 类,以便 EF 将在数据库中创建一个 Ratings 表。

public class ProductsContext : DbContext
{
    public ProductsContext() 
            : base("name=ProductsContext")
    {
    }

    public DbSet<Product> Products { get; set; }
    public DbSet<Supplier> Suppliers { get; set; }
    // New code:
    public DbSet<ProductRating> Ratings { get; set; }
}

将操作添加到 EDM

在 WebApiConfig.cs 中,添加以下代码:

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");

// New code:
builder.Namespace = "ProductService";
builder.EntityType<Product>()
    .Action("Rate")
    .Parameter<int>("Rating");

EntityTypeConfiguration.Action 方法将操作添加到实体数据模型 (EDM) 。 Parameter 方法指定操作的类型化参数。

此代码还设置 EDM 的命名空间。 命名空间很重要,因为操作的 URI 包含完全限定的操作名称:

http://localhost/Products(1)/ProductService.Rate

注意

在典型的 IIS 配置中,此 URL 中的点将导致 IIS 返回错误 404。 可以通过将以下部分添加到 Web.Config 文件来解决此问题:

<system.webServer>
    <handlers>
      <clear/>
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="/*" 
          verb="*" type="System.Web.Handlers.TransferRequestHandler" 
          preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
</system.webServer>

为操作添加控制器方法

若要启用“Rate”操作,请将以下方法添加到 ProductsController

[HttpPost]
public async Task<IHttpActionResult> Rate([FromODataUri] int key, ODataActionParameters parameters)
{
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }

    int rating = (int)parameters["Rating"];
    db.Ratings.Add(new ProductRating
    {
        ProductID = key,
        Rating = rating
    });

    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateException e)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return StatusCode(HttpStatusCode.NoContent);
}

请注意,方法名称与操作名称匹配。 [HttpPost] 属性指定方法是 HTTP POST 方法。

若要调用 操作,客户端将发送 HTTP POST 请求,如下所示:

POST http://localhost/Products(1)/ProductService.Rate HTTP/1.1
Content-Type: application/json
Content-Length: 12

{"Rating":5}

“Rate”操作绑定到 Product 实例,因此操作的 URI 是追加到实体 URI 的完全限定操作名称。 (回想一下,我们将 EDM 命名空间设置为“ProductService”,因此完全限定的操作名称为“ProductService.Rate”.)

请求正文包含操作参数作为 JSON 有效负载。 Web API 自动将 JSON 有效负载转换为 ODataActionParameters 对象,该对象只是参数值的字典。 使用此字典访问控制器方法中的参数。

如果客户端以错误格式发送操作参数, ModelState.IsValid 的值为 false。 检查控制器方法中的此标志,如果 IsValid 为 false,则返回错误。

if (!ModelState.IsValid)
{
    return BadRequest();
}

示例:添加函数

现在,让我们添加一个返回最昂贵产品的 OData 函数。 与之前一样,第一步是将 函数添加到 EDM。 在 WebApiConfig.cs 中,添加以下代码。

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
builder.EntitySet<Supplier>("Suppliers");

// New code:
builder.Namespace = "ProductService";
builder.EntityType<Product>().Collection
    .Function("MostExpensive")
    .Returns<double>();

在这种情况下, 函数绑定到 Products 集合,而不是单个 Product 实例。 客户端通过发送 GET 请求来调用函数:

GET http://localhost:38479/Products/ProductService.MostExpensive

下面是此函数的控制器方法:

public class ProductsController : ODataController
{
    [HttpGet]
    public IHttpActionResult MostExpensive()
    {
        var product = db.Products.Max(x => x.Price);
        return Ok(product);
    }

    // Other controller methods not shown.
}

请注意,方法名称与函数名称匹配。 [HttpGet] 属性指定方法是 HTTP GET 方法。

下面是 HTTP 响应:

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
OData-Version: 4.0
Date: Sat, 28 Jun 2014 00:44:07 GMT
Content-Length: 85

{
  "@odata.context":"http://localhost:38479/$metadata#Edm.Decimal","value":50.00
}

示例:添加未绑定函数

前面的示例是绑定到集合的函数。 在下一个示例中,我们将创建 一个未绑定 的函数。 未绑定函数作为服务上的静态操作调用。 此示例中的 函数将返回给定邮政编码的销售税。

在 WebApiConfig 文件中,将 函数添加到 EDM:

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");

// New code:
builder.Function("GetSalesTaxRate")
    .Returns<double>()
    .Parameter<int>("PostalCode");

请注意,我们直接在 ODataModelBuilder 上调用 Function,而不是实体类型或集合。 这会告知模型生成器函数未绑定。

下面是实现 函数的控制器方法:

[HttpGet]
[ODataRoute("GetSalesTaxRate(PostalCode={postalCode})")]
public IHttpActionResult GetSalesTaxRate([FromODataUri] int postalCode)
{
    double rate = 5.6;  // Use a fake number for the sample.
    return Ok(rate);
}

将此方法放入哪个 Web API 控制器并不重要。 可以将它放在 中 ProductsController,也可以定义单独的控制器。 [ODataRoute] 属性定义函数的 URI 模板。

下面是一个示例客户端请求:

GET http://localhost:38479/GetSalesTaxRate(PostalCode=10) HTTP/1.1

HTTP 响应:

HTTP/1.1 200 OK
Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
OData-Version: 4.0
Date: Sat, 28 Jun 2014 01:05:32 GMT
Content-Length: 82

{
  "@odata.context":"http://localhost:38479/$metadata#Edm.Double","value":5.6
}