ASP.NET Web API 2 中支援 OData 查詢選項
作者:Mike Wasson
此概觀與程式碼範例示範 ASP.NET 4.x ASP.NET Web API 2 中支援的 OData 查詢選項。
OData 會定義可用來修改 OData 查詢的參數。 用戶端會在要求 URI 的查詢字串中傳送這些參數。 例如,若要排序結果,用戶端會使用 $orderby 參數:
http://localhost/Products?$orderby=Name
OData 規格會呼叫這些參數 查詢選項。 您可以為專案中的任何 Web API 控制器啟用 OData 查詢選項, 控制器不需要是 OData 端點。 這可讓您輕鬆地將篩選和排序等功能新增至任何 Web API 應用程式。
啟用查詢選項之前,請閱讀 OData 安全性指引主題。
啟用 OData 查詢選項
Web API 支援下列 OData 查詢選項:
選項 | 描述 |
---|---|
$expand | 內嵌展開相關的實體。 |
$filter | 根據布林條件篩選結果。 |
$inlinecount | 告知伺服器在回應中包含相符實體的總計數。 (適用于伺服器端分頁.) |
$orderby | 排序結果。 |
$select | 選取要包含在回應中的屬性。 |
$skip | 略過前 n 個結果。 |
$top | 只傳回結果的前 n 個。 |
若要使用 OData 查詢選項,您必須明確地加以啟用。 您可以全域為整個應用程式啟用它們,或針對特定控制器或特定動作啟用它們。
若要全域啟用 OData 查詢選項,請在啟動時呼叫HttpConfiguration類別上的EnableQuerySupport:
public static void Register(HttpConfiguration config)
{
// ...
config.EnableQuerySupport();
// ...
}
EnableQuerySupport方法會全域啟用傳回IQueryable類型之任何控制器動作的查詢選項。 如果您不想讓整個應用程式啟用查詢選項,您可以將 [Queryable] 屬性新增至動作方法,以針對特定控制器動作啟用這些選項。
public class ProductsController : ApiController
{
[Queryable]
IQueryable<Product> Get() {}
}
查詢範例
本節說明可使用 OData 查詢選項的查詢類型。 如需查詢選項的特定詳細資料,請參閱 www.odata.org的 OData 檔。
如需$expand和$select的相關資訊,請參閱在 ASP.NET Web API OData 中使用$select、$expand和$value。
用戶端驅動分頁
對於大型實體集,用戶端可能會想要限制結果數目。 例如,用戶端一次可能會顯示 10 個專案,其中含有「下一個」連結以取得下一頁的結果。 若要這樣做,用戶端會使用$top和$skip選項。
http://localhost/Products?$top=10&$skip=20
$top選項會提供要傳回的專案數目上限,而 $skip 選項會提供要略過的專案數。 上述範例會擷取專案 21 到 30。
篩選
$filter選項可讓用戶端套用布林運算式來篩選結果。 篩選運算式相當強大;它們包含邏輯和算術運算子、字串函數和日期函式。
傳回類別等於 「Toys」 的所有產品。 | http://localhost/Products?$filter=Category eq 'Toys' |
---|---|
傳回價格小於 10 的所有產品。 | http://localhost/Products?$filter=Price lt 10 |
邏輯運算子:傳回所有價格 = 5 且 price >< = 15 的產品。 | http://localhost/Products?$filter=Price ge 5 和 Price le 15 |
字串函式:傳回名稱中有 「zz」 的所有產品。 | http://localhost/Products?$filter=substringof('zz',Name) |
日期函式:傳回 2005 年之後具有 ReleaseDate 的所有產品。 | 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分頁
如果您的資料庫包含數百萬筆記錄,您不想將所有記錄全部傳送到一個承載中。 若要避免這種情況,伺服器可以限制它在單一回應中傳送的專案數目。 若要啟用伺服器分頁,請在Queryable屬性中設定PageSize屬性。 此值是要傳回的專案數上限。
[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"
}
用戶端可以使用此連結來擷取下一頁。 若要瞭解結果集中的專案總數,用戶端可以使用 「allpages」 值來設定$inlinecount查詢選項。
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);
直接叫用查詢選項
您可以直接在控制器中叫用查詢選項,而不是使用 [Queryable] 屬性。 若要這樣做,請將 ODataQueryOptions 參數新增至控制器方法。 在此情況下,您不需要 [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>;
}
Web API 會從 URI 查詢字串填入 ODataQueryOptions 。 若要套用查詢,請將 IQueryable 傳遞至 ApplyTo 方法。 方法會傳回另一個 IQueryable。
針對進階案例,如果您沒有 IQueryable 查詢提供者,您可以檢查 ODataQueryOptions ,並將查詢選項轉譯為另一種表單。 (例如,請參閱 RaghuRam Nadiminti 的部落格文章 將 OData 查詢轉譯為 HQL)
查詢驗證
[Queryable]屬性會先驗證查詢,再執行查詢。 驗證步驟是在 QueryableAttribute.ValidateQuery 方法中執行。 您也可以自訂驗證程式。
另請參閱 OData 安全性指引。
首先,覆寫 Web.Http.OData.Query.Validators 命名空間中定義的其中一個驗證程式類別。 例如,下列驗證程式類別會停用 $orderby 選項的 'desc' 選項。
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>;
}
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應