ASP.NET Web API 2에서 OData 쿼리 옵션 지원Supporting OData Query Options in ASP.NET Web API 2

Mike Wassonby Mike Wasson

이 개요 코드 예제에서는 ASP.NET 4. x의 ASP.NET Web API 2에서 지 원하는 OData 쿼리 옵션을 보여 줍니다.This overview with code examples demonstrates the supporting OData Query Options in ASP.NET Web API 2 for ASP.NET 4.x.

OData는 OData 쿼리를 수정 하는 데 사용할 수 있는 매개 변수를 정의 합니다.OData defines parameters that can be used to modify an OData query. 클라이언트는 요청 URI의 쿼리 문자열에서 이러한 매개 변수를 보냅니다.The client sends these parameters in the query string of the request URI. 예를 들어 결과를 정렬 하기 위해 클라이언트는 $orderby 매개 변수를 사용 합니다.For example, to sort the results, a client uses the $orderby parameter:

http://localhost/Products?$orderby=Name

OData 사양에서는 이러한 매개 변수 쿼리 옵션을 호출 합니다.The OData specification calls these parameters query options. 컨트롤러가 OData 끝점이 될 필요 없이 프로젝트의 웹 API 컨트롤러에 대해 OData 쿼리 옵션을 사용 하도록 설정할 수 —.You can enable OData query options for any Web API controller in your project — the controller does not need to be an OData endpoint. 이렇게 하면 웹 API 응용 프로그램에 대 한 필터링 및 정렬과 같은 기능을 편리 하 게 추가할 수 있습니다.This gives you a convenient way to add features such as filtering and sorting to any Web API application.

쿼리 옵션을 사용 하도록 설정 하기 전에 OData 보안 지침항목을 참조 하세요.Before enabling query options, please read the topic OData Security Guidance.

OData 쿼리 옵션 사용Enabling OData Query Options

Web API는 다음과 같은 OData 쿼리 옵션을 지원 합니다.Web API supports the following OData query options:

옵션Option DescriptionDescription
$expand$expand 관련 엔터티를 인라인으로 확장 합니다.Expands related entities inline.
$filter$filter 부울 조건에 따라 결과를 필터링 합니다.Filters the results, based on a Boolean condition.
$inlinecount$inlinecount 응답에 일치 하는 엔터티의 총 개수를 포함 하도록 서버에 지시 합니다.Tells the server to include the total count of matching entities in the response. 서버 쪽 페이징에 유용 합니다.(Useful for server-side paging.)
$orderby$orderby 결과를 정렬 합니다.Sorts the results.
$select$select 응답에 포함할 속성을 선택 합니다.Selects which properties to include in the response.
$skip$skip 처음 n 개 결과를 건너뜁니다.Skips the first n results.
$top$top 결과의 첫 번째 n 개만 반환 합니다.Returns only the first n the results.

OData 쿼리 옵션을 사용 하려면 명시적으로 사용 하도록 설정 해야 합니다.To use OData query options, you must enable them explicitly. 전체 응용 프로그램에 대해 전역적으로 사용 하도록 설정 하거나 특정 컨트롤러 또는 특정 작업에 대해 사용 하도록 설정할 수 있습니다.You can enable them globally for the entire application, or enable them for specific controllers or specific actions.

OData 쿼리 옵션을 전역적으로 설정 하려면 시작 시 Httpconfiguration 클래스에서 enablequerysupport 를 호출 합니다.To enable OData query options globally, call EnableQuerySupport on the HttpConfiguration class at startup:

public static void Register(HttpConfiguration config)
{
    // ...

    config.EnableQuerySupport();

    // ...
}

Enablequerysupport 메서드는 IQueryable 형식을 반환 하는 모든 컨트롤러 작업에 대해 전역적으로 쿼리 옵션을 사용 하도록 설정 합니다.The EnableQuerySupport method enables query options globally for any controller action that returns an IQueryable type. 전체 응용 프로그램에 대해 쿼리 옵션을 사용 하지 않으려면 [쿼리 가능 ] 특성을 동작 메서드에 추가 하 여 특정 컨트롤러 작업에 대해 쿼리 옵션을 사용 하도록 설정할 수 있습니다.If you don't want query options enabled for the entire application, you can enable them for specific controller actions by adding the [Queryable] attribute to the action method.

public class ProductsController : ApiController
{
    [Queryable]
    IQueryable<Product> Get() {}
}

예제 쿼리Example Queries

이 섹션에서는 OData 쿼리 옵션을 사용 하 여 가능한 쿼리 유형을 보여 줍니다.This section shows the types of queries that are possible using the OData query options. 쿼리 옵션에 대 한 자세한 내용은 www.odata.org의 OData 설명서를 참조 하세요.For specific details about the query options, refer to the OData documentation at www.odata.org.

$Expand 및 $select에 대 한 자세한 내용은 $Value OData에서 $select, $expand 및 ASP.NET Web API 사용을 참조 하세요.For information about $expand and $select, see Using $select, $expand, and $value in ASP.NET Web API OData.

클라이언트 구동 페이징Client-Driven Paging

대량 엔터티 집합의 경우 클라이언트에서 결과 수를 제한할 수 있습니다.For large entity sets, the client might want to limit the number of results. 예를 들어 클라이언트는 다음 결과 페이지를 가져오기 위해 "다음" 링크를 포함 하 여 한 번에 10 개의 항목을 표시할 수 있습니다.For example, a client might show 10 entries at a time, with "next" links to get the next page of results. 이를 위해 클라이언트는 $top 및 $skip 옵션을 사용 합니다.To do this, the client uses the $top and $skip options.

http://localhost/Products?$top=10&$skip=20

$Top 옵션은 반환할 항목의 최대 수를 제공 하 고, $skip 옵션은 건너뛸 항목 수를 제공 합니다.The $top option gives the maximum number of entries to return, and the $skip option gives the number of entries to skip. 이전 예제에서는 21 ~ 30 개 항목을 인출 합니다.The previous example fetches entries 21 through 30.

필터링Filtering

$Filter 옵션을 사용 하면 클라이언트에서 부울 식을 적용 하 여 결과를 필터링 할 수 있습니다.The $filter option lets a client filter the results by applying a Boolean expression. 필터 식은 매우 강력 합니다. 여기에는 논리 및 산술 연산자, 문자열 함수 및 날짜 함수가 포함 됩니다.The filter expressions are quite powerful; they include logical and arithmetic operators, string functions, and date functions.

범주가 "장난감"과 같은 모든 제품을 반환 합니다.Return all products with category equal to "Toys". http://localhost/Products?$filter=Category eq ' 장난감 'http://localhost/Products?$filter=Category eq 'Toys'
가격이 10 보다 작은 모든 제품을 반환 합니다.Return all products with price less than 10. http://localhost/Products?$filter=Price lt 10http://localhost/Products?$filter=Price lt 10
논리 연산자: price >= 5 및 price <= 15 인 모든 제품을 반환 합니다.Logical operators: Return all products where price >= 5 and price <= 15. http://localhost/Products?$filter=Price ge 5 및 Price le 15http://localhost/Products?$filter=Price ge 5 and Price le 15
문자열 함수: 이름에 "zz"가 있는 모든 제품을 반환 합니다.String functions: Return all products with "zz" in the name. http://localhost/Products?$filter=substringof('zz',Name)
날짜 함수: 2005 이후의 ReleaseDate를 사용 하는 모든 제품을 반환 합니다.Date functions: Return all products with ReleaseDate after 2005. http://localhost/Products?$filter=year(ReleaseDate) gt 2005http://localhost/Products?$filter=year(ReleaseDate) gt 2005

정렬Sorting

결과를 정렬 하려면 $orderby 필터를 사용 합니다.To sort the results, use the $orderby filter.

가격을 기준으로 정렬 합니다.Sort by price. http://localhost/Products?$orderby=Price
가격을 기준으로 내림차순으로 정렬 합니다 (최고부터 낮음).Sort by price in descending order (highest to lowest). http://localhost/Products?$orderby=Price desc
범주별로 정렬 한 다음, 범주 내에서 내림차순으로 정렬 합니다.Sort by category, then sort by price in descending order within categories. http://localhost/odata/Products?$orderby=Category,Price desc

서버 기반 페이징Server-Driven Paging

데이터베이스에 수백만 개의 레코드가 포함 되어 있는 경우 한 페이로드에 모두 전송 하지는 않습니다.If your database contains millions of records, you don't want to send them all in one payload. 이를 방지 하기 위해 서버는 단일 응답으로 전송 되는 항목 수를 제한할 수 있습니다.To prevent this, the server can limit the number of entries that it sends in a single response. 서버 페이징을 사용 하도록 설정 하려면 쿼리 가능한 특성의 PageSize 속성을 설정 합니다.To enable server paging, set the PageSize property in the Queryable attribute. 값은 반환할 최대 항목 수입니다.The value is the maximum number of entries to return.

[Queryable(PageSize=10)]
public IQueryable<Product> Get() 
{
    return products.AsQueryable();
}

컨트롤러가 OData 형식을 반환 하는 경우 응답 본문에는 다음 데이터 페이지에 대 한 링크가 포함 됩니다.If your controller returns OData format, the response body will contain a link to the next page of data:

{
  "odata.metadata":"http://localhost/$metadata#Products",
  "value":[
    { "ID":1,"Name":"Hat","Price":"15","Category":"Apparel" },
    { "ID":2,"Name":"Socks","Price":"5","Category":"Apparel" },
    // Others not shown
  ],
  "odata.nextLink":"http://localhost/Products?$skip=10"
}

클라이언트는이 링크를 사용 하 여 다음 페이지를 가져올 수 있습니다.The client can use this link to fetch the next page. 클라이언트는 결과 집합에 있는 총 항목 수를 확인 하기 위해 "allpages" 값을 사용 하 여 $inlinecount 쿼리 옵션을 설정할 수 있습니다.To learn the total number of entries in the result set, the client can set the $inlinecount query option with the value "allpages".

http://localhost/Products?$inlinecount=allpages

"Allpages" 값은 응답의 총 개수를 포함 하도록 서버에 지시 합니다.The value "allpages" tells the server to include the total count in the response:

{
  "odata.metadata":"http://localhost/$metadata#Products",
  "odata.count":"50",
  "value":[
    { "ID":1,"Name":"Hat","Price":"15","Category":"Apparel" },
    { "ID":2,"Name":"Socks","Price":"5","Category":"Apparel" },
    // Others not shown
  ]
}

Note

다음-페이지 링크 및 인라인 개수에는 모두 OData 형식이 필요 합니다.Next-page links and inline count both require OData format. 그 이유는 OData에서 링크 및 개수를 유지 하기 위해 응답 본문에 특수 필드를 정의 하기 때문입니다.The reason is that OData defines special fields in the response body to hold the link and count.

OData 형식이 아닌 경우 **PageResult < T > ** 개체에 쿼리 결과를 래핑하여 다음 페이지 링크 및 인라인 개수를 지원할 수 있습니다.For non-OData formats, it is still possible to support next-page links and inline count, by wrapping the query results in a PageResult<T> object. 그러나 좀 더 많은 코드가 필요 합니다.However, it requires a bit more code. 다음은 예제입니다.Here is an example:

public PageResult<Product> Get(ODataQueryOptions<Product> options)
{
    ODataQuerySettings settings = new ODataQuerySettings()
    {
        PageSize = 5
    };

    IQueryable results = options.ApplyTo(_products.AsQueryable(), settings);

    return new PageResult<Product>(
        results as IEnumerable<Product>, 
        Request.GetNextPageLink(), 
        Request.GetInlineCount());
}

다음은 JSON 응답 예제입니다.Here is an example JSON response:

{
  "Items": [
    { "ID":1,"Name":"Hat","Price":"15","Category":"Apparel" },
    { "ID":2,"Name":"Socks","Price":"5","Category":"Apparel" },

    // Others not shown
    
  ],
  "NextPageLink": "http://localhost/api/values?$inlinecount=allpages&$skip=10",
  "Count": 50
}

쿼리 옵션 제한Limiting the Query Options

쿼리 옵션을 통해 클라이언트는 서버에서 실행 되는 쿼리에 대해 많은 제어를 제공 합니다.The query options give the client a lot of control over the query that is run on the server. 경우에 따라 보안 또는 성능상의 이유로 사용 가능한 옵션을 제한할 수 있습니다.In some cases, you might want to limit the available options for security or performance reasons. [쿼리 가능] 특성에는이에 대 한 몇 가지 기본 제공 속성이 있습니다.The [Queryable] attribute has some built in properties for this. 다음은 몇 가지 예제입니다.Here are some examples.

$Skip 및 $top만 허용 하 여 페이징을 지원 합니다.Allow only $skip and $top, to support paging and nothing else:

[Queryable(AllowedQueryOptions=
    AllowedQueryOptions.Skip | AllowedQueryOptions.Top)]

데이터베이스에서 인덱싱되지 않은 속성의 정렬을 방지 하기 위해 특정 속성 으로만 순서를 지정할 수 있습니다.Allow ordering only by certain properties, to prevent sorting on properties that are not indexed in the database:

[Queryable(AllowedOrderByProperties="Id")] // comma-separated list of properties

"Eq" 논리 함수를 허용 하지만 다른 논리 함수는 허용 하지 않습니다.Allow the "eq" logical function but no other logical functions:

[Queryable(AllowedLogicalOperators=AllowedLogicalOperators.Equal)]

산술 연산자를 허용 하지 않습니다.Do not allow any arithmetic operators:

[Queryable(AllowedArithmeticOperators=AllowedArithmeticOperators.None)]

QueryableAttribute 인스턴스를 생성 하 여 enablequerysupport 함수에 전달 하 여 전역으로 옵션을 제한할 수 있습니다.You can restrict options globally by constructing a QueryableAttribute instance and passing it to the EnableQuerySupport function:

var queryAttribute = new QueryableAttribute()
{
    AllowedQueryOptions = AllowedQueryOptions.Top | AllowedQueryOptions.Skip,
    MaxTop = 100
};
                
config.EnableQuerySupport(queryAttribute);

직접 쿼리 옵션 호출Invoking Query Options Directly

[쿼리할 수 있는] 특성을 사용 하는 대신 컨트롤러에서 직접 쿼리 옵션을 호출할 수 있습니다.Instead of using the [Queryable] attribute, you can invoke the query options directly in your controller. 이렇게 하려면 ODataQueryOptions 매개 변수를 컨트롤러 메서드에 추가 합니다.To do so, add an ODataQueryOptions parameter to the controller method. 이 경우 [쿼리 가능] 특성이 필요 하지 않습니다.In this case, you don't need the [Queryable] attribute.

public IQueryable<Product> Get(ODataQueryOptions opts)
{
    var settings = new ODataValidationSettings()
    {
        // Initialize settings as needed.
        AllowedFunctions = AllowedFunctions.AllMathFunctions
    };

    opts.Validate(settings);

    IQueryable results = opts.ApplyTo(products.AsQueryable());
    return results as IQueryable<Product>;
}

Web API는 URI 쿼리 문자열에서 ODataQueryOptions 를 채웁니다.Web API populates the ODataQueryOptions from the URI query string. 쿼리를 적용 하려면 IQueryableapplyto 메서드에 전달 합니다.To apply the query, pass an IQueryable to the ApplyTo method. 메서드는 다른 IQueryable을 반환 합니다.The method returns another IQueryable.

고급 시나리오의 경우 IQueryable 쿼리 공급자가 없으면 ODataQueryOptions 을 검토 하 고 쿼리 옵션을 다른 형식으로 변환할 수 있습니다.For advanced scenarios, if you do not have an IQueryable query provider, you can examine the ODataQueryOptions and translate the query options into another form. (예를 들어 RaghuRam Nadiminti의 블로그 게시물 OData 쿼리를 HQL로 변환( 샘플도 포함)을 참조 하세요.)(For example, see RaghuRam Nadiminti's blog post Translating OData queries to HQL, which also includes a sample.)

쿼리 유효성 검사Query Validation

[쿼리 가능 ] 특성은 쿼리를 실행 하기 전에 유효성을 검사 합니다.The [Queryable] attribute validates the query before executing it. 유효성 검사 단계는 QueryableAttribute 쿼리 메서드에서 수행 됩니다.The validation step is performed in the QueryableAttribute.ValidateQuery method. 유효성 검사 프로세스를 사용자 지정할 수도 있습니다.You can also customize the validation process.

또한 OData 보안 지침을 참조 하세요.Also see OData Security Guidance.

먼저, web.config 에 정의 된 유효성 검사기 클래스 중 하나를 재정의 합니다.First, override one of the validator classes that is defined in the Web.Http.OData.Query.Validators namespace. 예를 들어 다음 유효성 검사기 클래스는 $orderby 옵션에 대 한 ' desc ' 옵션을 사용 하지 않도록 설정 합니다.For example, the following validator class disables the 'desc' option for the $orderby option.

public class MyOrderByValidator : OrderByQueryValidator
{
    // Disallow the 'desc' parameter for $orderby option.
    public override void Validate(OrderByQueryOption orderByOption,
                                    ODataValidationSettings validationSettings)
    {
        if (orderByOption.OrderByNodes.Any(
                node => node.Direction == OrderByDirection.Descending))
        {
            throw new ODataException("The 'desc' option is not supported.");
        }
        base.Validate(orderByOption, validationSettings);
    }
}

Validatequery 메서드를 재정의 하는 [쿼리할 수 있는] 특성의 서브 클래스입니다.Subclass the [Queryable] attribute to override the ValidateQuery method.

public class MyQueryableAttribute : QueryableAttribute
{
    public override void ValidateQuery(HttpRequestMessage request, 
        ODataQueryOptions queryOptions)
    {
        if (queryOptions.OrderBy != null)
        {
            queryOptions.OrderBy.Validator = new MyOrderByValidator();
        }
        base.ValidateQuery(request, queryOptions);
    }
}

그런 다음 사용자 지정 특성을 전역으로 설정 하거나 controller 별로 설정 합니다.Then set your custom attribute either globally or per-controller:

// Globally:
config.EnableQuerySupport(new MyQueryableAttribute());

// Per controller:
public class ValuesController : ApiController
{
    [MyQueryable]
    public IQueryable<Product> Get()
    {
        return products.AsQueryable();
    }
}

ODataQueryOptions 를 직접 사용 하는 경우 옵션에서 유효성 검사기를 설정 합니다.If you are using ODataQueryOptions directly, set the validator on the options:

public IQueryable<Product> Get(ODataQueryOptions opts)
{
    if (opts.OrderBy != null)
    {
        opts.OrderBy.Validator = new MyOrderByValidator();
    }

    var settings = new ODataValidationSettings()
    {
        // Initialize settings as needed.
        AllowedFunctions = AllowedFunctions.AllMathFunctions
    };

    // Validate
    opts.Validate(settings);

    IQueryable results = opts.ApplyTo(products.AsQueryable());
    return results as IQueryable<Product>;
}