ASP.NET 웹 API를 사용하여 OData v4에서 열린 형식Open Types in OData v4 with ASP.NET Web API

로 마이크로 소프트by Microsoft

OData v4에서 open 형식은 형식 정의에 선언된 모든 속성 외에 동적 속성을 포함하는 구조화된 형식입니다.In OData v4, an open type is a structured type that contains dynamic properties, in addition to any properties that are declared in the type definition. 개방형 형식을 사용하면 데이터 모델에 유연성을 추가할 수 있습니다.Open types let you add flexibility to your data models. 이 자습서에서는 ASP.NET 웹 API OData에서 열린 형식을 사용하는 방법을 보여 주입니다.This tutorial shows how to use open types in ASP.NET Web API OData.

이 자습서에서는 웹 API에서 OData 끝점을 만드는 방법을 이미 알고 ASP.NET 가정합니다.This tutorial assumes that you already know how to create an OData endpoint in ASP.NET Web API. 그렇지 않은 경우 먼저 OData v4 끝점 만들기를 읽는 것으로 시작합니다.If not, start by reading Create an OData v4 Endpoint first.

튜토리얼에 사용되는 소프트웨어 버전Software versions used in the tutorial

  • 웹 API OData 5.3Web API OData 5.3
  • OData v4OData v4

첫째, 일부 OData 용어:First, some OData terminology:

  • 엔터티 유형: 키가 있는 구조화 된 형식입니다.Entity type: A structured type with a key.
  • 복합 유형: 키가 없는 구조식 형식입니다.Complex type: A structured type without a key.
  • 열기 유형: 동적 속성이 있는 형식입니다.Open type: A type with dynamic properties. 엔터티 형식과 복잡한 형식을 모두 열 수 있습니다.Both entity types and complex types can be open.

동적 속성의 값은 기본 형식, 복합 형식 또는 열거형 형식일 수 있습니다. 또는 이러한 유형의 컬렉션입니다.The value of a dynamic property can be a primitive type, complex type, or enumeration type; or a collection of any of those types. 열려 있는 형식에 대한 자세한 내용은 OData v4 사양을참조하십시오.For more information about open types, see the OData v4 specification.

웹 OData 라이브러리 설치Install the Web OData Libraries

NuGet 패키지 관리자를 사용하여 최신 웹 API OData 라이브러리를 설치합니다.Use NuGet Package Manager to install the latest Web API OData libraries. 패키지 관리자 콘솔 창에서:From the Package Manager Console window:

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

CLR 유형 정의Define the CLR Types

먼저 EDM 모델을 CLR 유형으로 정의합니다.Start by defining the EDM models as CLR types.

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(엔터티 데이터 모델)이 생성되면When the Entity Data Model (EDM) is created,

  • Category은 열거형입니다.Category is an enumeration type.
  • Address는 복잡한 형식입니다.Address is a complex type. (키가 없으므로 엔터티 형식이 아닙니다.)(It does not have a key, so it is not an entity type.)
  • Customer는 엔터티 형식입니다.Customer is an entity type. (열쇠가 있습니다.)(It has a key.)
  • Press는 개방형 복합 유형입니다.Press is an open complex type.
  • Book는 열린 엔터티 형식입니다.Book is an open entity type.

열린 형식을 만들려면 CLR 형식에는 동적 속성을 IDictionary<string, object>포함하는 형식의 속성이 있어야 합니다.To create an open type, the CLR type must have a property of type IDictionary<string, object>, which holds the dynamic properties.

EDM 모델 빌드Build the EDM Model

ODataConventionModelBuilder를 사용하여 EDM을 Press 만드는 Book 경우 IDictionary<string, object> 속성의 존재에 따라 개방형 유형으로 자동으로 추가됩니다.If you use ODataConventionModelBuilder to create the EDM, Press and Book are automatically added as open types, based on the presence of a IDictionary<string, object> property.

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을 명시적으로 빌드할 수도 있습니다.You can also build the EDM explicitly, using ODataModelBuilder.

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 컨트롤러 추가Add an OData Controller

