ASP.NET Web APIを使用して OData v4 エンドポイントを作成する

Open Data Protocol (OData) は、Web のデータ アクセス プロトコルです。 OData は、CRUD 操作 (作成、読み取り、更新、削除) を使用してデータ セットのクエリと操作を行う一様な方法を提供します。

ASP.NET Web APIでは、プロトコルの v3 と v4 の両方がサポートされます。 v4 エンドポイントを v3 エンドポイントと並行して実行することもできます。

このチュートリアルでは、CRUD 操作をサポートする OData v4 エンドポイントを作成する方法について説明します。

チュートリアルで使用するソフトウェアのバージョン

  • Web API 5.2
  • OData v4
  • Visual Studio 2017 (Visual Studio 2017 はこちらからダウンロード)
  • Entity Framework 6
  • .NET 4.7.2

チュートリアルのバージョン

OData バージョン 3 については、「 OData v3 エンドポイントの作成」を参照してください。

Visual Studio プロジェクトを作成する

Visual Studio の [ ファイル ] メニューで、[ 新しい>プロジェクト] を選択します。

[インストール済み>Visual C#>Web] を展開し、ASP.NET Web アプリケーション (.NET Framework) テンプレートを選択します。 プロジェクトに「ProductService」という名前を付けます。

Visual Studio の新しいプロジェクト ウィンドウのスクリーンショット。ドット NET Framework を使用して A S P ドット NET Web アプリケーションを作成するためのメニュー オプションが表示されています。

[OK] を選択します。

A S P ドット NET Web アプリケーションのスクリーンショット。Web A P I フォルダーとコア参照を使用してアプリケーションを作成するために使用できるテンプレートが示されています。

[空] テンプレートを選択します。 [ Add folders and core references for:]\(フォルダーとコア参照の追加\) で、[ Web API] を選択します。 [OK] を選択します。

OData パッケージをインストールする

[ツール] メニューで、[NuGet パッケージ マネージャー]>[パッケージ マネージャー コンソール] の順に選択します。 [パッケージ マネージャー コンソール] ウィンドウで、次のように入力します。

Install-Package Microsoft.AspNet.Odata

このコマンドは、最新の OData NuGet パッケージをインストールします。

モデル クラスの追加

モデルは、アプリケーション内のデータ エンティティを表すオブジェクトです。

ソリューション エクスプローラーで、[モデル] フォルダーを右クリックします。 コンテキスト メニューの [クラス追加]> を選択します。

ソリューション エクスプローラー ウィンドウのスクリーンショット。モデル クラス オブジェクトをプロジェクトに追加するパスが強調表示されています。

Note

慣例により、モデル クラスは Models フォルダーに配置されますが、独自のプロジェクトでこの規則に従う必要はありません。

クラスに Product という名前を付けます。 Product.cs ファイルで、定型コードを次のように置き換えます。

namespace ProductService.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }
}

プロパティは Id エンティティ キーです。 クライアントは、キーでエンティティに対してクエリを実行できます。 たとえば、ID が 5 の製品を取得するには、URI は です /Products(5)。 プロパティは Id 、バックエンド データベースの主キーでもあります。

Entity Framework を有効にする

このチュートリアルでは、Entity Framework (EF) Code First を使用してバックエンド データベースを作成します。

Note

Web API OData では EF は必要ありません。 データベース エンティティをモデルに変換できる任意のデータ アクセス層を使用します。

まず、EF 用の NuGet パッケージをインストールします。 [ツール] メニューで、[NuGet パッケージ マネージャー]>[パッケージ マネージャー コンソール] の順に選択します。 [パッケージ マネージャー コンソール] ウィンドウで、次のように入力します。

Install-Package EntityFramework

Web.config ファイルを開き、configSections 要素の後に次のセクションを構成要素内に追加します。

<configuration>
  <configSections>
    <!-- ... -->
  </configSections>

  <!-- Add this: -->
  <connectionStrings>
    <add name="ProductsContext" connectionString="Data Source=(localdb)\mssqllocaldb; 
        Initial Catalog=ProductsContext; Integrated Security=True; MultipleActiveResultSets=True; 
        AttachDbFilename=|DataDirectory|ProductsContext.mdf"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

この設定により、LocalDB データベースの接続文字列が追加されます。 このデータベースは、アプリをローカルで実行するときに使用されます。

次に、Models フォルダーに という名前 ProductsContext のクラスを追加します。

using System.Data.Entity;
namespace ProductService.Models
{
    public class ProductsContext : DbContext
    {
        public ProductsContext() 
                : base("name=ProductsContext")
        {
        }
        public DbSet<Product> Products { get; set; }
    }
}

コンストラクターで、 "name=ProductsContext" 接続文字列の名前を指定します。

OData エンドポイントを構成する

ファイル App_Start/WebApiConfig.cs を開きます。 次の using ステートメントを 追加します。

using ProductService.Models;
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;

