Actions And Functions In OData V4 Using ASP.NET Web API
Introduction
Sometimes we need to perform an operation which is not directly related to entity. The actions and functions are the way to perform server side operations which are not easily defined as CRUD operations on entities.
We can define actions and functions to OData V4 endpoint with Web API. Both actions and functions are able to return data. The following are some use of actions.
- Sending the data which are not related to entity
- Sending several entities at once
- Complex transactions
- Update few property of entity
The functions are very useful for returning data which are not directly related to entity or collection. An action and function is able to target a single entity or collection of entities. This is called binding in term of OData terminology. We can also have unbound action or function which can be referred to as static operation on service.
Action
Actions will provide a way to inject behaviors into the current Web API action without affecting current data model.
Suppose I have two tables, Employee and EmployeeRating. The relation between the entities is shown below.
The following is the class definition and Model definition,
- namespace WebAPITest {
- using System.ComponentModel.DataAnnotations;
- using System.ComponentModel.DataAnnotations.Schema;
- [Table("Employee")]
- publicpartialclassEmployee {
- publicint Id {
- get;
- set;
- }
- [Required]
- [StringLength(50)]
- publicstring Name {
- get;
- set;
- }
- publicint DepartmentId {
- get;
- set;
- }
- [Column(TypeName = "money")]
- publicdecimal ? Salary {
- get;
- set;
- }
- [StringLength(255)]
- publicstring EmailAddress {
- get;
- set;
- }
- [StringLength(50)]
- publicstring PhoneNumber {
- get;
- set;
- }
- publicstring Address {
- get;
- set;
- }
- publicvirtualEmployeeRating EmployeeRating {
- get;
- set;
- }
- }
- [Table("EmployeeRating")]
- publicpartialclassEmployeeRating {
- [Key]
- [DatabaseGenerated(DatabaseGeneratedOption.None)]
- publicint EmployeeId {
- get;
- set;
- }
- publicdouble Rating {
- get;
- set;
- }
- publicvirtualEmployee Employee {
- get;
- set;
- }
- }
- }
- namespace WebAPITest {
- using System.Data.Entity;
- publicpartialclassEntityModel: DbContext {
- public EntityModel(): base("name=EntityModel") {}
- publicvirtualDbSet < Employee > Employees {
- get;
- set;
- }
- publicvirtualDbSet < EmployeeRating > EmployeeRatings {
- get;
- set;
- }
- protectedoverridevoid OnModelCreating(DbModelBuilder modelBuilder) {
- modelBuilder.Entity < Employee > ()
- .Property(e => e.Name)
- .IsUnicode(false);
- modelBuilder.Entity < Employee > ()
- .Property(e => e.Salary)
- .HasPrecision(19, 4);
- modelBuilder.Entity < Employee > ()
- .HasOptional(e => e.EmployeeRating)
- .WithRequired(e => e.Employee);
- }
- }
- }
In order to declare the action, we need to modify Web API configuration to add the action parameter to EDM (entity data model).
The EntityTypeConfiguration class has method called "Action" is used to add an action to the EDM (entity data model). The Parameter method is used to specify a typed parameter for defined action.
- ODataConventionModelBuilder builder = newODataConventionModelBuilder();
- builder.Namespace = "WebAPITest";
- builder.ContainerName = "DefaultContainer";
- builder.EntitySet < EmployeeRating > ("EmployeeRating");
- builder.EntitySet < Employee > ("Employee");
- builder.EntityType < Employee > ()
- .Action("Rate")
- .Parameter < double > ("Rating");
- var edmModel = builder.GetEdmModel();
Definition of Action in OData controller
To enable "Rate" action, we need to add the following method to the EmployeeControler.
- [HttpPost]
- publicasyncTask<IHttpActionResult> Rate([FromODataUri] int key, ODataActionParameters parameters)
- {
- double rating = (double)parameters["Rating"];
- //Do Code…
- return StatusCode(HttpStatusCode.NoContent);
- }
URI - http://localhost:24367/Employee(1)/WebAPITest.Rate
The request body contains the parameter's value in form of JSON. Web API is automatically convert it in to ODataActionParameters object. This object is just dictionary type. This object can be used to retrieve value in controller.
When I am posting data using said URI, I am getting 404 –Not Found error. Here DOT (.) is present in URI; it will create issue with IIS and returns 404 error. We can resolve this issue by adding the following setting in web.config file.
- <system.webServer>
- <handlers>
- <clear/>
- <addname="ExtensionlessUrlHandler-Integrated-4.0" path="/*" verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
- </handlers>
- </system.webServer>
Functions
There are two types of function supported by OData
- Bound function
The first step to add OData function to the controller is to modify the web API configuration. By using "function" method of EntityTypeConfiguration class, we can define the function and using "Returns" method we can define the return type of the function. The function can invoked by the client using GET request.Controller function definition as following-- ODataConventionModelBuilder builder = newODataConventionModelBuilder();
- builder.Namespace = "WebAPITest";
- builder.ContainerName = "DefaultContainer";
- builder.EntitySet<EmployeeRating>("EmployeeRating");
- builder.EntitySet<Employee>("Employee");
- builder.EntityType<Employee>().Collection
- .Function("GetHighestRating")
- .Returns<double>();
Here URI pattern for the function is same Action I.e. namespace.functionname- [HttpGet]
- publicIHttpActionResult GetHighestRating()
- {
- var rating = context.EmployeeRatings.Max(x => x.Rating);
- return Ok(rating);
- }
URL - http://localhost:24367/Employee/WebAPITest.GetHighestRating
Output
In the above example, function was bound to the collection. It is normally refer as Bound function. - Unbound function
Unbound functions are normally not bound to any specific service and they are called as static operation on service.
Following code change needs to be done in web API configuration-In the following function, I have passed employee id as parameter and function returns its rating. This function does not depends on the Employee type, so we can create this function in the separate controller. The [ODataRoute] attribute used to define the URI template for the function.- ODataConventionModelBuilder builder = newODataConventionModelBuilder();
- builder.Namespace = "WebAPITest";
- builder.ContainerName = "DefaultContainer";
- builder.EntitySet<EmployeeRating>("EmployeeRating");
- builder.EntitySet<Employee>("Employee");
- builder.Function("GetEmployeeRating")
- .Returns<double>()
- .Parameter<int>("EmployeeId");
URL - http://localhost:24367/GetEmployeeRating(EmployeeId=1)- [HttpGet]
- [ODataRoute("GetEmployeeRating(EmployeeId={employeeId})")]
- publicIHttpActionResult GetEmployeeRating(int employeeId)
- {
- var rating = context.Employees.Where(f => f.Id == employeeId).FirstOrDefault().EmployeeRating.Rating;
- return Ok(rating);
- }
Output
Summary
Web API 2 supports the Action and function with OData and it is very helpful to perform the operation which is not directly related to entity.
0 Comments