다음으로 OData 컨트롤러를 추가합니다.Next, add an OData controller. 이 자습서에서는 GET 및 POST 요청만 지원하고 메모리 내 목록을 사용하여 엔터티를 저장하는 단순화된 컨트롤러를 사용합니다.For this tutorial, we'll use a simplified controller that just supports GET and POST requests, and uses an in-memory list to store entities.

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 인스턴스에는 동적 속성이 없습니다.Notice that the first Book instance has no dynamic properties. Book 번째 인스턴스에는 다음과 같은 동적 속성이 있습니다.The second Book instance has the following dynamic properties:

  • "게시됨": 기본 형식"Published": Primitive type
  • "작성자": 기본 형식의 컬렉션"Authors": Collection of primitive types
  • "기타 범주": 열거형 형식의 컬렉션입니다."OtherCategories": Collection of enumeration types.

또한 해당 Press Book 인스턴스의 속성에는 다음과 같은 동적 속성이 있습니다.Also, the Press property of that Book instance has the following dynamic properties:

  • "블로그": 원시 형식"Blog": Primitive type
  • "주소": 복잡한 유형"Address": Complex type

메타데이터 쿼리Query the Metadata

OData 메타데이터 문서를 받으려면 GET ~/$metadata요청을 로 보냅니다.To get the OData metadata document, send a GET request to ~/$metadata. 응답 본문은 다음과 유사해야 합니다.The response body should look similar to this:

<?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>

메타데이터 문서에서 다음을 확인할 수 있습니다.From the metadata document, you can see that:

  • BookPress 형식의 경우 OpenType 특성값은 true입니다.For the Book and Press types, the value of the OpenType attribute is true. CustomerAddress 형식에는 이 특성이 없습니다.The Customer and Address types don't have this attribute.
  • Book 엔터티 유형에는 ISBN, 제목 및 Press의 세 가지 선언된 속성이 있습니다.The Book entity type has three declared properties: ISBN, Title, and Press. OData 메타데이터에는 CLR 클래스의 Book.Properties 속성이 포함되지 않습니다.The OData metadata does not include the Book.Properties property from the CLR class.
  • 마찬가지로 Press 복합 형식에는 이름과 범주라는 두 개의 선언된 속성만 있습니다.Similarly, the Press complex type has only two declared properties: Name and Category. 메타데이터에는 CLR Press.DynamicProperties 클래스의 속성이 포함되지 않습니다.The metadata does not include the Press.DynamicProperties property from the CLR class.

엔터티 쿼리Query an Entity

ISBN이 "978-0-7356-7942-9"와 동일한 책을 얻으려면 GET 요청을 ~/Books('978-0-7356-7942-9')로 보내십시오.To get the book with ISBN equal to "978-0-7356-7942-9", send a GET request to ~/Books('978-0-7356-7942-9'). 응답 본문은 다음과 유사해야 합니다.The response body should look similar to the following. (더 읽기 쉽게 하기 위해 들여쓰기됩니다.)(Indented to make it more readable.)

{
  "@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"
  ]
}

동적 속성은 선언된 속성과 인라인으로 포함됩니다.Notice that the dynamic properties are included inline with the declared properties.

엔터티 게시POST an Entity

Book 엔터티를 추가하려면 에 POST ~/Books요청을 보냅니다.To add a Book entity, send a POST request to ~/Books. 클라이언트는 요청 페이로드에서 동적 속성을 설정할 수 있습니다.The client can set dynamic properties in the request payload.

다음은 예제 요청입니다.Here is an example request. "가격" 및 "게시된" 속성을 기록합니다.Note the "Price" and "Published" properties.

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 이러한 속성을 사전에 추가한 것을 확인할 수 있습니다.If you set a breakpoint in the controller method, you can see that Web API added these properties to the Properties dictionary.

추가 리소스Additional Resources

OData 오픈 타입 샘플OData Open Type Sample