Obsługa opcji zapytań OData w interfejsie API sieci Web 2 ASP.NET
Autor: Mike Wasson
Ten przegląd z przykładami kodu przedstawia obsługę opcji zapytań OData w ASP.NET Web API 2 dla ASP.NET 4.x.
OData definiuje parametry, których można użyć do modyfikowania zapytania OData. Klient wysyła te parametry w ciągu zapytania identyfikatora URI żądania. Na przykład w celu posortowania wyników klient używa parametru $orderby:
http://localhost/Products?$orderby=Name
Specyfikacja OData wywołuje te opcje zapytania parametrów. W projekcie można włączyć opcje zapytań OData dla dowolnego kontrolera internetowego interfejsu API — kontroler nie musi być punktem końcowym OData. Zapewnia to wygodny sposób dodawania funkcji, takich jak filtrowanie i sortowanie do dowolnej aplikacji internetowego interfejsu API.
Przed włączeniem opcji zapytania zapoznaj się z tematem Wskazówki dotyczące zabezpieczeń OData.
- Włączanie opcji zapytania OData
- Przykładowe zapytania
- Stronicowanie oparte na serwerze
- Ograniczanie opcji zapytania
- Wywoływanie opcji zapytania bezpośrednio
- Sprawdzanie poprawności zapytania
Włączanie opcji zapytania OData
Internetowy interfejs API obsługuje następujące opcje zapytania OData:
Opcja | Opis |
---|---|
$expand | Rozwija powiązane jednostki w tekście. |
$filter | Filtruje wyniki na podstawie warunku logicznego. |
$inlinecount | Informuje serwer o dołączeniu łącznej liczby pasujących jednostek w odpowiedzi. (Przydatne w przypadku stronicowania po stronie serwera). |
$orderby | Sortuje wyniki. |
$select | Wybiera właściwości do uwzględnienia w odpowiedzi. |
$skip | Pomija pierwsze n wyników. |
$top | Zwraca tylko pierwsze n wyników. |
Aby użyć opcji zapytania OData, należy je jawnie włączyć. Można je włączyć globalnie dla całej aplikacji lub włączyć dla określonych kontrolerów lub określonych akcji.
Aby włączyć opcje zapytań OData globalnie, wywołaj metodę EnableQuerySupport w klasie HttpConfiguration podczas uruchamiania :
public static void Register(HttpConfiguration config)
{
// ...
config.EnableQuerySupport();
// ...
}
Metoda EnableQuerySupport umożliwia globalne opcje zapytań dla każdej akcji kontrolera zwracającej typ IQueryable . Jeśli nie chcesz włączyć opcji zapytania dla całej aplikacji, możesz włączyć je dla określonych akcji kontrolera, dodając atrybut [Queryable] do metody akcji.
public class ProductsController : ApiController
{
[Queryable]
IQueryable<Product> Get() {}
}
Przykładowe zapytania
W tej sekcji przedstawiono typy zapytań, które są możliwe przy użyciu opcji zapytania OData. Aby uzyskać szczegółowe informacje o opcjach zapytania, zapoznaj się z dokumentacją OData pod adresem www.odata.org.
Aby uzyskać informacje o $expand i $select, zobacz Using $select, $expand and $value in ASP.NET Web API OData (Używanie $select, $expand i $value w usłudze OData internetowego interfejsu API ASP.NET).
Stronicowanie oparte na kliencie
W przypadku dużych zestawów jednostek klient może chcieć ograniczyć liczbę wyników. Na przykład klient może wyświetlać 10 wpisów jednocześnie z linkami "next", aby uzyskać następną stronę wyników. W tym celu klient używa opcji $top i $skip.
http://localhost/Products?$top=10&$skip=20
Opcja $top daje maksymalną liczbę wpisów do zwrócenia, a opcja $skip daje liczbę wpisów do pominięcia. Poprzedni przykład pobiera wpisy od 21 do 30.
Filtrowanie
Opcja $filter umożliwia klientowi filtrowanie wyników przez zastosowanie wyrażenia logicznego. Wyrażenia filtru są dość zaawansowane; obejmują operatory logiczne i arytmetyczne, funkcje ciągów i funkcje daty.
Zwróć wszystkie produkty z kategorią równą "Toys". | http://localhost/Products?$filter=Category eq "Toys" |
---|---|
Zwróć wszystkie produkty z ceną mniejszą niż 10. | http://localhost/Products?$filter=Price lt 10 |
Operatory logiczne: Zwracaj wszystkie produkty, w których cena = 5 i cena ><= 15. | http://localhost/Products?$filter=Price ge 5 i Price le 15 |
Funkcje ciągów: Zwracaj wszystkie produkty o nazwie "zz". | http://localhost/Products?$filter=substringof('zz',Name) |
Funkcje daty: Zwracaj wszystkie produkty z wartością ReleaseDate po 2005 roku. | http://localhost/Products?$filter=year(ReleaseDate) gt 2005 |
Sortowanie
Aby posortować wyniki, użyj filtru $orderby.
Sortuj według ceny. | http://localhost/Products?$orderby=Price |
---|---|
Sortuj według ceny w kolejności malejącej (najwyższa do najniższej). | http://localhost/Products?$orderby=Price desc |
Sortuj według kategorii, a następnie sortuj według ceny w kolejności malejącej w kategoriach. | http://localhost/odata/Products?$orderby=Category,Price desc |
stronicowanie Server-Driven
Jeśli baza danych zawiera miliony rekordów, nie chcesz wysyłać ich wszystkich w jednym ładunku. Aby temu zapobiec, serwer może ograniczyć liczbę wpisów wysyłanych w jednej odpowiedzi. Aby włączyć stronicowanie serwera, ustaw właściwość PageSize w atrybucie Queryable . Wartość jest maksymalną liczbą wpisów do zwrócenia.
[Queryable(PageSize=10)]
public IQueryable<Product> Get()
{
return products.AsQueryable();
}
Jeśli kontroler zwróci format OData, treść odpowiedzi będzie zawierać link do następnej strony danych:
{
"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"
}
Klient może użyć tego linku, aby pobrać następną stronę. Aby poznać łączną liczbę wpisów w zestawie wyników, klient może ustawić opcję zapytania $inlinecount z wartością "allpages".
http://localhost/Products?$inlinecount=allpages
Wartość "allpages" informuje serwer o dołączeniu łącznej liczby w odpowiedzi:
{
"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
]
}
Uwaga
Linki na następnej stronie i liczba śródliniowa wymagają formatu OData. Przyczyną jest to, że OData definiuje specjalne pola w treści odpowiedzi, aby przechowywać link i liczbę.
W przypadku formatów innych niż OData nadal można obsługiwać linki następnej strony i liczbę śródliniową, opakowując wyniki zapytania w obiekcie PageResult<T> . Jednak wymaga nieco więcej kodu. Oto przykład:
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());
}
Oto przykładowa odpowiedź 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
}
Ograniczanie opcji zapytania
Opcje zapytania dają klientowi dużą kontrolę nad zapytaniem uruchamianym na serwerze. W niektórych przypadkach możesz ograniczyć dostępne opcje ze względów bezpieczeństwa lub wydajności. Atrybut [Queryable] ma kilka wbudowanych właściwości dla tego. Oto kilka przykładów.
Zezwalaj tylko na $skip i $top, aby obsługiwać stronicowanie i nic innego:
[Queryable(AllowedQueryOptions=
AllowedQueryOptions.Skip | AllowedQueryOptions.Top)]
Zezwalaj na porządkowanie tylko według określonych właściwości, aby zapobiec sortowaniu właściwości, które nie są indeksowane w bazie danych:
[Queryable(AllowedOrderByProperties="Id")] // comma-separated list of properties
Zezwalaj na funkcję logiczną "eq", ale nie ma innych funkcji logicznych:
[Queryable(AllowedLogicalOperators=AllowedLogicalOperators.Equal)]
Nie zezwalaj na żadne operatory arytmetyczne:
[Queryable(AllowedArithmeticOperators=AllowedArithmeticOperators.None)]
Opcje można ograniczyć globalnie, tworząc wystąpienie QueryableAttribute i przekazując je do funkcji EnableQuerySupport :
var queryAttribute = new QueryableAttribute()
{
AllowedQueryOptions = AllowedQueryOptions.Top | AllowedQueryOptions.Skip,
MaxTop = 100
};
config.EnableQuerySupport(queryAttribute);
Wywoływanie opcji zapytania bezpośrednio
Zamiast używać atrybutu [Queryable] można wywołać opcje zapytania bezpośrednio w kontrolerze. W tym celu dodaj parametr ODataQueryOptions do metody kontrolera. W takim przypadku nie potrzebujesz atrybutu [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>;
}
Internetowy interfejs API wypełnia ciąg zapytania ODataQueryOptions z ciągu zapytania identyfikatora URI. Aby zastosować zapytanie, przekaż zapytanie IQueryable do metody ApplyTo . Metoda zwraca kolejną metodę IQueryable.
W przypadku zaawansowanych scenariuszy, jeśli nie masz dostawcy zapytań Z możliwością zapytania IQueryable , możesz sprawdzić protokół ODataQueryOptions i przetłumaczyć opcje zapytania na inny formularz. (Na przykład zobacz wpis w blogu RaghuRam Nadiminti tłumaczenia zapytań OData na HQL)
Sprawdzanie poprawności zapytania
Atrybut [Queryable] weryfikuje zapytanie przed jego wykonaniem. Krok weryfikacji jest wykonywany w metodzie QueryableAttribute.ValidateQuery . Można również dostosować proces weryfikacji.
Zobacz również wskazówki dotyczące zabezpieczeń OData.
Najpierw przesłoń jedną z klas modułu sprawdzania poprawności, która jest zdefiniowana w przestrzeni nazw Web.Http.OData.Query.Validators . Na przykład następująca klasa modułu sprawdzania poprawności wyłącza opcję "desc" dla opcji $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);
}
}
Podklasa atrybutu [Queryable], aby zastąpić metodę 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);
}
}
Następnie ustaw atrybut niestandardowy globalnie lub na kontroler:
// Globally:
config.EnableQuerySupport(new MyQueryableAttribute());
// Per controller:
public class ValuesController : ApiController
{
[MyQueryable]
public IQueryable<Product> Get()
{
return products.AsQueryable();
}
}
Jeśli używasz funkcji ODataQueryOptions bezpośrednio, ustaw moduł sprawdzania poprawności w opcjach:
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>;
}