共用方式為


支援 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</a> 的 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 選項允許用戶端透過套用布林運算式來篩選結果。 篩選運算式非常強大;它們包括邏輯和算術運算子、字串函式和日期函式。

返回所有類別為「玩具」的產品。 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>;
}