ASP.NET Web API を使用した OData v4 のオープン型

提供元: Microsoft

OData v4 では、"オープン型" は、型定義で宣言されているプロパティに加えて動的プロパティを含む構造化型です。 オープン型を使用すると、データ モデルに柔軟性を追加できます。 このチュートリアルでは、ASP.NET Web API OData でオープン型を使用する方法について説明します。

このチュートリアルでは、ASP.NET Web API で OData エンドポイントを作成する方法を既に理解していることを前提としています。 そうでない場合は、最初に OData v4 エンドポイントの作成に関する記事を読んでください。

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

  • Web API OData 5.3
  • OData v4

まず、OData の用語をいくつか次に示します。

  • エンティティ型: キーを持つ構造化型。
  • 複合型: キーのない構造化型。
  • オープン型: 動的プロパティを持つ型。 エンティティ型と複合型の両方を開くことができます。

動的プロパティの値には、プリミティブ型、複合型、列挙型、またはこれらのいずれかの型のコレクションを指定できます。 オープン型の詳細については、OData v4 の仕様に関する記事を参照してください。

Web OData ライブラリをインストールする

NuGet パッケージ マネージャーを使用して、最新の Web API OData ライブラリをインストールします。 [パッケージ マネージャー コンソール] ウィンドウから:

Install-Package Microsoft.AspNet.OData
Install-Package Microsoft.AspNet.WebApi.OData

CLR 型を定義する

まず、EDM モデルを CLR 型として定義します。

public enum Category
{
    Book,
    Magazine,
    EBook
}

public class Address
{
    public string City { get; set; }
    public string Street { get; set; }
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Press
{
    public string Name { get; set; }
    public string Email { get; set; }
    public Category Category { get; set; }
    public IDictionary<string, object> DynamicProperties { get; set; }
}

public class Book
{
    [Key]
    public string ISBN { get; set; }
    public string Title { get; set; }
    public Press Press { get; set; }
    public IDictionary<string, object> Properties { get; set; }
}

Entity Data Model (EDM) が作成されると、

  • Category は列挙型です。
  • Address は複合型です。 (キーがないため、エンティティ型ではありません)。
  • Customer はエンティティ型です。 (キーが含まれます)。
  • Press はオープン複合型です。
  • Book はオープン エンティティ型です。

オープン型を作成するには、動的プロパティを保持する型 IDictionary<string, object> のプロパティが CLR 型に必要です。

EDM モデルをビルドする

ODataConventionModelBuilder を使用して EDM を作成した場合、IDictionary<string, object> プロパティの存在に基づいて、PressBook がオープン型として自動的に追加されます。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
        builder.EntitySet<Book>("Books");
        builder.EntitySet<Customer>("Customers");
        var model = builder.GetEdmModel();

        config.MapODataServiceRoute(
            routeName: "ODataRoute",
            routePrefix: null,
            model: model);

    }
}

ODataModelBuilder を使用して、EDM を明示的にビルドすることもできます。

ODataModelBuilder builder = new ODataModelBuilder();

ComplexTypeConfiguration<Press> pressType = builder.ComplexType<Press>();
pressType.Property(c => c.Name);
// ...
pressType.HasDynamicProperties(c => c.DynamicProperties);

EntityTypeConfiguration<Book> bookType = builder.EntityType<Book>();
bookType.HasKey(c => c.ISBN);
bookType.Property(c => c.Title);
// ...
bookType.ComplexProperty(c => c.Press);
bookType.HasDynamicProperties(c => c.Properties);

// ...
builder.EntitySet<Book>("Books");
IEdmModel model = builder.GetEdmModel();

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

次に、OData コントローラーを追加します。 このチュートリアルでは、GET および POST 要求のみをサポートし、メモリ内リストを使用してエンティティを格納する、簡略化されたコントローラーを使用します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.OData;

namespace MyApp.Controllers
{
    public class BooksController : ODataController
    {
        private IList<Book> _books = new List<Book>
        {
            new Book
            {
                ISBN = "978-0-7356-8383-9",
                Title = "SignalR Programming in Microsoft ASP.NET",
                Press = new Press
                {
                    Name = "Microsoft Press",
                    Category = Category.Book
                }
            },

            new Book
            {
                ISBN = "978-0-7356-7942-9",
                Title = "Microsoft Azure SQL Database Step by Step",
                Press = new Press
                {
                    Name = "Microsoft Press",
                    Category = Category.EBook,
                    DynamicProperties = new Dictionary<string, object>
                    {
                        { "Blog", "https://blogs.msdn.com/b/microsoft_press/" },
                        { "Address", new Address { 
                              City = "Redmond", Street = "One Microsoft Way" }
                        }
                    }
                },
                Properties = new Dictionary<string, object>
                {
                    { "Published", new DateTimeOffset(2014, 7, 3, 0, 0, 0, 0, new TimeSpan(0))},
                    { "Authors", new [] { "Leonard G. Lobel", "Eric D. Boyd" }},
                    { "OtherCategories", new [] {Category.Book, Category.Magazine}}
                }
            }
        };

        [EnableQuery]
        public IQueryable<Book> Get()
        {
            return _books.AsQueryable();
        }

