使用 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 術語中,這就是繫結。 您也可以擁有「未繫結」動作/函式,這些動作/函式稱為服務上的靜態操作。
範例:新增動作
讓我們定義一個對產品進行評分的動作。
注意
這個教學課程是以「使用 ASP.NET Web API 2 建立 OData v4 端點」教學課程為基礎。
首先,加入 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; }
}
}
也要在 ProductsContext
類別中新增一個 DbSet,以便 EF 將在資料庫中建立一個評分表。
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
}