使用 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; }
}
建立實體資料模型 (EDM) 時,
Category
是一個枚舉類型。Address
是一個複雜類型。 (它沒有金鑰,因此它不是實體類型。)Customer
是一個實體類型。 (它有一個金鑰。)Press
是一種開放的複雜類型。Book
是一種開放的實體類型。
若要建立開放類型,CLR 類型必須具有 type 屬性 IDictionary<string, object>
,該屬性會保存動態屬性。
建構 EDM 模型
如果您使用 ODataConventionModelBuilder 建立 EDM,並且 Press
和 Book
會根據 IDictionary<string, object>
屬性的存在自動新增為開放類型。
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
執行個體沒有動態屬性。 第二個 Book
執行個體具有以下動態屬性:
- 「已發表」:原始類型
- “Authors”:原始類型的集合
- “OtherCategories”:枚舉類型的集合。
此外,Book
執行個體的 Press
屬性具有下列動態屬性:
- 「部落格」:原始類型
- 「地址」:複雜類型
查詢中繼資料
若要取得 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。 OData 中繼資料不包括 CLR 類別的Book.Properties
屬性。- 同樣,
Press
複雜類型只有兩個宣告的屬性:Name 和 Category。 中繼資料不包括 CLR 類別的Press.DynamicProperties
屬性。
查詢實體
若要取得 ISBN 等於「978-0-7356-7942-9」的圖書,請向 ~/Books('978-0-7356-7942-9')
傳送 GET 請求。 回應本文應類似於以下內容。 (縮排以使其更具可讀性。)
{
"@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"
]
}
請注意,動態屬性包含在宣告的屬性中。
發佈實體
若要新增 Book 實體,請將 POST 要求傳送至 ~/Books
。 用戶端可以在請求負載中設定動態屬性。
這是一個請求範例。 請注意「價格」和「已發布」屬性。
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
字典中。