        public IHttpActionResult Get([FromODataUri]string key)
        {
            Book book = _books.FirstOrDefault(e => e.ISBN == key);
            if (book == null)
            {
                return NotFound();
            }

            return Ok(book);
        }

        public IHttpActionResult Post(Book book)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            } 
            // For this sample, we aren't enforcing unique keys.
            _books.Add(book);
            return Created(book);
        }
    }
}

最初の Book インスタンスには動的プロパティがないことに注意してください。 2 番目の Book インスタンスには、次の動的プロパティがあります。

  • "Published": プリミティブ型
  • "Authors": プリミティブ型のコレクション
  • "OtherCategories": 列挙型のコレクション。

また、その Book インスタンスの Press プロパティには、次の動的プロパティがあります。

  • "Blog": プリミティブ型
  • "Address": 複合型

メタデータのクエリを実行する

OData メタデータ ドキュメントを取得するには、GET 要求を ~/$metadata に送信します。 応答本文は次のようになります。

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
  <edmx:DataServices>
    <Schema Namespace="MyApp.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityType Name="Book" OpenType="true">
        <Key>
          <PropertyRef Name="ISBN" />
        </Key>
        <Property Name="ISBN" Type="Edm.String" Nullable="false" />
        <Property Name="Title" Type="Edm.String" />
        <Property Name="Press" Type="MyApp.Models.Press" />
      </EntityType>
      <EntityType Name="Customer">
        <Key>
          <PropertyRef Name="Id" />
        </Key>
        <Property Name="Id" Type="Edm.Int32" Nullable="false" />
        <Property Name="Name" Type="Edm.String" />
        <Property Name="Address" Type="MyApp.Models.Address" />
      </EntityType>
      <ComplexType Name="Press" OpenType="true">
        <Property Name="Name" Type="Edm.String" />
        <Property Name="Category" Type="MyApp.Models.Category" Nullable="false" />
      </ComplexType>
      <ComplexType Name="Address">
        <Property Name="City" Type="Edm.String" />
        <Property Name="Street" Type="Edm.String" />
      </ComplexType>
      <EnumType Name="Category">
        <Member Name="Book" Value="0" />
        <Member Name="Magazine" Value="1" />
        <Member Name="EBook" Value="2" />
      </EnumType>
    </Schema>
    <Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityContainer Name="Container">
        <EntitySet Name="Books" EntityType="MyApp.Models.Book" />
        <EntitySet Name="Customers" EntityType="MyApp.Models.Customer" />
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

メタデータ ドキュメントから、次のことがわかります。

  • Book および Press 型の場合、OpenType 属性の値は true です。 CustomerAddress 型には、この属性がありません。
  • Book エンティティ型には、ISBN、Title、Press の 3 つの宣言されたプロパティがあります。 OData メタデータには、CLR クラスの Book.Properties プロパティは含まれません。
  • 同様に、Press 複合型には、Name と Category という 2 つの宣言されたプロパティしかありません。 メタデータには、CLR クラスの Press.DynamicProperties プロパティは含まれません。

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

ISBN が "978-0-7356-7942-9" の書籍を取得するには、GET 要求を ~/Books('978-0-7356-7942-9') に送信します。 応答本文は、次のようになります。 (読みやすくするためにインデントされます)。

{
  "@odata.context":"http://localhost:37141/$metadata#Books/$entity",
    "ISBN":"978-0-7356-7942-9",
    "Title":"Microsoft Azure SQL Database Step by Step",
    "Press":{
      "Name":"Microsoft Press",
      "Category":"EBook",
      "Blog":"https://blogs.msdn.com/b/microsoft_press/",
      "Address":{
        "@odata.type":"#MyApp.Models.Address",
        "City":"Redmond",
        "Street":"One Microsoft Way"
      }
  },
  "Published":"2014-07-03T00:00:00Z",
  "Authors@odata.type":"#Collection(String)",
  "Authors":[
    "Leonard G. Lobel","Eric D. Boyd"
  ],
  "OtherCategories@odata.type":"#Collection(MyApp.Models.Category)",
  "OtherCategories":[
    "Book","Magazine"
  ]
}

動的プロパティは、宣言されたプロパティにインラインで含まれていることに注意してください。

エンティティを POST する

Book エンティティを追加するには、POST 要求を ~/Books に送信します。 クライアントでは、要求ペイロードで動的プロパティを設定できます。

要求の例を以下に示します。 "Price" および "Published" プロパティをメモします。

POST http://localhost:37141/Books HTTP/1.1
User-Agent: Fiddler
Host: localhost:37141
Content-Type: application/json
Content-Length: 191

{
  "ISBN":"978-0-7356-8383-9","Title":"Programming Microsoft ASP.NET MVC","Press":{
  "Name":"Microsoft Press","Category":"Book"
   }, "Price": 49.99, "Published":"2014-02-15T00:00:00Z"
}

コントローラー メソッドにブレークポイントを設定した場合、Web API によってこれらのプロパティが Properties ディクショナリに追加されていることがわかります。

Screenshot of the code that sends a POST request, highlighting the 'add books' part of the code to show the properties added by the Web A P I.

その他のリソース

OData オープン型のサンプル