Description
I have 2 controllers with the same name, but in different namespaces. Both have GET actions with differing parameter lists. I'm using convention-based routing.
// v1
namespace v1
{
public class SensorsController : ODataController
{
[HttpGet]
public IActionResult Get(ODataQueryOptions<v1.Sensor> query, ODataQuerySettings querySettings) {}
}
}
// v2
namespace v2
{
public class SensorsController : ODataController
{
[HttpGet]
public IActionResult Get(ODataQueryOptions<v2.Sensor> query, ODataQuerySettings querySettings, [FromODataUri] Guid tid){}
}
}
// Startup.cs
app.UseMvc(routeBuilder =>
{
IEdmModel model = BuildEdmModelV1(app.ApplicationServices); // v1 models
routeBuilder.MapODataServiceRoute("ODataRouteV1", "odata", model);
IEdmModel model = BuildEdmModelV2(app.ApplicationServices); // v2 models
routeBuilder.MapODataServiceRoute("ODataRouteV2", "v2/odata/tenants/{tid}", model);
});
This is successful:
http://localhost:3000/v2/odata/tenants/myTidGuid/Sensors
This results in the exception below:
http://localhost:3000/odata/Sensors
[11:21:06 DBG] Request successfully matched the route with name 'ODataRouteV1' and template 'odata/{*odataPath}'.
[11:21:06 DBG] Executing action v2.SensorsController.Get
[11:21:07 INF] Executed action v2.SensorsController.Get in 337.9326ms
[11:21:07 ERR] Connection id "0HLEMR6DSEVVE", Request id "0HLEMR6DSEVVE:00000003": An unhandled exception was thrown by the application.
System.ArgumentException: The given model does not contain the type 'v1.Sensor'.
Parameter name: elementClrType
at Microsoft.AspNet.OData.ODataQueryContext..ctor(IEdmModel model, Type elementClrType, ODataPath path) in C:\Repos\WebApi\src\Microsoft.AspNet.OData.Shared\ODataQueryContext.cs:line 55
at Microsoft.AspNet.OData.ODataQueryParameterBindingAttribute.ODataQueryParameterBinding.BindModelAsync(ModelBindingContext bindingContext) in C:\Repos\WebApi\src\Microsoft.AspNetCore.OData\ODataQueryParameterBindingAttribute.cs:line 89
at Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BinderTypeModelBinder.d__2.MoveNext()
As shown above, the v2 controller is selected for the v1 route. In the code below (from ODataActionSelector.cs) considerCandidates
contains GET actions from both v1 and v2 controllers. However it's not able to disambiguate and just returns FirstOrDefault(), which happens to always be the v2 controller. So the v2 route works, but v1 doesn't.
var matchedCandidates = considerCandidates
.Where(c => !c.FilteredParameters.Any() || c.FilteredParameters.All(p => availableKeys.Contains(p.Name.ToLowerInvariant())))
.OrderByDescending(c => c.FilteredParameters.Count)
.ThenByDescending(c => c.TotalParameterCount);
Assemblies affected
Microsoft.AspNetCore.OData 7.0.0.B4
Reproduce steps
The simplest set of steps to reproduce the issue. If possible, reference a commit that demonstrates the issue.
Expected result
The correct controller should be selected based on the registered route.
Actual result
The correct controller is not selected.
Additional detail
I've tried various forms of attribute routing without success. It does work with MVC routing, however the entities are not returned in an OData JSON envelope, which will break clients.
// v1
[HttpGet("odata/sensors")] // MVC route. This works
public IActionResult Get(ODataQueryOptions<Sensor> query, ODataQuerySettings querySettings)
// Startup.cs
routeBuilder.MapODataServiceRoute("ODataRouteV1", "odata", model);
routeBuilder.EnableDependencyInjection();
Any idea what I'm doing wrong?