Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Applies To:# OData Web API 8 supported OData Web API v8
This tutorial shows how ASP.NET Core OData 8 supports singleton routing. An understanding of routing fundamentals in ASP.NET Core OData 8 is assumed. If you're unfamiliar with routing in ASP.NET Core OData 8, you may want to go through the routing overview tutorial.
Introduction
You use a singleton to represent a special entity where there can be only one of it's kind. A singleton is not referenced by key. OData allows defining such a special entity that can be addressed directly by it's name from the service root.
OData singleton routing convention supports the following route templates:
Request Method | Route Template |
---|---|
GET |
~/{singleton} |
GET |
~/{singleton}/{cast} |
PUT |
~/{singleton} |
PUT |
~/{singleton}/{cast} |
PATCH |
~/{singleton} |
PATCH |
~/{singleton}/{cast} |
Note: {cast}
is a placeholder for the fully-qualified name for a derived type
To illustrate singleton routing convention, let's build a sample OData service.
Packages
Install the Microsoft.AspNetCore.OData 8.x Nuget package:
In the Visual Studio Package Manager Console:
Install-Package Microsoft.AspNetCore.OData
Prerequisites
Visual Studio 2022 with the ASP.NET and web development workload
Models
The following are the models for the OData service:
Company
class
namespace SingletonRouting.Models
{
public class Company
{
public int Id { get; set; }
public string Name { get;set; }
}
}
HoldingCompany
class
namespace SingletonRouting.Models
{
public class HoldingCompany : Company
{
public int NumberOfSubsidiaries { get; set; }
}
}
Edm model and service configuration
The logic for building the Edm model and configuring the OData service is as follows:
// Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OData;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData.ModelBuilder;
using SingletonRouting.Models;
var builder = WebApplication.CreateBuilder(args);
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.Singleton<Company>("Company");
builder.Services.AddControllers()
.AddOData(options =>
{
options.Select().EnableQueryFeatures(null).AddRouteComponents(
routePrefix: "odata",
model: modelBuilder.GetEdmModel());
});
var app = builder.Build();
app.UseODataRouteDebug();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
app.Run();
In the above block of code, we define a singleton named Company
. Implicitly, Company
and HoldingCompany
get included in the Edm model as entity types.
Controller
The partial structure of the controller for the OData service is as follows:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using SingletonRouting.Models;
public class CompanyController : ODataController
{
private static Company company;
static CompanyController()
{
company = new HoldingCompany
{
Id = 13,
Name = "Company LLC",
NumberOfSubsidiaries = 7
};
}
}
Notice how we're making use of a static constructor that is called only once in the lifetime of the service. We are initializing the company
static class member to an instance of the derived type HoldingCompany
.
Routing conventions for singletons
In this section we cover the conventions for singleton routing and the controller actions (endpoints) required for the request to be routed successfully.
Retrieving a singleton
The route template for this request is GET ~/{singleton}
.
The following request returns the Company
singleton:
GET http://localhost:5000/odata/Company
For the above request to be conventionally-routed, a controller action named Get
(or GetCompany
) is expected:
public ActionResult<Company> Get()
{
return company;
}
The following JSON payload shows the expected response:
{
"@odata.context": "http://localhost:5000/odata/$metadata#Company/SingletonRouting.Models.HoldingCompany",
"@odata.type": "#SingletonRouting.Models.HoldingCompany",
"Id": 13,
"Name": "Company LLC",
"NumberOfSubsidiaries": 7
}
Retrieving a derived singleton
The route template for this request is GET ~/{singleton}/{cast}
.
The following request returns the HoldingCompany
derived singleton:
GET http://localhost:5000/odata/Company/SingletonRouting.Models.HoldingCompany
For the above request to be conventionally-routed, a controller action named GetFromHoldingCompany
is expected:
public ActionResult<HoldingCompany> GetFromHoldingCompany()
{
if (!(company is HoldingCompany holdingCompany))
{
return NotFound();
}
return holdingCompany;
}
The following JSON payload shows the expected response:
{
"@odata.context": "http://localhost:5000/odata/$metadata#Company/SingletonRouting.Models.HoldingCompany",
"Id": 13,
"Name": "Company LLC",
"NumberOfSubsidiaries": 7
}
Updating a singleton
The route template for this request is PUT ~/{singleton}
The following PUT
request updates the Company
singleton:
PUT http://localhost:5000/odata/Company
Here's the request body:
{
"Id": 13,
"Name": "Company LLP",
}
For the above request to be conventionally-routed, a controller action named Put
(or PutCompany
) is expected. The controller action should accept a single parameter of type Company
decorated with FromBody
attribute:
public ActionResult Put([FromBody] Company updated)
{
company.Name = updated.Name;
return Ok();
}
The response status code should be 200
. Querying the updated entity should return the following:
{
"@odata.context": "http://localhost:5000/odata/$metadata#Company/SingletonRouting.Models.HoldingCompany",
"Id": 13,
"Name": "Company LLP",
"NumberOfSubsidiaries": 7
}
Updating a derived singleton
The route template for this request is PUT ~/{singleton}/{cast}
The following PUT
request updates the HoldingCompany
derived singleton:
PUT http://localhost:5000/odata/Company/SingletonRouting.Models.HoldingCompany
Here's the request body:
{
"Id": 13,
"Name": "Company LTD",
"NumberOfSubsidiaries": 5
}
For the above request to be conventionally-routed, a controller action named PutFromHoldingCompany
is expected. The controller action should accept a single parameter of type HoldingCompany
decorated with FromBody
attribute:
public ActionResult PutFromHoldingCompany([FromBody] HoldingCompany updated)
{
if (!(company is HoldingCompany holdingCompany))
{
return NotFound();
}
holdingCompany.Name = updated.Name;
holdingCompany.NumberOfSubsidiaries = updated.NumberOfSubsidiaries;
return Ok();
}
The response status code should be 200
. Querying the updated derived singleton should return the following:
{
"@odata.context": "http://localhost:5000/odata/$metadata#Company/SingletonRouting.Models.HoldingCompany",
"Id": 13,
"Name": "Company LTD",
"NumberOfSubsidiaries": 5
}
Patching a singleton
The route template for this request is PATCH ~/{singleton}
The following PATCH
request patches the Company
singleton:
PATCH http://localhost:5000/odata/Company
Here's the request body:
{
"Name": "Company (PTY) LTD"
}
For the above request to be conventionally-routed, a controller action named Patch
(or PatchCompany
) is expected. The controller action should accept a single parameter of type Delta<Company>
decorated with FromBody
attribute:
public ActionResult Patch([FromBody] Delta<Company> delta)
{
delta.Patch(company);
return Ok();
}
The response status code should be 200
. Querying the patched singleton should return the following:
{
"@odata.context": "http://localhost:5000/odata/$metadata#Company/SingletonRouting.Models.HoldingCompany",
"@odata.type": "#SingletonRouting.Models.HoldingCompany",
"Id": 13,
"Name": "Company (PTY) LTD",
"NumberOfSubsidiaries": 3
}
Patching a derived singleton
The route template for this request is PATCH ~/{singleton}/{cast}
The following PATCH
request patches the HoldingCompany
derived singleton:
PATCH http://localhost:5000/odata/Company/SingletonRouting.Models.HoldingCompany
Here's the request body:
{
"Name": "Company A/S",
"NumberOfSubsidiaries": 3
}
For the above request to be conventionally-routed, a controller action named PatchFromHoldingCompany
is expected. The controller action should accept a single parameter of type Delta<HoldingCompany>
decorated with FromBody
attribute:
public ActionResult PatchFromHoldingCompany([FromBody] Delta<HoldingCompany> delta)
{
if (!(company is HoldingCompany holdingCompany))
{
return NotFound();
}
delta.Patch(holdingCompany);
return Ok();
}
The response status code should be 200
. Querying the patched derived singleton should return the following:
{
"@odata.context": "http://localhost:5000/odata/$metadata#Company/SingletonRouting.Models.HoldingCompany",
"Id": 13,
"Name": "Company A/S",
"NumberOfSubsidiaries": 3
}
Singleton routing endpoint mappings
If you went through this tutorial and implemented the logic in an OData service, you can run the application and visit the $odata
endpoint (http://localhost:5000/$odata) to view the endpoint mappings: