文章出處

問題

在 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 的東西。

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()