共用方式為


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