操作和使用 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. 本教程在本教程以创建 OData v4 终结点使用 ASP.NET Web API 2The 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.

Note

本教程基于本教程创建 OData v4 终结点使用 ASP.NET Web API 2This tutorial builds on the tutorial Create an OData v4 Endpoint Using ASP.NET Web API 2

首先,添加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; }  
    }
}

此外将添加DbSetProductsContext基类,因此 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; }
}

将操作添加到 EDMAdd 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.Action方法将操作添加到实体数据模型 (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

若要启用"速率"操作,添加以下方法ProductsController:To 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}

"速率"操作绑定到 Product 实例,因此该操作的 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.Rate"。)(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.IsValid为 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>();

在这种情况下,此函数绑定到产品集合,而不是各个实例的产品。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 文件中,添加到 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
}