ASP.NET Web API 사용하여 OData v4에서 형식 열기
OData v4에서 열린 형식은 형식 정의에 선언된 속성 외에도 동적 속성을 포함하는 구조화된 형식입니다. 형식을 열면 데이터 모델에 유연성을 추가할 수 있습니다. 이 자습서에서는 ASP.NET Web API OData에서 열린 형식을 사용하는 방법을 보여줍니다.
이 자습서에서는 ASP.NET Web API OData 엔드포인트를 만드는 방법을 이미 알고 있다고 가정합니다. 그렇지 않은 경우 먼저 OData v4 엔드포인트 만들기를 읽어 보세요.
자습서에서 사용되는 소프트웨어 버전
- Web API OData 5.3
- OData v4
첫째, 일부 OData 용어:
- 엔터티 형식: 키가 있는 구조화된 형식입니다.
- 복합 형식: 키가 없는 구조화된 형식입니다.
- 열기 형식: 동적 속성이 있는 형식입니다. 엔터티 형식과 복합 형식을 모두 열 수 있습니다.
동적 속성의 값은 기본 형식, 복합 형식 또는 열거형 형식일 수 있습니다. 또는 이러한 형식의 컬렉션입니다. 열린 형식에 대한 자세한 내용은 OData v4 사양을 참조하세요.
웹 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 형식에 동적 속성을 보유하는 형식 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
instance 동적 속성이 없습니다. 두 번째 Book
instance 다음과 같은 동적 속성이 있습니다.
- "게시됨": 기본 형식
- "Authors": 기본 형식의 컬렉션
- "OtherCategories": 열거형 형식의 컬렉션입니다.
Press
또한 해당 Book
instance 속성에는 다음과 같은 동적 속성이 있습니다.
- "블로그": 기본 형식
- "주소": 복합 형식
메타데이터 쿼리
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>
메타데이터 문서에서 다음을 확인할 수 있습니다.
- 및
Press
형식의Book
경우 특성 값은OpenType
true입니다. 및Address
형식에는Customer
이 특성이 없습니다. Book
엔터티 형식에는 ISBN, Title 및 Press라는 세 가지 선언된 속성이 있습니다. OData 메타데이터에는 CLR 클래스의Book.Properties
속성이 포함되지 않습니다.- 마찬가지로
Press
복합 형식에는 Name 및 Category라는 두 개의 선언된 속성만 있습니다. 메타데이터에는 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"
]
}
동적 속성은 선언된 속성과 인라인으로 포함됩니다.
엔터티 게시
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
볼 수 있습니다.