本篇体验OData的Action和Function功能。上下文信息参考"ASP.NET Web API基于OData的增删改查,以及处理实体间关系"。在本文之前,我存在的疑惑包括:
● 为什么需要OData的Action和Function功能?
● Action和Function之间有什么区别?
● 如何设置OData的的Action和Function功能?这中间有哪些惯例呢?
为某个Product添加Action
如果我们希望通过http://localhost:54714/odata/Products(1)/SomeActionName来实现在某个Product基础上,为Product的关联表ProductRating添加一条数据,该如何做到呢?
首先创建产品评论的一个模型:
public class ProductRating { public int ID { get; set; } public int Rating { get; set; } public int ProductID { get; set; } public virtual Product Product { get; set; } }
把模型添加到上下文中:
public class ProductsContext : DbContext { public ProductsContext() : base("name=ProductsContext") { } public DbSet<Product> Products { get; set; } public DbSet<Supplier> Suppliers { get; set; } public DbSet<ProductRating> Ratings { get; set; } }
添加迁移并更新数据库:
Add-Migration "AddProductRating" -StartUpProjectName ProductService -ProjectName ProductService
Update-Database -StartUpProjectName ProductService -ProjectName ProductService
现在,需要在WebApiConfig.cs中的Register方法中,为Product的EDM添加一个Action。
ODataModelBuilder builder = new ODataConventionModelBuilder(); builder.Namespace = "ProductService"; builder.EntitySet<Product>("Products");//创建EntityDataModel(EDM) builder.EntitySet<Supplier>("Suppliers"); //http://localhost/Products(1)/ProductService.Rate 注意需要在Web.config中添加配置,因为在IIS上不允许带点,否则会返回404 builder.EntityType<Product>() .Action("Rate")//给EDM添加一个Action .Parameter<int>("Rating"); //Rating作为参数在前后端传递 config.MapODataServiceRoute( routeName: "ODataRoute", routePrefix: "odata", model:builder.GetEdmModel());
以上,
● 通过builder.Namespace定义了Action的命名空间为ProductService
● 通过Action方法给Product这个EDM定义了Rate这个Action
● 通过Parameter<T>方法定义了参数
这意味着:
● 我们发出的请求格式大致是:http://localhost:54714/odata/Products(1)/ProductService.Rate
● API的action方法中,action名称是Rate,可以从前端接受一个名称为Rating的键值
可问题还有:
● 前端如何把Rating这个参数传递出去呢?
● 后端又如何接受这个Rating参数呢?
来看后端控制器部分的action,在ProductsController中添加如下:
//这里的action名称Rate必须和EDM定义的时候保持一致 [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 ex) { if (!ProductExists(key)) { return NotFound(); } else { throw; } } return StatusCode(HttpStatusCode.NoContent); }
可见,后端是通过ODataActionParameters来接受前端传来的变量Rating。
现在前端可以试着发出请求了:
POST http://localhost:54714/odata/Products(1)/ProductService.Rate
Body {"Rating":5}
我们把Rating变量放在了Body中,以json传递给后端。
可是,返回结果是: 404 Not Found
这是因为,IIS还不接受类似ProductService.Rate这样的写法,在Web.config添加如下配置:
<system.webServer> <handlers> ... <remove name="ExtensionlessUrlHandler-Integrated-4.0" /> <add name="ExtensionlessUrlHandler-Integrated-4.0" path="/odata/*" verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" /> </handlers> </system.webServer>
重新请求,返回:204 No Content
为Product集合添加Action
如果想为Products集合添加如下方法发出如下请求:http://localhost:54714/odata/Products/ProductService.MostExpensive
首先还是配置EDM:
builder.EntityType<Product>().Collection .Function("MostExpensive") .Returns<double>();
在ProductsController中添加如下Action:
[HttpGet] public IHttpActionResult MostExpensive() { var product = db.Products.Max(x => x.Price); return Ok(product); }
前端请求:
GET http://localhost:54714/odata/Products/ProductService.MostExpensive
返回:
{
"@odata.context": "http://localhost:54714/odata/$metadata#Edm.Decimal",
"value": 18.2
}
和EDM模型无关,添加Function
当我们需要添加一个与EDM 模型无关的方法时候,就使用Function。
首先在WebApi.config中配置如下:
builder.Function("GetSalesTaxRate") .Returns<double>() .Parameter<int>("PostalCode");
在某个Controller中添加如下:
[HttpGet] [ODataRoute("GetSalesTaxRate(PostalCode={postalCode})")] public IHttpActionResult GetSalesTaxRate([FromODataUri] int postalCode) { double rate = 5.8; return Ok(rate); }
以上,ODataRoute设定路由规则。前端发出如下请求:
GET http://localhost:54714/odata/GetSalesTaxRate(PostalCode=10)
返回:
{
"@odata.context": "http://localhost:54714/odata/$metadata#Edm.Double",
"value": 5.8
}
总结:当需要针对某个EDM模型或EDM模型集合进行CRUD以外的操作,甚至涉及多个模型的操作使用Action,但添加和EDM模型无关的操作,使用Function。