支援 ASP.NET Web API 2 中的 OData 查詢選項
演講者:Mike Wasson
此概述與程式碼範例示範了 ASP.NET Web API 2 for ASP.NET 4.x 中支援的 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
有關 $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 選項允許用戶端透過套用布林運算式來篩選結果。 篩選運算式非常強大;它們包括邏輯和算術運算子、字串函式和日期函式。
返回所有類別為「玩具」的產品。 | http://localhost/Products?$filter=Category eq 'Toys' |
---|---|
退回所有價格低於 10 的產品。 | http://localhost/Products?$filter=Price lt 10 |
邏輯運算符:傳回價格 >= 5 和價格 <= 15 的所有產品。 | http://localhost/Products?$filter=Price ge 5 and 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 |
伺服器驅動的分頁
如果您的資料庫包含數百萬筆記錄,您不希望將它們全部傳送到一個有效負載中。 為了防止這種情況,伺服器可以限制它在單一回應中傳送的項目數。 若要啟用伺服器分頁,請在 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"
}
用戶端可以使用此連結取得下一頁。 若要了解結果集中的項目總數,用戶端可以將 $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);
直接呼叫查詢選項
您可以直接在控制器中呼叫查詢選項,而不是使用 [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>;
}