aspnet-api-versioning
aspnet-api-versioning copied to clipboard
How to route controllers correctly, when exists 2 OData entity sets with the same name, but with different route prefixes?
I used Microsoft.AspNetCore.OData.Versioning.ApiExplorer nuget.
The question is, how to correct mark controller, in order to route will valid? See the example, for more details:
public class OrderModelConfiguration : IModelConfiguration
{
private EntityTypeConfiguration<OrderMarket> ConfigureCurrent( ODataModelBuilder builder )
{
var order = builder.EntitySet<OrderMarket>( "orders" ).EntityType;
order.HasKey( p => p.Id );
return order;
}
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
if ( routePrefix != "api/v{version:apiVersion}/odata/market" )
{
return;
}
ConfigureCurrent( builder );
}
}
public class OrderLimitModelConfiguration : IModelConfiguration
{
private EntityTypeConfiguration<OrderLimit> ConfigureCurrent( ODataModelBuilder builder )
{
var order = builder.EntitySet<OrderLimit>( "orders" ).EntityType;
order.HasKey( p => p.Id );
return order;
}
public void Apply( ODataModelBuilder builder, ApiVersion apiVersion, string routePrefix )
{
if ( routePrefix != "api/v{version:apiVersion}/odata/limit" )
{
return;
}
ConfigureCurrent( builder );
}
}
Routes configuration:
endpoints.MapVersionedODataRoute( "odata-market", "api/v{version:apiVersion}/odata/market", modelBuilder, cb =>
{
cb.AddCommonService( "odata-market", "api/v{version:apiVersion}/odata/market", endpoints, modelBuilder );
} );
endpoints.MapVersionedODataRoute( "odata-limit", "api/v{version:apiVersion}/odata/limit", modelBuilder, cb =>
{
cb.AddCommonService( "odata-limit", "api/v{version:apiVersion}/odata/limit", endpoints, modelBuilder );
} );
Controllers:
//should be: api/v1/odata/market/orders
[ApiVersion( "1.0" )]
[ApiVersion( "2.0" )]
[ControllerName("orders")]
public class OrdersController : ODataController
{
public IActionResult Get(
[FromQuery] ODataQueryOptionsSlim<OrderMarket> query)
{
return Ok( new[] { new OrderMarket { Id = 1, Customer = "Bill Mei" } } );
}
}
//should be: api/v1/odata/limit/orders
[ApiVersion( "1.0" )]
[ApiVersion( "2.0" )]
[ControllerName( "orders" )]
public class LimitControllers : ODataController
{
public IActionResult Get(
[FromQuery] ODataQueryOptions<OrderLimit> query)
{
return Ok( new[] { new OrderLimit { Id = 1, Customer = "Bill Mei" } } );
}
}
How to map that LimitControllers handles api/v1/odata/limit/orders requests, OrdersController handles api/v1/odata/market/orders requests?
What you have looks right. What actually happens? Ideally, the controller type names should match up, but you've used [ControllerName] and everything does seem to line up against orders. Did it ever work before versioning? It might have. I suspect part of the issue here is the routing attributes or, rather, the lack thereof. Endpoint Routing requires the routing attributes and I don't see any. I will attempt to create a repro from the information you've provided. If you have something you can share, it would certainly save me some time.
The problem is that with these 2 controllers, the application doesn't work. These controllers don't bind correctly to routes api/v1/odata/market/orders and api/v1/odata/limit/orders. I mean how to mark the controller in order to it should handle api/v1/odata/limit/orders with OData entity set OrderLimit? Because, when I bind via [ControllerName], I have 2 entity sets with 'orders' names, and these sets conflict.
You can try to create a repro with these code snippets, which I provided. Just create 2 classes of orders.
I thought this type of setup was possible. I've attempted to reproduce your scenario given the setup you've provided, but I can't get it to work. I started with the bare bones OData (e.g. no versioning) and then started working backward from there. It still doesn't work. Do you have the world's simplest repro, without versioning, that demonstrates OData supports this type of setup?
I'm seeing that OData is always trying to resolve Orders from the Market EDM. The Market endpoint works as expected, but the Limit endpoint always tries to resolve the entity from the Market EDM and fails. This could very well be a bug or limitation in OData. I know that OData doesn't care about the prefix. It only sees orders. This would explain how it is confused between the two. I can't say for sure why it always resolves orders for Market, but not Limit. This could be due to the order of registration or alphabetical order of their names. It does, however, consistently fail the same way.
I don't mind continuing to help you come a resolution, but I at least need a non-versioned solution to work from or I may just be chasing my tail. Honestly, I would recommend against using duplicate resource names; especially, in an OData application. If you change the routes to simply be ~/orders/[market | limit] that should solve your problem. You could also just use a discriminator in the query string too. Market and limit orders are still both orders. Something like ~/orders?type=[market | order]. Those are just suggestions.
I think this is a limitation in AspNetCore.OData v7. You can try v8 to solve this issue, but notice v8 is not compatible with api versioning.
I'm not entirely sure what the issue is or was. The existing examples so show configurations with different route prefixes and it was working. There definitely have been limitations with the OData routing extensions. It got quite a bit better in OData 8. The 6.0 release is just around the corner and has support for OData 8. There are similar samples with multiple route prefixes that you can follow to see if it resolves you issue. You can find the latest examples here.
If you have repro, I can take a deeper look; otherwise, we may have reached the end of this issue.
Two updates:
- Apologies, but I missed the detail in
AddCommonService. I'm not sure what that is or what it does, but it accepts themodelBuilder. Did you filter out the EDMs? Calling.GetEdmModels()will return them all. You want.GetEdmModels(prefix), which you can get from the suppliedprefixparameter. - API Versioning 6.0 Preview 3 is now available with support for OData 8.0. That might be a solution for you.
There hasn't been any other activity and this thread seems to be solved or abandoned. 6.0 has been officially released with support for OData 8.x and .NET 6.0. This scenario should be fully supported now. If the issue remains or you run into some other issue, I'm happy to reopen the issue and investigate further. Thanks.