Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

How do you use versioning with OData attribute routing? #961

Discussion options

I'm trying to use api versioning with odata attribute routing, but can't seem to find any way in which it works. I'm using Asp.Versioning.OData 6.4.0, but had the same issues in version 7 too.

I set up my services the way I would expect for versioning in the url segment:

var builder = WebApplication.CreateBuilder(args);
builder.Services
 .AddControllers()
 .AddOData();
builder.Services
 .AddApiVersioning(options => options.ApiVersionReader = new UrlSegmentApiVersionReader())
 .AddOData(options =>
 {
 options.ModelBuilder.DefaultModelConfiguration = (builder, apiVersion, routePrefix) =>
 {
 builder.EntitySet<Person>("People");
 };
 options.AddRouteComponents("api/v{version:apiVersion}");
 });

Then I setup a controller as follows (note I'm specifically avoiding convention based action names to enforce that it uses the attribute routing only):

 [ApiVersion(1)]
 [ApiVersion(2)]
 [Route("api/v{version:apiVersion}")]
 public class PeopleController : ODataController
 {
 [HttpGet("people")]
 public IActionResult GetAll()
 {
 return Ok(new List<Person>());
 }
 [HttpPost("people")]
 public IActionResult PostAPerson()
 {
 return Ok();
 }
 }

I get the following error:

RoutePatternException: The route parameter name 'version' appears more than one time in the route template.

If I remove the Route attribute from the controller, then both api/v1/people and just people gives a 404.
If instead I remove the prefix from the AddRouteComponents call then api/v1/people bypasses OData just returning an array with no OData context etc.

I have similar issue using the QueryStringApiVersionReader.

The only way I can get anything to work is to use convention based routing, is attribute routing not supported, or am I missing something?

I can push a simple git repo with my code if that would help.

You must be logged in to vote

Honestly, this is probably a question better suited for the OData team, but I'll do my best to answer anyway. API Versioning doesn't change the way that attribute routing works in OData. The mapping is a bit strange to me and the documentation that shows E2E configuration is lacking IMHO. It certainly doesn't appear to work the way one might expect. If you find that it works normally, but then stops working when you apply API Versioning, then there might be an issue.

It took a lot of tinkering, but this configuration did finally work:

[ApiVersion( 1 )]
[ApiVersion( 2 )]
public class PeopleController : ODataController
{
 [HttpGet( "api/v{version:apiVersion}/people" )]
 [HttpGet( "api...

Replies: 1 comment 1 reply

Comment options

Honestly, this is probably a question better suited for the OData team, but I'll do my best to answer anyway. API Versioning doesn't change the way that attribute routing works in OData. The mapping is a bit strange to me and the documentation that shows E2E configuration is lacking IMHO. It certainly doesn't appear to work the way one might expect. If you find that it works normally, but then stops working when you apply API Versioning, then there might be an issue.

It took a lot of tinkering, but this configuration did finally work:

[ApiVersion( 1 )]
[ApiVersion( 2 )]
public class PeopleController : ODataController
{
 [HttpGet( "api/v{version:apiVersion}/people" )]
 [HttpGet( "api/v{version:apiVersion}/people/$count" )]
 public IActionResult GetAll()
 {
 return Ok( new List<Person>() );
 }
 [HttpPost( "api/v{version:apiVersion}/people" )]
 public IActionResult PostAPerson( [FromBody] Person person )
 {
 return Ok();
 }
}

As far as I can tell, the OData routing infrastructure does not honor built-in route tokens such as [controller] and presumably [action]. I found that when I was using [Route] to specify the prefix - any prefix, the generated route template would end up using the prefix from [Route] and the OData configured route prefix. That feels like a bug to me. 🤷🏽

Ultimately, going the path of route attributes feels tried and true, but you still have to know the OData routing conventions and match it if you want the full OData feature set. Even when some parts work, you might be missing other parts that you didn't realize you're missing; for example, ~/$count. For that reason, I've surrendered to conventions whenever possible. Sadly, there are some route templates that don't seem to work unless you specify the template. 😞

The OData route debugging middleware is extremely useful in figuring out if you did things right:

if ( app.Environment.IsDevelopment() )
{
 // navigate to ~/$odata to determine whether any endpoints did not match an odata route template
 app.UseODataRouteDebug();
}

If you have an expected OData route and it doesn't show up in the OData list, then you know something is configured incorrectly. I hope that helps.

You must be logged in to vote
1 reply
Comment options

Wow, that does seem to work, thank you for your quick response!

It is a shame that the common parts of the route url can't be extracted to the [Route] attribute in this case. It does seem that that is something related to the versioning, or maybe just generally having parameters in the template? as I believe it works ok when you have just a simple route prefix and setup your services using just the original IMvcBuilder.AddOData extension.

Maybe I'll just stick to convention based routing for now then.

Thanks again.

Answer selected by cjwainwright
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /