Поделиться через


Поддержка параметров запроса OData в веб-API ASP.NET 2

Майк Уассон

В этом обзоре с примерами кода демонстрируются поддерживаемые параметры запроса OData в веб-API ASP.NET 2 для ASP.NET 4.x.

OData определяет параметры, которые можно использовать для изменения запроса OData. Клиент отправляет эти параметры в строке запроса URI запроса. Например, для сортировки результатов клиент использует параметр $orderby:

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

Спецификация OData вызывает параметры запроса этих параметров. Вы можете включить параметры запроса OData для любого контроллера веб-API в проекте. Контроллер не должен быть конечной точкой OData. Это позволяет легко добавлять такие функции, как фильтрация и сортировка, в любое приложение веб-API.

Прежде чем включать параметры запроса, ознакомьтесь с разделом Руководство по безопасности OData.

Включение параметров запроса OData

Веб-API поддерживает следующие параметры запроса OData:

Параметр Описание
$expand Развертывает встроенные связанные сущности.
$filter Фильтрует результаты на основе логического условия.
$inlinecount Указывает серверу включить общее количество соответствующих сущностей в ответ. (Полезно для разбиения по страницам на стороне сервера.)
$orderby Сортирует результаты.
$select Выбирает свойства, которые следует включить в ответ.
$skip Пропускает первые n результатов.
$top Возвращает только первые n результатов.

Чтобы использовать параметры запроса OData, их необходимо включить явным образом. Их можно включить глобально для всего приложения или для определенных контроллеров или определенных действий.

Чтобы включить параметры запроса OData глобально, вызовите EnableQuerySupport в классе HttpConfiguration при запуске:

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

    config.EnableQuerySupport();

    // ...
}

Метод EnableQuerySupport включает параметры запроса глобально для любого действия контроллера, возвращающего тип IQueryable . Если вы не хотите, чтобы параметры запроса были включены для всего приложения, их можно включить для определенных действий контроллера, добавив атрибут [Запрашиваемое] в метод action.

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

Примеры запросов

В этом разделе показаны типы запросов, которые можно использовать с помощью параметров запроса OData. Дополнительные сведения о параметрах запроса см. в документации по OData на www.odata.org.

Сведения о $expand и $select см. в статье Использование $select, $expand и $value в веб-API ASP.NET OData.

Разбиение по страницам на основе клиента

Для больших наборов сущностей клиенту может потребоваться ограничить количество результатов. Например, клиент может отображать 10 записей одновременно с ссылками next для получения следующей страницы результатов. Для этого клиент использует параметры $top и $skip.

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

Параметр $top предоставляет максимальное количество возвращаемых записей, а параметр $skip — количество записей, которые нужно пропустить. В предыдущем примере извлекается запись от 21 до 30.

Фильтрация

Параметр $filter позволяет клиенту фильтровать результаты, применяя логическое выражение. Выражения фильтра довольно мощные; Они включают логические и арифметические операторы, строковые функции и функции даты.

Возвращает все продукты с категорией , равной "Игрушки". http://localhost/Products?$filter=Category eq 'Toys'
Возвращает все продукты с ценой менее 10. http://localhost/Products?$filter=Price lt 10
Логические операторы: возвращают все продукты, где цена >= 5 и цена <= 15. http://localhost/Products?$filter=Price ge 5 и Price le 15
Строковые функции: возвращают все продукты с zz в имени. http://localhost/Products?$filter=substringof('zz',Name)
Функции даты: возвращают все продукты с датами releasedate после 2005 года. http://localhost/Products?$filter=year(ReleaseDate) gt 2005

Сортировка

Чтобы отсортировать результаты, используйте фильтр $orderby.

Сортировка по цене. http://localhost/Products?$orderby=Price
Сортировка по цене в порядке убывания (от максимального к наименьшему). http://localhost/Products?$orderby=Price desc
Сортировка по категории, а затем сортировка по цене в порядке убывания в категориях. http://localhost/odata/Products?$orderby=Category,Price desc

Server-Driven разбиение по страницам

Если база данных содержит миллионы записей, вы не хотите отправлять их все в одну полезную нагрузку. Чтобы избежать этого, сервер может ограничить количество записей, отправляемых в одном ответе. Чтобы включить разбиение на страницы сервера, задайте свойство PageSize в атрибуте Queryable . Значением является максимальное количество возвращаемых записей.

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

Если контроллер возвращает формат OData, текст ответа будет содержать ссылку на следующую страницу данных:

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

Клиент может использовать эту ссылку для получения следующей страницы. Чтобы узнать общее количество записей в результирующем наборе, клиент может задать параметр запроса $inlinecount со значением allpages.

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

Значение allpages указывает серверу включить общее количество в ответ:

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

Примечание

Для ссылок на следующую страницу и количества строк требуется формат OData. Причина заключается в том, что OData определяет специальные поля в тексте ответа для хранения ссылки и счетчика.

Для форматов, отличных от OData, по-прежнему можно поддерживать ссылки на следующую страницу и количество встроенных данных, заключив результаты запроса в объект PageResult<T> . Однако для этого требуется немного больше кода. Вот пример:

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:

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

Ограничение параметров запроса

Параметры запроса предоставляют клиенту большой контроль над запросом, выполняемым на сервере. В некоторых случаях может потребоваться ограничить доступные параметры по соображениям безопасности или производительности. Атрибут [Queryable] имеет некоторые встроенные свойства для этого. Рассмотрим некоторые примеры.

Разрешите только $skip и $top для поддержки разбиения по страницам и ничего другого:

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

Разрешите упорядочение только по определенным свойствам, чтобы предотвратить сортировку по свойствам, которые не индексированы в базе данных:

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

Разрешите логическую функцию "eq", но не другие логические функции:

[Queryable(AllowedLogicalOperators=AllowedLogicalOperators.Equal)]

Не разрешайте арифметические операторы:

[Queryable(AllowedArithmeticOperators=AllowedArithmeticOperators.None)]

Вы можете ограничить параметры глобально, создав экземпляр QueryableAttribute и передав его в функцию EnableQuerySupport :

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

Вызов параметров запроса напрямую

Вместо использования атрибута [Запрашиваемое] можно вызывать параметры запроса непосредственно в контроллере. Для этого добавьте параметр ODataQueryOptions в метод контроллера. В этом случае атрибут [Запрашиваемый] не требуется .

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>;
}

Веб-API заполняет ODataQueryOptions из строки запроса URI. Чтобы применить запрос, передайте IQueryable в метод ApplyTo . Метод возвращает другое значение IQueryable.

В расширенных сценариях, если у вас нет поставщика запросов IQueryable , можно изучить ODataQueryOptions и преобразовать параметры запроса в другую форму. (Например, см. запись блога RaghuRam Nadiminti Перевод запросов OData в HQL)

Проверка запросов

Атрибут [Queryable] проверяет запрос перед его выполнением. Шаг проверки выполняется в методе QueryableAttribute.ValidateQuery . Вы также можете настроить процесс проверки.

См. также руководство по безопасности OData.

Сначала переопределите один из классов проверяющих элементов, определенных в пространстве имен Web.Http.OData.Query.Validators . Например, следующий класс проверяющего элемента отключает параметр "desc" для параметра $orderby.

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);
    }
}

Подкласс атрибута [Queryable] для переопределения метода ValidateQuery .

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);
    }
}

Затем задайте пользовательский атрибут глобально или для каждого контроллера:

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

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

Если вы используете ODataQueryOptions напрямую, задайте проверяющий элемент управления для параметров:

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>;
}