次に、 Register メソッドに次のコードを追加します。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // New code:
        ODataModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Product>("Products");
        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: builder.GetEdmModel());
    }
}

このコードでは、次の 2 つのことが行われます。

  • エンティティ データ モデル (EDM) を作成します。
  • ルートを追加します。

EDM は、データの抽象モデルです。 EDM は、サービス メタデータ ドキュメントを作成するために使用されます。 ODataConventionModelBuilder クラスは、既定の名前付け規則を使用して EDM を作成します。 この方法では、最小限のコードが必要です。 EDM をより詳細に制御する場合は、 ODataModelBuilder クラスを使用して、プロパティ、キー、ナビゲーション プロパティを明示的に追加して EDM を作成できます。

ルートは、HTTP 要求をエンドポイントにルーティングする方法を Web API に指示します。 OData v4 ルートを作成するには、 MapODataServiceRoute 拡張メソッドを呼び出します。

アプリケーションに複数の OData エンドポイントがある場合は、それぞれに個別のルートを作成します。 各ルートに一意のルート名とプレフィックスを指定します。

OData コントローラーを追加する

コントローラーは、HTTP 要求を処理するクラスです。 OData サービスでエンティティ セットごとに個別のコントローラーを作成します。 このチュートリアルでは、エンティティ用に 1 つのコントローラーを Product 作成します。

ソリューション エクスプローラーで、 Controllers フォルダーを右クリックし、 [クラス追加>] を選択します。 クラスに ProductsController という名前を付けます。

Note

OData v3 のこのチュートリアルのバージョンでは、 コントローラーの追加 スキャフォールディングを使用します。 現在、OData v4 のスキャフォールディングはありません。

ProductsController.cs の定型コードを次のように置き換えます。

using ProductService.Models;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.OData;
namespace ProductService.Controllers
{
    public class ProductsController : ODataController
    {
        ProductsContext db = new ProductsContext();
        private bool ProductExists(int key)
        {
            return db.Products.Any(p => p.Id == key);
        } 
        protected override void Dispose(bool disposing)
        {
            db.Dispose();
            base.Dispose(disposing);
        }
    }
}

コントローラーは、 クラスを ProductsContext 使用して EF を使用してデータベースにアクセスします。 コントローラーが Dispose メソッドをオーバーライドして ProductsContext を破棄していることに注意してください。

これがコントローラーの開始点です。 次に、すべての CRUD 操作のメソッドを追加します。

エンティティ セットのクエリを実行する

に次のメソッドを ProductsController追加します。

[EnableQuery]
public IQueryable<Product> Get()
{
    return db.Products;
}
[EnableQuery]
public SingleResult<Product> Get([FromODataUri] int key)
{
    IQueryable<Product> result = db.Products.Where(p => p.Id == key);
    return SingleResult.Create(result);
}

メソッドのパラメーターなしのバージョンは、 Get Products コレクション全体を返します。 キー パラメーターを持つ メソッドはGet、キー (この場合は プロパティ) によって製品をId検索します。

[EnableQuery] 属性を使用すると、クライアントは、$filter、$sort、$pageなどのクエリ オプションを使用してクエリを変更できます。 詳細については、「 OData クエリ オプションのサポート」を参照してください。

エンティティ セットにエンティティを追加する

クライアントが新しい製品をデータベースに追加できるようにするには、次のメソッドを に追加します ProductsController

public async Task<IHttpActionResult> Post(Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    db.Products.Add(product);
    await db.SaveChangesAsync();
    return Created(product);
}

エンティティを更新する

OData では、エンティティ (PATCH と PUT) を更新するための 2 つの異なるセマンティクスがサポートされています。

  • PATCH は部分的な更新を実行します。 クライアントは、更新するプロパティのみを指定します。
  • PUT はエンティティ全体を置き換えます。

PUT の欠点は、クライアントがエンティティ内のすべてのプロパティ (変更されていない値を含む) の値を送信する必要があることです。 OData 仕様では、PATCH が推奨されていることを示します。

いずれの場合も、PATCH メソッドと PUT メソッドの両方のコードを次に示します。

public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    var entity = await db.Products.FindAsync(key);
    if (entity == null)
    {
        return NotFound();
    }
    product.Patch(entity);
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(entity);
}
public async Task<IHttpActionResult> Put([FromODataUri] int key, Product update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    if (key != update.Id)
    {
        return BadRequest();
    }
    db.Entry(update).State = EntityState.Modified;
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(update);
}

PATCH の場合、コントローラーは Delta<T> 型を使用して変更を追跡します。

エンティティを削除する

クライアントがデータベースから製品を削除できるようにするには、次のメソッドを に追加します ProductsController

public async Task<IHttpActionResult> Delete([FromODataUri] int key)
{
    var product = await db.Products.FindAsync(key);
    if (product == null)
    {
        return NotFound();
    }
    db.Products.Remove(product);
    await db.SaveChangesAsync();
    return StatusCode(HttpStatusCode.NoContent);
}