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 アクション」を参照してください。

アクション関数の違いは、アクションには副作用がある可能性があるのに対し、関数には副作用がないということです。 アクションと関数の両方がデータを返すことができます。 アクションには、次のような用途があります。

  • 複雑なトランザクション。
  • 一度に複数のエンティティを操作。
  • エンティティの特定のプロパティに対してのみ更新を許可する。
  • エンティティではないデータの送信。

関数は、エンティティまたはコレクションに直接対応しない情報を返す場合に便利です。

アクション (または関数) は、1 つのエンティティまたはコレクションを対象にすることができます。 OData の用語では、これがバインディングです。 サービスに対する静的操作として呼び出される "バインドされていない" アクション/関数を持つこともできます。

例: アクションの追加

製品を評価するアクションを定義しましょう。

Note

このチュートリアルは、「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; }  
    }
}

また、DbSetProductsContext クラスに追加して、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

Note

一般的な 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>

アクションの Controller メソッドを追加する

"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 オブジェクトに自動的に変換します。これは単なるパラメーター値のディクショナリです。 このディクショナリを使用して、Controller メソッドのパラメーターにアクセスします。

クライアントがアクション パラメーターを間違った形式で送信した場合、ModelState.IsValid の値は false になります。 IsValid が false の場合は、Controller メソッドでこのフラグを確認し、エラーを返します。

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>();

この場合、関数は個々の Product インスタンスではなく Products コレクションにバインドされます。 クライアントは GET 要求を送信して関数を呼び出します。

GET http://localhost:38479/Products/ProductService.MostExpensive

この関数の Controller メソッドを次に示します。

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
}

例: Unbound 関数の追加

前の例は、コレクションにバインドされた関数でした。 この次の例では、バインドされていない関数を作成します。 バインドされていない関数は、サービスに対する静的操作として呼び出されます。 この例の 関数は、指定された郵便番号の消費税を返します。

WebApiConfig ファイルで、EDM に関数を追加します。

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");

// New code:
builder.Function("GetSalesTaxRate")
    .Returns<double>()
    .Parameter<int>("PostalCode");

エンティティ型またはコレクションではなく、ODataModelBuilderFunction を直接呼び出していることに注意してください。 これにより、関数がバインド解除されていることをモデル ビルダーに通知します。

関数を実装する Controller メソッドを次に示します。

[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
}