Ordenação dinâmica passo a passo
ResumoOrdenação dinâmica permite ao cliente escolher os campos e a direção (
orderBy=preco desc,nome asc). Implementamos com construção dinâmica de expressão, de forma segura contra injeção.
1. Objetivos de Aprendizagem#
- Aceitar parâmetros de ordenação múltipla via query string.
- Construir a cláusula
ORDER BYdinamicamente e com segurança. - Definir uma ordenação padrão e validar campos permitidos.
2. Pré-requisitos#
- Busca e filtragem (Módulos 17–18).
3. Conceito#
O cliente envia orderBy=preco desc,nome. Precisamos transformar isso numa ordenação aplicada à IQueryable. O desafio: fazer isso sem abrir brecha de injeção — só campos existentes e permitidos podem ser usados. Uma abordagem segura combina reflexão para validar propriedades e a biblioteca System.Linq.Dynamic.Core para aplicar.
4. Mão na Massa#
4.1. Setup#
dotnet add Catalog.Repository package System.Linq.Dynamic.Core
4.2. Implementação Passo a Passo#
- Helper que monta a string validada:
using System.Reflection;
using System.Text;
public static class OrderQueryBuilder
{
public static string CreateOrderQuery<T>(string? orderByQueryString)
{
var defaultOrder = $"{typeof(T).GetProperties()[0].Name} ascending";
if (string.IsNullOrWhiteSpace(orderByQueryString)) return defaultOrder;
var terms = orderByQueryString.Split(',', StringSplitOptions.RemoveEmptyEntries);
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
var sb = new StringBuilder();
foreach (var term in terms)
{
var field = term.Trim().Split(' ')[0];
var prop = props.FirstOrDefault(p =>
p.Name.Equals(field, StringComparison.OrdinalIgnoreCase));
if (prop is null) continue; // ignora campos inexistentes (segurança)
var direction = term.Trim().EndsWith(" desc", StringComparison.OrdinalIgnoreCase)
? "descending" : "ascending";
sb.Append($"{prop.Name} {direction}, ");
}
var result = sb.ToString().TrimEnd(',', ' ');
return string.IsNullOrWhiteSpace(result) ? defaultOrder : result;
}
}
- Aplicação no repositório:
using System.Linq.Dynamic.Core;
var ordered = query.OrderBy(OrderQueryBuilder.CreateOrderQuery<Produto>(p.OrderBy));
4.3. Executando#
curl "http://localhost:5000/api/categorias/{catId}/produtos?orderBy=preco desc,nome"
5. Exemplo Completo#
Campos inexistentes ou não permitidos são ignorados, e sempre há uma ordenação padrão — nunca deixamos Skip/Take sem ordem definida (Módulo 30).
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
| Validar campos contra as propriedades reais (allowlist) | Interpolar o input direto no OrderBy dinâmico |
| Definir ordenação padrão | Paginar sem ORDER BY |
Ignorar termos inválidos silenciosamente ou retornar 400 | Lançar exceção crua para input malformado |
Atenção
System.Linq.Dynamic.Coreexecuta strings como expressão. Nunca passe o input do usuário sem validar contra uma allowlist de propriedades — caso contrário, é uma brecha de injeção.
7. Segurança e Produção#
- A allowlist por reflexão é a defesa central contra injeção de ordenação.
- Índices nas colunas ordenáveis evitam sorts caros no banco.
8. Exercícios#
- Fácil: adicione ordenação por data de criação.
- Médio: retorne
400quando nenhum campo válido for informado (em vez de usar o padrão). - Desafio: implemente a ordenação sem
System.Linq.Dynamic.Core, montandoExpressionmanualmente.
9. Resumo#
Ordenação dinâmica traduz orderBy=... em ORDER BY, validando campos por reflexão (allowlist) e garantindo ordenação padrão. Segurança contra injeção é obrigatória.
10. Próximos Passos#
Módulo 34: data shaping — deixar o cliente escolher quais campos retornar.
11. Referências#
- Microsoft Learn — Ordenar dados com LINQ
- Documentação — System.Linq.Dynamic.Core
- Microsoft Learn — Reflexão em .NET