[Boiled ASP. NET Web API2 methodology] (12-2) Functions and actions supported by OData, api2odata
Problem
Use OData functions and actions in Web APIs.
Solution
You can use ODataModelBuilder to construct a series of functions and actions provided in ASP. NET Web APIs, EntityCollectionConfiguration, and EnityTypeConfiguration to customize functions and actions.
When we create our own ODataModelBuilder, we can specify the Function or Action name and define their input parameters. See list 12-12.
List 12-12.
1 var ODataBuilder = new ODataConventionModelBuilder (); 2 3 ODataBuilder. entitySet <Player> ("Players"); 4 5 var player = ODataBuilder. entityType <Player> (); 6 7 // Function-read data 8 9 player. function ("PercentageOfAllGoals "). returns <double> (); 10 11 // Action-request operation 12 13 player. action ("TradePlayer "). parameter <string> ("NewTeam ");
The Controller Action and OData Function/Action are associated through naming conventions. Therefore, we need to add appropriate actions to the OData controller.
Working Principle
ASP. net web APIs support OData from Version 2.2 and have become part of OData 3.0 specifications. On the other hand, OData actions can also be used in previous Web APIs.
We can define OData Function/Action in the form of Web API Action and expose it to the client for access at the same time.
The main advantage of using Action or Function is that we can transfer the query responsibility to the server, especially in complex queries, which can reduce unnecessary troubles on the client.
The actions and functions of OData are a little different. They are defined in the specification as "a group of extensions that can be executed, can be used as services, or can be used as resources ". The main difference is that
- Function: can have no results, but must have a return value.
- Action: can affect the server, but cannot return values.
- In addition, the Function can be called in $ filter.
In ASP. net web APIs that implement OData, actions and functions are defined together with OData conventions. They are defined through ODataConventionModelBuilder instances. The construction of web api OData supports three types of operations:
- Service Action/Function: directly defined by ODataModelBuilder
- Set Action/Function: EntityCollectionConfiguration directly defined
- Entity Action/Function: EntityTypeConfiguration directly defined
Code demo
As shown in listing 12-13, there is a simple data set. for demonstration purposes, there is also a DTO class of Player in the Controller for data operations through memory.
We use this code to simulate three types of OData: service, set, and object binding. The demo mainly focuses on functions. However, the definition and use of actions are almost the same. That is to say, there is no problem in changing all the methods using the Function declaration to the Action Declaration method.
Listing 12-13. Memory Data and entity model
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 SkaterStat13 {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 : ODataController22 {23 private static List<Player> _players = new List<Player>24 {25 new Player26 {27 Id = 1,28 Name = "Filip",29 Team = "Whales",30 Stats = new SkaterStat31 {32 GamesPlayed = 82,33 Goals = 37,34 Assists = 4335 }36 },37 new Player38 {39 Id = 2,40 Name = "Felix",41 Team = "Whales",42 Stats = new SkaterStat43 {44 GamesPlayed = 80,45 Goals = 30,46 Assists = 3147 }48 new Player49 {50 Id = 3,51 Name = "Luiz",52 Team = "Dolphins",53 Stats = new SkaterStat54 {55 GamesPlayed = 78,56 Goals = 20,57 Assists = 3058 }59 },60 new Player61 {62 Id = 4,63 Name = "Terry",64 Team = "Dolphins",65 Stats = new SkaterStat66 {67 GamesPlayed = 58,68 Goals = 19,69 Assists = 3070 }71 }72 };73 }
The Function method mentioned above comes from ODataModelBuilder; EntityCollectionConfiguration, EntityTypeConfiguration, and returns a FunctionConfiguration instance. We use it to configure our Function. For example, if Function is supported in $ filter, what parameters should be received and returned. For example, the Startup class in this demo defines three OData Function types and one object type of ODataModelBuilder, as shown in listing 12-14.
Listing 12-14 OData Function service, set, and Entity
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/* Set Function */14 15 player. collection. function ("TopPpg "). returnsCollection <Player> (); 16 17/* entity Function */18 19 player. function ("PercentageOfAllGoals "). returns <double> (); 20 21/* service 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 is a set Function that returns the set of players with the highest score (score + assist) ratio in each game. PercentageOfAllGoals is an entity Function that returns the percentage of scores given to all contestants in each game. This Function requires the client to upload a key (player ID). However, note that this key is the Id of the object and does not need to be specified in the Function. Finally, TotalTeamPoints is an unrestricted service Function. In other words, instead of a player, TotalTeamPoints transfers the most parameter to a team name and returns the total score (score + assist) of all the team members. In addition, TotalTeamPoints will also be included in the document service,/OData/$ metadata, as the Function entry.
These functions are all using the LINQ expression in Action. The Function of unrestricted Service uses the ODataRoute attribute, because the default EMD-driven routing Convention cannot complete the entire Function.
12-15/Use OData Function to expose the 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 }
In these cases, you can use the Function name in the URI to call them. According to the specifications, brackets must be used to call the OData Function:
- /OData/Players/Default. TopPpg ()
- /OData/Players (1)/Default. PercentageOfAllGoals ()
- /OData/TotalTeamPoints (team = 'whales ')
The introduction of OData in ASP. net web APIs has come to an end. Next, we will introduce Route in some time.