問題 Web API 怎麼支持通用的 OData 系統查詢項,例如 $select 或 $filter。 解決方案 為了在 Web API 中啟用查詢項,我們需要在 Action 上使用 EnableQueryAttribute。 如果 Action 沒有返回集合,而是返回單個對象的實例,調用端仍然 ...
問題
Web API 怎麼支持通用的 OData 系統查詢項,例如 $select 或 $filter。
解決方案
為了在 Web API 中啟用查詢項,我們需要在 Action 上使用 EnableQueryAttribute。
如果 Action 沒有返回集合,而是返回單個對象的實例,調用端仍然可以使用 $expand 和 $select 兩個查詢語句,要達到這個目的,我們必須將返回對象包裝在 SingleResult<T> 中。集合和單個對象實例作為返回值的例子如訂單 12-7 所示
清單 12-7. 在兩個路由上啟用查詢語句
1 public class PlayersController : ODataController 2 3 { 4 5 private readonly PlayersContext playersDbContext = new PlayersContext(); 6 7 [EnableQuery] 8 9 public IQueryable<Player> GetAllPlayers() 10 11 { 12 13 Return playersDbContext; 14 15 } 16 17 [EnableQuery] 18 19 public SingleResult<Player> GetSinglePlayers(int key) 20 21 { 22 23 return SingleResult.Create(playersDbContext.Where(x => x.Id == key).AsQueryable()); 24 25 } 26 27 }
工作原理
OData 查詢選項是被定義在 OData 規範中,如,查詢字元串的參數控制的是返回資源的數量和順序。ASP.NET Web API 幾乎支持所有的標準查詢項:
- $expand:允許響應給客戶端的信息包含關聯資源(導航屬性)
- $select:限制返回的屬性
- $filter:過濾 Api 暴露出來的資源
- $count:獲取集合中實體的總數
- $orderby:指定集合的排序 key
- $skip:獲取集合跳過的數量
- $top:限制集合返回集合的數量
- $format:請求特定的響應格式
唯一不支持的是 $search 查詢參數。
小提示 可以查看 OData 4.0 支持的完整的文檔地址:http://docs.oasis-open.org/OData/new-in-OData/v4.0/cn01/new-in-OData-v4.0-cn01.html
查詢項是在 ASP.NET Web API 中被 ODataQueryOption 類所描述的。EnableQueryAttribute 的工作方式實際是非常簡單的。因為他是 Action 的 Filter,在 OnActionExecuted 方法中會獲取 Action 的響應,並轉換 HttpContent 為ObjectContent<T>。然後根據 HttpRequest 基於客戶端的參數構造 ODataQueryOptions 實例,並返回相應的響應給客戶端。如果響應是集合的話,可以響應客戶端所有的查詢項。如果響應不是集合,就會通過 ObjectContent 包裝成 SingleResult<T>。這些時候,$expand 和 $select 都是可用的。
不使用 EnableQueryAttribute,也可以使用 ODataQueryOptions,他是可以接受 Action 的參數的。這種方式是通過自定義 HttpParameterBinding 類型的 ODataQueryParameterBinding 來支持的。我們也可以使用 ODataQueryOptions 實例中的信息手動執行相關查詢。
在 Web API 中我們不需要完全掌握 OData 就可以體會到查詢項給我們帶來的便利。就算不是基於 ODataCotrollers,只要 Action 上使用了 EnableQueryAttribute 屬性標簽,我們還是可以在 Web API 上使用一些基礎查詢項,例如,$top,$skip,$select。
代碼演示
在 Controller 的 Action 上使用 EnableQueryAttribute,這樣的請求就可以使用 OData 查詢項,如清單 12-7
- Host/Plays(1)$select=Name,Team:使用 Player 實體的 Id 進行過濾,僅僅返回 Name 和 Team 兩個屬性。這個例子的響應,如清單 12-8.
清單 12-8. 從 OData 的 Web API 查詢響應例子
{
"@OData.context":"http://localhost:43539/OData/$metadata#Players(Name,Team)/$entity","Name":"Name1","Team":"Team"
}
- Host/Players?skip=1&$top=2:忽略集合中第一個實體,然後再剩下的實體中獲取兩個返回(可以理解為分頁的第二頁數據,每頁大小是 2)。具體響應,如清單 12-9 所示。
清單 12-9. 從 OData 的 Web API 查詢響應例子
{
"@OData.context":"http://localhost:43539/OData/$metadata#Players","value":[
{
"Id":1,"Name":"Name1","Team":"Team"
},{
"Id":2,"Name":"Name11","Team":"Team"
}
]
}
- Host/Players?$format=application/json;OData.metadata=full&$filter=Team%20eq%20%27Team2%27:請求條件你為 Team 屬性值為 Whales,以 json 格式返回 ,包含 OData 元數據的所有信息,包含類型和導航鏈接。具體響應結果,如清單 12-10 所示
{
"@OData.context":"http://localhost:43539/OData/$metadata#Players","value":[
{
"@OData.type":"#BoiledCode.WebApi.Recipe.ODataDemo.Models.Player","@OData.id":"http://localhost:43539/OData/Players(3)","@OData.editLink":"http://localhost:43539/OData/Players(3)","Id":3,"Name":"Name2","Team":"Team2"
},{
"@OData.type":"#BoiledCode.WebApi.Recipe.ODataDemo.Models.Player","@OData.id":"http://localhost:43539/OData/Players(4)","@OData.editLink":"http://localhost:43539/OData/Players(4)","Id":4,"Name":"Name21","Team":"Team2"
}
]
}
就像上面提到的,我們可以通過在 Action 方法中使用 ODataQueryOptions 參數,來手動的實現詢項。ODataQueryParameterBinding 會幫我完成將對象傳入到 Action 的操作,這樣我們就可以 Action 內部提取 OData 相關的查詢項了。
清單 12-11. 在 Action 中使用 ODataQueryOptions
1 public IQueryable<Player> GetAllPlayers(ODataQueryOptions queryOptions) 2 3 { 4 5 // 客戶端傳入的 top 和 skip 6 7 var filtered = db.Players.Skip(queryOptions.Skip.Value); 8 9 if (queryOptions.Top.Value > 0) 10 11 { 12 13 filtered = filtered.Take(queryOptions.Top.Value); 14 15 } 16 17 return filtered.AsQueryable(); 18 19 }