使用 ASP.NET Web API 2.2 的 OData v4 中的操作和函数Actions and Functions in OData v4 Using ASP.NET Web API 2.2

作者: Mike Wassonby Mike Wasson

在 OData 中,操作和函数是一种添加服务器端行为的方法,这些行为不容易定义为对实体的 CRUD 操作。In OData, actions and functions are a way to add server-side behaviors that are not easily defined as CRUD operations on entities. 本教程演示如何使用 Web API 2.2 将操作和函数添加到 OData v4 终结点。This tutorial shows how to add actions and functions to an OData v4 endpoint, using Web API 2.2. 本教程基于使用 ASP.NET Web API 2 创建 OData V4 终结点教程The tutorial builds on the tutorial Create an OData v4 Endpoint Using ASP.NET Web API 2

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

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

教程版本Tutorial versions

对于 OData 版本3,请参阅ASP.NET Web API 2 中的 Odata 操作For OData Version 3, see OData Actions in ASP.NET Web API 2.

操作函数之间的区别在于操作可能有副作用,而函数不会有副作用。The difference between actions and functions is that actions can have side effects, and functions do not. 操作和函数都可以返回数据。Both actions and functions can return data. 操作的一些用途包括:Some uses for actions include:

  • 复杂事务。Complex transactions.
  • 一次操作多个实体。Manipulating several entities at once.
  • 仅允许更新实体的某些属性。Allowing updates only to certain properties of an entity.
  • 发送的数据不是实体。Sending data that is not an entity.

函数可用于返回与实体或集合不直接对应的信息。Functions are useful for returning information that does not correspond directly to an entity or collection.

操作(或函数)可以面向单个实体或集合。An action (or function) can target a single entity or a collection. 在 OData 术语中,这是绑定In OData terminology, this is the binding. 你还可以 "未绑定的" 操作/函数,这些操作在服务上被称为静态操作。You can also have "unbound" actions/functions, which are called as static operations on the service.

示例:添加操作Example: Adding an Action

让我们定义一个操作来对产品进行评级。Let's define an action to rate a product.

首先,添加一个 ProductRating 模型来表示评级。First, add a ProductRating model to represent the ratings.

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 将在数据库中创建一个分级表。Also add a DbSet to the ProductsContext class, so that EF will create a Ratings table in the database.

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 添加操作Add the Action to the EDM

在 WebApiConfig.cs 中,添加以下代码:In WebApiConfig.cs, add the following code:

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

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

EntityTypeConfiguration方法将操作添加到实体数据模型(EDM)。The EntityTypeConfiguration.Action method adds an action to the entity data model (EDM). 参数方法为操作指定类型化参数。The Parameter method specifies a typed parameter for the action.

此代码还设置 EDM 的命名空间。This code also sets the namespace for the EDM. 命名空间之所以重要,是因为操作的 URI 包含完全限定的操作名称:The namespace matters because the URI for the action includes the fully-qualified action name:

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

Note

在典型的 IIS 配置中,此 URL 中的点将导致 IIS 返回错误404。In a typical IIS configuration, the dot in this URL will cause IIS to return error 404. 可以通过将以下部分添加到 web.config 文件来解决此问题:You can resolve this by adding the following section to your Web.Config file:

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

为操作添加控制器方法Add a Controller Method for the Action

若要启用 "速率" 操作,请将以下方法添加到 ProductsControllerTo enable the "Rate" action, add the following method to 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);
}

请注意,方法名称与操作名称匹配。Notice that the method name matches the action name. [HttpPost] 特性指定方法是 HTTP POST 方法。The [HttpPost] attribute specifies the method is an HTTP POST method.

若要调用该操作,客户端将发送 HTTP POST 请求,如下所示:To invoke the action, the client sends an HTTP POST request like the following:

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

{"Rating":5}

"速率" 操作绑定到产品实例,因此操作的 URI 是附加到实体 URI 的完全限定的操作名称。The "Rate" action is bound to Product instances, so the URI for the action is the fully-qualified action name appended to the entity URI. (请记住,我们将 EDM 命名空间设置为 "ProductService",因此完全限定的操作名称 "ProductService"。)(Recall that we set the EDM namespace to "ProductService", so the fully-qualified action name is "ProductService.Rate".)

请求正文包含操作参数作为 JSON 有效负载。The body of the request contains the action parameters as a JSON payload. Web API 会自动将 JSON 负载转换为ODataActionParameters对象,该对象只是参数值的字典。Web API automatically converts the JSON payload to an ODataActionParameters object, which is just a dictionary of parameter values. 使用此字典可以访问控制器方法中的参数。Use this dictionary to access the parameters in your controller method.

如果客户端发送的操作参数格式不正确,则ModelState的值为 false。If the client sends the action parameters in the wrong format, the value of ModelState.IsValid is false. 检查控制器方法中的此标志,并在IsValid为 false 时返回错误。Check this flag in your controller method and return an error if IsValid is false.

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

示例:添加函数Example: Adding a Function

现在,让我们添加一个返回最昂贵的产品的 OData 函数。Now let's add an OData function that returns the most expensive product. 与前面一样,第一步是将函数添加到 EDM。As before, the first step is adding the function to the EDM. 在 WebApiConfig.cs 中,添加以下代码。In WebApiConfig.cs, add the following code.

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 集合,而不是单独的产品实例。In this case, the function is bound to the Products collection, rather than individual Product instances. 客户端通过发送 GET 请求来调用函数:Clients invoke the function by sending a GET request:

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

下面是此函数的控制器方法:Here is the controller method for this function:

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

    // Other controller methods not shown.
}

请注意,方法名称与函数名称匹配。Notice that the method name matches the function name. [HttpGet] 特性指定方法是 HTTP GET 方法。The [HttpGet] attribute specifies the method is an HTTP GET method.

下面是 HTTP 响应:Here is the HTTP response:

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
}

示例:添加未绑定函数Example: Adding an Unbound Function

上一个示例是绑定到集合的函数。The previous example was a function bound to a collection. 在下面的示例中,我们将创建一个未绑定的函数。In this next example, we'll create an unbound function. 未绑定函数将作为服务的静态操作调用。Unbound functions are called as static operations on the service. 在此示例中,函数将返回给定邮政编码的增值税。The function in this example will return the sales tax for a given postal code.

在 Webapiconfig.cs 文件中,将函数添加到 EDM:In the WebApiConfig file, add the function to the EDM:

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

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

请注意,我们将直接在ODataModelBuilder(而不是实体类型或集合)上调用函数Notice that we are calling Function directly on the ODataModelBuilder, instead of the entity type or collection. 这会告知模型生成器该函数未绑定。This tells the model builder that the function is unbound.

下面是实现函数的控制器方法:Here is the controller method that implements the 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 控制器上并不重要。It does not matter which Web API controller you place this method in. 可以将其放在 ProductsController中,也可以定义一个单独的控制器。You could put it in ProductsController, or define a separate controller. [ODataRoute] 特性定义函数的 URI 模板。The [ODataRoute] attribute defines the URI template for the function.

下面是客户端请求的示例:Here is an example client request:

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

HTTP 响应:The HTTP response:

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
}