使用 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
}