Create a Singleton in OData v4 Using Web API 2.2
by Zoe Luo
Traditionally, an entity could only be accessed if it were encapsulated inside an entity set. But OData v4 provides two additional options, Singleton and Containment, both of which WebAPI 2.2 supports.
This article shows how to define a singleton in an OData endpoint in Web API 2.2. For information on what a singleton is and how you can benefit from using it, see Using a singleton to define your special entity. To create an OData V4 endpoint in Web API, see Create an OData v4 Endpoint Using ASP.NET Web API 2.2.
We’ll create a singleton in your Web API project using the following data model:
A singleton named Umbrella
will be defined based on type Company
, and an entity set named Employees
will be defined based on type Employee
.
The solution used in this tutorial can be downloaded from CodePlex.
Define the data model
Define the CLR types.
[!code-csharpMain]1: /// <summary>
2: /// Present the EntityType "Employee"
3: /// </summary>
4: public class Employee
5: {
6: public int ID { get; set; }
7: public string Name { get; set; }
8:
9: [Singleton]
10: public Company Company { get; set; }
11: }
12: /// <summary>
13: /// Present company category, which is an enum type
14: /// </summary>
15: public enum CompanyCategory
16: {
17: IT = 0,
18: Communication = 1,
19: Electronics = 2,
20: Others = 3
21: }
22: /// <summary>
23: /// Present the EntityType "Company"
24: /// </summary>
25: public class Company
26: {
27: public int ID { get; set; }
28: public string Name { get; set; }
29: public Int64 Revenue { get; set; }
30: public CompanyCategory Category { get; set; }
31: public List<Employee> Employees { get; set; }
32: }
Generate the EDM model based on the CLR types.
[!code-csharpMain]
1: public static IEdmModel GetEdmModel()
2: {
3: ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
4: builder.EntitySet<Employee>("Employees"); builder.Singleton<Company>("Umbrella");
5: builder.Namespace = typeof(Company).Namespace;
6: return builder.GetEdmModel();
7: }
Here,
builder.Singleton<Company>("Umbrella")
tells the model builder to create a singleton namedUmbrella
in the EDM model.The generated metadata will look like the following:
[!code-xmlMain]
1: <EntityContainer Name="Container">
2: <EntitySet Name="Employees" EntityType="ODataSingletonSample.Employee">
3: <NavigationPropertyBinding Path="Company" Target="Umbrella"/>
4: </EntitySet>
5: <Singleton Name="Umbrella" Type="ODataSingletonSample.Company">
6: <NavigationPropertyBinding Path="Employees" Target="Employees"/>
7: </Singleton>
8: </EntityContainer>
From the metadata we can see that the navigation property
Company
in theEmployees
entity set is bound to the singletonUmbrella
. The binding is done automatically byODataConventionModelBuilder
, since onlyUmbrella
has theCompany
type. If there is any ambiguity in the model, you can useHasSingletonBinding
to explicitly bind a navigation property to a singleton;HasSingletonBinding
has the same effect as using theSingleton
attribute in the CLR type definition:[!code-csharpMain]
1: EntitySetConfiguration<Employee> employeesConfiguration =
2: builder.EntitySet<Employee>("Employees");
3: employeesConfiguration.HasSingletonBinding(c => c.Company, "Umbrella");
Define the singleton controller
Like the EntitySet controller, the singleton controller inherits from ODataController
, and the singleton controller name should be [singletonName]Controller
.
[!code-csharpMain]
1: public class UmbrellaController : ODataController
2: {
3: public static Company Umbrella;
4: static UmbrellaController()
5: {
6: InitData();
7: }
8: private static void InitData()
9: {
10: Umbrella = new Company()
11: {
12: ID = 1,
13: Name = "Umbrella",
14: Revenue = 1000,
15: Category = CompanyCategory.Communication,
16: Employees = new List<Employee>()
17: };
18: }
19: }
In order to handle different kinds of requests, actions are required to be pre-defined in the controller. Attribute routing is enabled by default in WebApi 2.2. For example, to define an action to handle querying Revenue
from Company
using attribute routing, use the following:
[!code-csharpMain]
1: [ODataRoute("Umbrella/Revenue")]
2: public IHttpActionResult GetCompanyRevenue()
3: {
4: return Ok(Umbrella.Revenue);
5: }
If you are not willing to define attributes for each action, just define your actions following OData Routing Conventions. Since a key is not required for querying a singleton, the actions defined in the singleton controller are slightly different from actions defined in the entityset controller.
For reference, method signatures for every action definition in the singleton controller are listed below.
[!code-csharpMain]
1: // Get Singleton
2: // ~/singleton
3: public IHttpActionResult Get()
4: public IHttpActionResult GetUmbrella()
5:
6: // Get Singleton
7: // ~/singleton/cast
8: public IHttpActionResult GetFromSubCompany()
9: public IHttpActionResult GetUmbrellaFromSubCompany()
10:
11: // Get Singleton Property
12: // ~/singleton/property
13: public IHttpActionResult GetName()
14: public IHttpActionResult GetNameFromCompany()
15:
16: // Get Singleton Navigation Property
17: // ~/singleton/navigation
18: public IHttpActionResult GetEmployees()
19: public IHttpActionResult GetEmployeesFromCompany()
20:
21: // Update singleton by PUT
22: // PUT ~/singleton
23: public IHttpActionResult Put(Company newCompany)
24: public IHttpActionResult PutUmbrella(Company newCompany)
25:
26: // Update singleton by Patch
27: // PATCH ~/singleton
28: public IHttpActionResult Patch(Delta<Company> item)
29: public IHttpActionResult PatchUmbrella(Delta<Company> item)
30:
31: // Add navigation link to singleton
32: // POST ~/singleton/navigation/$ref
33: public IHttpActionResult CreateRef(string navigationProperty, [FromBody] Uri link)
34:
35: // Delete navigation link from singleton
36: // DELETE ~/singleton/navigation/$ref?$id=~/relatedKey
37: public IHttpActionResult DeleteRef(string relatedKey, string navigationProperty)
38:
39: // Add a new entity to singleton navigation property
40: // POST ~/singleton/navigation
41: public IHttpActionResult PostToEmployees([FromBody] Employee employee)
42:
43: // Call function bounded to singleton
44: // GET ~/singleton/function()
45: public IHttpActionResult GetEmployeesCount()
Basically, this is all you need to do on the service side. The sample project contains all of the code for the solution and the OData client that shows how to use the singleton. The client is built by following the steps in Create an OData v4 Client App.
.
Thanks to Leo Hu for the original content of this article.
|