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>
プロパティの存在に基づいて、Press
と Book
がオープン型として自動的に追加されます。
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 です。Customer
とAddress
型には、この属性がありません。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
ディクショナリに追加されていることがわかります。