Suporte a opções de consulta OData no ASP.NET Web API 2
por Mike Wasson
Esta visão geral com exemplos de código demonstra o suporte às Opções de Consulta OData no ASP.NET Web API 2 para ASP.NET 4.x.
OData define parâmetros que podem ser usados para modificar uma consulta OData. O cliente envia esses parâmetros na cadeia de caracteres de consulta do URI de solicitação. Por exemplo, para classificar os resultados, um cliente usa o parâmetro $orderby:
http://localhost/Products?$orderby=Name
A especificação OData chama essas opções de consulta de parâmetros. Você pode habilitar opções de consulta OData para qualquer controlador de API Web em seu projeto — o controlador não precisa ser um ponto de extremidade OData. Isso oferece uma maneira conveniente de adicionar recursos como filtragem e classificação a qualquer aplicativo de API Web.
Antes de habilitar as opções de consulta, leia o tópico Diretrizes de segurança do OData.
- Habilitando opções de consulta OData
- Consultas de exemplo
- Paginação controlada por servidor
- Limitando as opções de consulta
- Invocando opções de consulta diretamente
- Validação de consulta
Habilitando opções de consulta OData
A API Web dá suporte às seguintes opções de consulta OData:
Opção | Descrição |
---|---|
$expand | Expande entidades relacionadas embutidas. |
$filter | Filtra os resultados, com base em uma condição booliana. |
$inlinecount | Informa ao servidor para incluir a contagem total de entidades correspondentes na resposta. (Útil para paginação do lado do servidor.) |
$orderby | Classifica os resultados. |
$select | Seleciona quais propriedades incluir na resposta. |
$skip | Ignora os primeiros n resultados. |
$top | Retorna apenas os primeiros n resultados. |
Para usar as opções de consulta OData, você deve habilitá-las explicitamente. Você pode habilitá-los globalmente para todo o aplicativo ou habilitá-los para controladores específicos ou ações específicas.
Para habilitar as opções de consulta OData globalmente, chame EnableQuerySupport na classe HttpConfiguration na inicialização:
public static void Register(HttpConfiguration config)
{
// ...
config.EnableQuerySupport();
// ...
}
O método EnableQuerySupport habilita as opções de consulta globalmente para qualquer ação do controlador que retorna um tipo IQueryable . Se você não quiser que as opções de consulta sejam habilitadas para todo o aplicativo, habilite-as para ações específicas do controlador adicionando o atributo [Queryable] ao método de ação.
public class ProductsController : ApiController
{
[Queryable]
IQueryable<Product> Get() {}
}
Consultas de Exemplo
Esta seção mostra os tipos de consultas possíveis usando as opções de consulta OData. Para obter detalhes específicos sobre as opções de consulta, consulte a documentação do OData em www.odata.org.
Para obter informações sobre $expand e $select, consulte Usando $select, $expand e $value no ASP.NET Web API OData.
Paginação controlada pelo cliente
Para grandes conjuntos de entidades, talvez o cliente queira limitar o número de resultados. Por exemplo, um cliente pode mostrar 10 entradas por vez, com links "próximo" para obter a próxima página de resultados. Para fazer isso, o cliente usa as opções $top e $skip.
http://localhost/Products?$top=10&$skip=20
A opção $top fornece o número máximo de entradas a serem retornadas e a opção $skip fornece o número de entradas a serem ignoradas. O exemplo anterior busca as entradas de 21 a 30.
Filtragem
A opção $filter permite que um cliente filtre os resultados aplicando uma expressão booliana. As expressões de filtro são bastante poderosas; eles incluem operadores lógicos e aritméticos, funções de cadeia de caracteres e funções de data.
Retorne todos os produtos com categoria igual a "Brinquedos". | http://localhost/Products?$filter=Category eq 'Toys' |
---|---|
Retornar todos os produtos com preço inferior a 10. | http://localhost/Products?$filter=Price lt 10 |
Operadores lógicos: retornam todos os produtos em que o preço >= 5 e o preço <= 15. | http://localhost/Products?$filter=Price ge 5 e Price le 15 |
Funções de cadeia de caracteres: retorna todos os produtos com "zz" no nome. | http://localhost/Products?$filter=substringof('zz',Name) |
Funções de data: retornar todos os produtos com ReleaseDate após 2005. | http://localhost/Products?$filter=year(ReleaseDate) gt 2005 |
Classificação
Para classificar os resultados, use o filtro $orderby.
Classificar por preço. | http://localhost/Products?$orderby=Price |
---|---|
Classificar por preço em ordem decrescente (mais alto para menor). | http://localhost/Products?$orderby=Price desc |
Classifique por categoria e, em seguida, classifique por preço em ordem decrescente dentro das categorias. | http://localhost/odata/Products?$orderby=Category,Price desc |
Paginação Server-Driven
Se o banco de dados contiver milhões de registros, você não deseja enviá-los todos em uma carga. Para evitar isso, o servidor pode limitar o número de entradas que envia em uma única resposta. Para habilitar a paginação do servidor, defina a propriedade PageSize no atributo Queryable . O valor é o número máximo de entradas a serem retornadas.
[Queryable(PageSize=10)]
public IQueryable<Product> Get()
{
return products.AsQueryable();
}
Se o controlador retornar o formato OData, o corpo da resposta conterá um link para a próxima página de dados:
{
"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"
}
O cliente pode usar esse link para buscar a próxima página. Para saber o número total de entradas no conjunto de resultados, o cliente pode definir a opção de consulta $inlinecount com o valor "allpages".
http://localhost/Products?$inlinecount=allpages
O valor "allpages" informa ao servidor para incluir a contagem total na resposta:
{
"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
]
}
Observação
Os links da próxima página e a contagem embutida exigem o formato OData. O motivo é que o OData define campos especiais no corpo da resposta para manter o link e a contagem.
Para formatos não OData, ainda é possível dar suporte a links de próxima página e contagem embutida, encapsulando os resultados da consulta em um objeto PageResult<T> . No entanto, ele requer um pouco mais de código. Veja um exemplo:
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());
}
Aqui está um exemplo de resposta 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
}
Limitando as opções de consulta
As opções de consulta dão ao cliente muito controle sobre a consulta que é executada no servidor. Em alguns casos, talvez você queira limitar as opções disponíveis por motivos de segurança ou desempenho. O atributo [Queryable] tem algumas propriedades internas para isso. Veja alguns exemplos.
Permita apenas $skip e $top, para dar suporte à paginação e nada mais:
[Queryable(AllowedQueryOptions=
AllowedQueryOptions.Skip | AllowedQueryOptions.Top)]
Permita a ordenação somente por determinadas propriedades, para impedir a classificação em propriedades que não são indexadas no banco de dados:
[Queryable(AllowedOrderByProperties="Id")] // comma-separated list of properties
Permitir a função lógica "eq", mas nenhuma outra função lógica:
[Queryable(AllowedLogicalOperators=AllowedLogicalOperators.Equal)]
Não permita operadores aritméticos:
[Queryable(AllowedArithmeticOperators=AllowedArithmeticOperators.None)]
Você pode restringir as opções globalmente construindo uma instância QueryableAttribute e passando-a para a função EnableQuerySupport :
var queryAttribute = new QueryableAttribute()
{
AllowedQueryOptions = AllowedQueryOptions.Top | AllowedQueryOptions.Skip,
MaxTop = 100
};
config.EnableQuerySupport(queryAttribute);
Invocando opções de consulta diretamente
Em vez de usar o atributo [Queryable] , você pode invocar as opções de consulta diretamente no controlador. Para fazer isso, adicione um parâmetro ODataQueryOptions ao método do controlador. Nesse caso, você não precisa do atributo [Queryable] .
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>;
}
A API Web preenche o ODataQueryOptions da cadeia de caracteres de consulta URI. Para aplicar a consulta, passe um IQueryable para o método ApplyTo . O método retorna outro IQueryable.
Para cenários avançados, se você não tiver um provedor de consultas IQueryable , poderá examinar o ODataQueryOptions e converter as opções de consulta em outro formulário. (Por exemplo, consulte a postagem no blog de RaghuRam Nadiminti Traduzindo consultas OData para HQL)
Validação de consulta
O atributo [Queryable] valida a consulta antes de executá-la. A etapa de validação é executada no método QueryableAttribute.ValidateQuery . Você também pode personalizar o processo de validação.
Confira também Diretrizes de Segurança do OData.
Primeiro, substitua uma das classes de validador definidas no namespace Web.Http.OData.Query.Validators . Por exemplo, a classe de validador a seguir desabilita a opção 'desc' para a opção $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);
}
}
Subclasse o atributo [Queryable] para substituir o método 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);
}
}
Em seguida, defina seu atributo personalizado globalmente ou por controlador:
// Globally:
config.EnableQuerySupport(new MyQueryableAttribute());
// Per controller:
public class ValuesController : ApiController
{
[MyQueryable]
public IQueryable<Product> Get()
{
return products.AsQueryable();
}
}
Se você estiver usando ODataQueryOptions diretamente, defina o validador nas opções:
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>;
}