-
Notifications
You must be signed in to change notification settings - Fork 714
How to add URL path version to ODataController using attribute routing in OData 8.x #1095
-
I am trying out version interleaving example with URL path version + OData, but I could not get OData to recognize the versions on the controller as expected with route attribute. I am looking for guidance for how to setup URL path version correctly with OData 8.x
.NET version
6.0
Package versions
<PackageReference Include="Asp.Versioning.OData" Version="6.4.0" /> <PackageReference Include="Microsoft.AspNetCore.OData" Version="8.2.5" />
Routes that I want
WeatherForecastController
|--- Get() -> /api/v1.0/weatherforecast
|--- GetV2() -> /api/v2.0/weatherforecast
|--- GetV3() -> /api/v3.0/weatherforecast
Setup code:
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers().AddOData(options => { options.Select().Filter(); }); builder.Services.AddApiVersioning().AddOData( options => { options.ModelBuilder.DefaultModelConfiguration = (builder, apiVersion, routePrefix) => { builder.EntitySet<WeatherForecast>("WeatherForecast"); }; options.AddRouteComponents("api/v{version:apiVersion}"); }); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseODataRouteDebug(); app.UseRouting(); app.MapControllers(); app.Run();
Controller:
namespace InterleavingVersions.Controllers { [ApiVersion(1.0)] [ApiVersion(2.0)] [ApiVersion(3.0)] // [Route("api/v{version:apiVersion}")] -> adding this header will result in runtime exception at startup time. see the exception in the below public class WeatherForecastController : ODataController { private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } // Conventional [EnableQuery] [HttpGet] public IQueryable<WeatherForecast> Get() { return new[] { new WeatherForecast() { Id = "v1" } }.AsQueryable(); } // Attribute WITHOUT the version prefix [EnableQuery] [HttpGet("WeatherForecast"), MapToApiVersion(2.0)] public IQueryable<WeatherForecast> GetV2() { return new[] { new WeatherForecast() { Id = "v2" } }.AsQueryable(); } // Attribute WITH the version prefix [EnableQuery] [HttpGet("api/v{version:apiVersion}/WeatherForecast"), MapToApiVersion(3.0)] public IQueryable<WeatherForecast> GetV3() { return new[] { new WeatherForecast() { Id = "v3" } }.AsQueryable(); } } }
Actual OData routes:
Expected vs actual
- Conventional: this generates the template that I expected.
- Attribute WITHOUT the version prefix
- Expected: route template has URL path prefix
api/v{version:apiVersion}
- Actual: the prefix does not present in the URL path, and versioning only works with query string like
https://localhost:xxxx/weatherforecast?api-version=2.0
- Expected: route template has URL path prefix
- Attribute WITH the version prefix: this generates the template that I expected
- I also tried to add the version route prefix
api/v{version:apiVersion}
withRouteAttribute
, and this will result in a runtime exception at startup time.
Error 1:
Attribute routes with the same name 'api/v{version:apiVersion}/WeatherForecast' must have the same template:
Action: 'InterleavingVersions.Controllers.WeatherForecastController.Get (InterleavingVersions)' - Template: 'api/v{version:apiVersion}/api/v{version:apiVersion}/WeatherForecast'
Action: 'InterleavingVersions.Controllers.WeatherForecastController.Get (InterleavingVersions)' - Template: 'api/v{version:apiVersion}/api/v{version:apiVersion}/WeatherForecast'
Action: 'InterleavingVersions.Controllers.WeatherForecastController.Get (InterleavingVersions)' - Template: 'api/v{version:apiVersion}/WeatherForecast'
Error 2:
Attribute routes with the same name 'api/v{version:apiVersion}/WeatherForecast/$count' must have the same template:
Action: 'InterleavingVersions.Controllers.WeatherForecastController.Get (InterleavingVersions)' - Template: 'api/v{version:apiVersion}/api/v{version:apiVersion}/WeatherForecast/$count'
Action: 'InterleavingVersions.Controllers.WeatherForecastController.Get (InterleavingVersions)' - Template: 'api/v{version:apiVersion}/api/v{version:apiVersion}/WeatherForecast/$count'
Action: 'InterleavingVersions.Controllers.WeatherForecastController.Get (InterleavingVersions)' - Template: 'api/v{version:apiVersion}/WeatherForecast/$count''
Note that since 8.0, ASP.NET Core OData deprecated the OdataRouteAttribute
: https://devblogs.microsoft.com/odata/attribute-routing-in-asp-net-core-odata-8-0-rc/, and attribute routes are specified with the ASP.NET Core route attributes.
Beta Was this translation helpful? Give feedback.
All reactions
Navigating the mess that is OData routing conventions is daunting. I empathize.
Observations
- You shouldn't have to call
UseRouting()
as that is done for you (it worked for me without it) - Naming with OData is everything. I'm 99% sure an entity and entity set cannot have the same name
a. This means you should haveWeatherForecasts
andWeatherForecast
- OData is intrinsically not version-aware and, therefore, makes many assumptions in its naming conventions
a. There can only be oneGet
in code, which is enforced by the compiler
b.GetV2
will never be seen or treated as matchingGet
without some help
Honestly, I don't think I have a lot of examples of version interleaving with OData. This is ...
Replies: 1 comment
-
Navigating the mess that is OData routing conventions is daunting. I empathize. Observations
Honestly, I don't think I have a lot of examples of version interleaving with OData. This is probably due to the version-to-EDM affinity, but it is possible and supported. SolutionTo make things work the way you want, this simple change should do the trick: [ApiVersion( 1.0 )] [ApiVersion( 2.0 )] [ApiVersion( 3.0 )] public class WeatherForecastsController : ODataController { [EnableQuery] public IQueryable<WeatherForecast> Get() => new WeatherForecast[] { new() { Id = "v1" } }.AsQueryable(); [EnableQuery] [ActionName( nameof( Get ) )] [MapToApiVersion( 2.0 )] public IQueryable<WeatherForecast> GetV2() => new WeatherForecast[] { new() { Id = "v2" } }.AsQueryable(); [EnableQuery] [ActionName( nameof( Get ) )] [MapToApiVersion( 3.0 )] public IQueryable<WeatherForecast> GetV3() => new WeatherForecast[] { new() { Id = "v3" } }.AsQueryable(); }
|
Beta Was this translation helpful? Give feedback.