問題 在 Web API 中使用 OData Function 和 Action。 解決方案 可以通過 ODataModelBuilder,使用 OData 構建 ASP.NET Web API, EntityCollectionConfiguration,EnityTypeConfiguratio ...
問題
在 Web API 中使用 OData Function 和 Action。
解決方案
可以通過 ODataModelBuilder,使用 OData 構建 ASP.NET Web API, EntityCollectionConfiguration,EnityTypeConfiguration 類中提供的一系列 Function 和 Action 來自定義 Function 和 Action。
當我們都建自己的 ODataModelBuilder 的時候,可以指定 Function 或 Action 名稱並定義他們的輸入參數。如清單12-12 所示。
清單 12-12.
1 var ODataBuilder = new ODataConventionModelBuilder(); 2 3 ODataBuilder.EntitySet<Player>("Players"); 4 5 var player = ODataBuilder.EntityType<Player>(); 6 7 // Function – 讀取數據 8 9 player.Function("PercentageOfAllGoals").Returns<double>(); 10 11 // Action – 請求操作 12 13 player.Action("TradePlayer").Parameter<string>("NewTeam");
Controller Action 和 OData Function/Action 之間是通過命名的約定建立關聯,因此,我們需要在 OData 的 controller 中添加合適的 Action。
工作原理
ASP.NET WEB API 從 2.2 版本開始支持 OData,而且,已經成為 OData 3.0 規範的一部分。另一方面,在之前 Web API 中 OData 的 Action 也是可以使用的。
我們是可以以 Web API Action 的形式定義 OData Function/Action 同時暴露給客戶端訪問。
使用 Action 或 Function 的主要優勢是,我們可以將查詢的責任轉交給伺服器,尤其是複雜查詢的時候,可以減輕客戶端的不必要的麻煩。
OData 的 Action 和 Function 是有點不一樣的;他們都是在規範中被定義的“一組可以被執或可以作為服務或可以作為資源的操作的擴展”。主要的不同是
- Function:可以能沒有什麼結果,但是必須有返回值
- Action:可以對伺服器產生影響,但是不能有返回值
- 另外,Function 可以在 $filter 中被調用
在實現了 OData 的 ASP.NET WEB API 中,Action 和 Function 是連同 OData 約定一起被定義,他們是通過 ODataConventionModelBuilder 的實例定義。WEB API OData 的構建支持三種類型(級別)的操作:
- 服務 Action/Function:ODataModelBuilder 直接定義
- 集合 Action/Function:EntityCollectionConfiguration 直接定義
- 實體 Action/Function:EntityTypeConfiguration直接定義
代碼演示
如清單 12-13 所示,一個簡單的數據集合,為了演示的方面,在 Controller 中通過記憶體進行數據的操作,還有一個 Player 的 DTO 的類。
我們就使用這些代碼模擬 OData 的三種類型:服務,集合,實體綁定。演示中主要關註在 Function 上,但是, Action 的定義和使用也是幾乎一樣的。也就是說,在所有使用 Function 聲明的方法的地方,都換成 Action 聲明的方法是沒有毛病的。
清單 12-13. 記憶體數據和實體模型
1 public class Player 2 { 3 public int Id { get; set; } 4 5 public string Name { get; set; } 6 7 public string Team { get; set; } 8 9 public SkaterStat Stats { get; set; } 10 } 11 12 public class SkaterStat 13 { 14 public int Goals { get; set; } 15 16 public int Assists { get; set; } 17 18 public int GamesPlayed { get; set; } 19 } 20 21 public class PlayersController : ODataController 22 { 23 private static List<Player> _players = new List<Player> 24 { 25 new Player 26 { 27 Id = 1, 28 Name = "Filip", 29 Team = "Whales", 30 Stats = new SkaterStat 31 { 32 GamesPlayed = 82, 33 Goals = 37, 34 Assists = 43 35 } 36 }, 37 new Player 38 { 39 Id = 2, 40 Name = "Felix", 41 Team = "Whales", 42 Stats = new SkaterStat 43 { 44 GamesPlayed = 80, 45 Goals = 30, 46 Assists = 31 47 } 48 new Player 49 { 50 Id = 3, 51 Name = "Luiz", 52 Team = "Dolphins", 53 Stats = new SkaterStat 54 { 55 GamesPlayed = 78, 56 Goals = 20, 57 Assists = 30 58 } 59 }, 60 new Player 61 { 62 Id = 4, 63 Name = "Terry", 64 Team = "Dolphins", 65 Stats = new SkaterStat 66 { 67 GamesPlayed = 58, 68 Goals = 19, 69 Assists = 30 70 } 71 } 72 }; 73 }
前面提到的 Function 方法,來自於 ODataModelBuilder;EntityCollectionConfiguration,EntityTypeConfiguration,都返回一個 FunctionConfiguration 的實例,我們就是用它來配置我們的 Function,例如,在 $filter 中是否支持 Function,接收什麼樣的參數,應該返回什麼。例如,這個演示的 Startup 類中定義了 ODataModelBuilder的 三個 OData Function 類型和一個實體類型,如清單 12-14 所示。
清單 12-14 OData Function 服務、集合、實體
1 public class Startup 2 { 3 4 public void Configuration(IAppBuilder builder) 5 { 6 7 var ODataBuilder = new ODataConventionModelBuilder(); 8 9 ODataBuilder.EntitySet<Player>("Players"); 10 11 var player = ODataBuilder.EntityType<Player>(); 12 13 /* 集合 Function */ 14 15 player.Collection.Function("TopPpg").ReturnsCollection<Player>(); 16 17 /* 實體 Function */ 18 19 player.Function("PercentageOfAllGoals").Returns<double>(); 20 21 /* 服務 Function */ 22 23 var serviceFunc = ODataBuilder.Function("TotalTeamPoints"); 24 25 serviceFunc.Returns<int>().Parameter<string>("team"); 26 27 serviceFunc.IncludeInServiceDocument = true; 28 29 var edm = ODataBuilder.GetEdmModel(); 30 31 var config = new HttpConfiguration(); 32 33 config.MapODataServiceRoute("Default OData", "OData", edm); 34 35 builder.UseWebApi(config); 36 37 } 38 39 }
TopPpg 是一個集合 Function,他將返回每場比賽最高分(得分+助攻)比例 player 的集合。PercentageOfAllGoals 是一個實體 Function,返回每場比賽給定參賽者相對所有得分的分數比例。這個 Function 需要客戶端傳一個 key(player ID),但是,需要註意的是,這個 key 是實體對象的 Id,不需要在 Function 中特殊指明。最後,TotalTeamPoints 是無限制的服務 Function,也就是說,不是特指某一個 player,而是傳入一個隊名最為參數,同時返回整個隊內所有隊員分數(得分+助攻)的總和。另外,TotalTeamPoints 也會包含在文檔服務中,/OData/$metadata ,作為 Function 入口。
這些 Function 在 Action 中都是使用的 LINQ 表達式。無限制服務的 Function 使用了 ODataRoute 屬性,因為預設的 EMD 驅動路由約定不能完成整個功能。
12-15/ 使用 OData Function 來暴露 Controller 的 Action
1 [HttpGet] 2 public IEnumerable<Player> TopPpg() 3 { 4 var result = _players.OrderByDescending(x => (double)(x.Stats.Goals + x.Stats.Assists) / (double)x.Stats.GamesPlayed).Take(3); 5 return (result); 6 } 7 8 9 [HttpGet] 10 public IHttpActionResult PercentageOfAllGoals(int key) 11 { 12 var player = _players.FirstOrDefault(x => x.Id == key); 13 if (player == null) 14 return (NotFound()); 15 var result = (double)player.Stats.Goals / (double)_players.Sum(x => x.Stats.Goals) * 100; 16 return (Ok(result)); 17 } 18 19 20 [HttpGet] 21 [ODataRoute("TotalTeamPoints(team={team})")] 22 public int TotalTeamPoints([FromODataUri] string team) 23 { 24 var result = _players.Where(x => string.Equals(x.Team, team, StringComparison. 25 InvariantCultureIgnoreCase)) 26 .Sum(x => x.Stats.Goals + x.Stats.Assists); 27 return (result); 28 }
在這些地方,可以在 URI 中使用 Function 名稱來調用他們。根據規範,調用 OData Function 的時候需要使用括弧:
- /OData/Players/Default.TopPpg()
- /OData/Players(1)/Default.PercentageOfAllGoals()
- /OData/TotalTeamPoints(team='Whales')
關於 OData 在 ASP.NET WEB API 中的介紹就此告一段落,接下來,一段時間將介紹關於 Route 的東西。