Inconsistent route detection behavior between OData convention routing and OData attribute routing
@xuzhg is already working on this.
Since Jan 12, 2022.
- Dominant language
- C#
- Stars
- 505
- Forks
- 186
- PR merge metrics
- No merged PRs in 30d
Description
- Latest affected version: Microsoft.AspNetCore.OData v8.0.5
Let's take the default controller generated by the web API template:
(ps: value in Route was hardcoded due to https://github.com/OData/AspNetCoreOData/issues/430 )
[ApiController]
[Route("WeatherForecast")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
...
}
}
If I use this controller directly and enable OData with the WeatherForecast entityset, I get duplicate route exceptions when accessing the route:

AmbiguousMatchException: The request matched multiple endpoints. Matches: WebApplication3.Controllers.WeatherForecastController.Get (WebApplication3) WebApplication3.Controllers.WeatherForecastController.Get (WebApplication3)
Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState)
Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ProcessFinalCandidates(HttpContext httpContext, CandidateState[] candidateState)
Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.Select(HttpContext httpContext, CandidateState[] candidateState)
Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.SelectAsync(HttpContext httpContext, CandidateSet candidateSet)
Microsoft.AspNetCore.Routing.Matching.DfaMatcher.SelectEndpointWithPoliciesAsync(HttpContext httpContext, IEndpointSelectorPolicy[] policies, CandidateSet candidateSet)
Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.g__AwaitMatch|8_1(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matchTask)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Here is how it looks like in the OData route debugging page. The route is detected both as an OData route, as well as a normal MVC route:

If I change the base class of the controller, from ControllerBase to ODataController, the behavior changes: the route is still duplicated, but now they are 2 OData route detections (https://github.com/OData/AspNetCoreOData/issues/428). If I access the route, it now works:


My understanding with OData v8 is that we are trying to leverage common mechanisms across OData and MVC as much as possible, with the reuse of the same route attributes, etc. This behavior difference with the change in base class goes against that design philosophy IMHO.
The problem seems to be caused by a clash in behavior between OData convention routing, and OData attribute routing. If I change the original controller by just renaming the method to something else (which doesn't match the GET pattern):
[ApiController]
[Route("WeatherForecast")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public IEnumerable<WeatherForecast> Whatever()
{
...
}
}
I now get a single, MVC route:

So, what is happening is that, OData convention routing is considering ControllerBase actions, but OData attribute routing is not.
I believe that it would best if either:
AddODataalways implied OData routes by default. Once a route is found to be an OData route, it should not be considered an MVC route anymore (OData > MVC precedence). Opt-out using[ODataIgnore]. This would be regardless of the base class used.AddOData+ODataControllerbase class implied OData routes by default. ForControllerBasecontrollers, opt-in to OData using[ODataAttributeRouting], do not detect as OData routes automatically.
Since the core of the problem is related to convention vs attribute routing here, I think the [ODataAttributeRouting] attribute is too specific in this case. Instead, it should be just [ODataRouting] (to indicate either attribute routing, or convention routing).
With all that in mind, I personally believe 1 is the cleaner approach here (ideally combined with a potential removal of ODataController altogether and a removal of [ODataAttributeRouting]) but it seems you moved more into approach 2 with the existing attributes. I understand ODataController provides the Updated and Created methods for constructing the OData-specific results, but those could be provided in another way (say, extension methods for ControllerBase for example).
Regardless, the case where we have a single route mapped to both OData and MVC should never happen (should be an invalid scenario). Explicitly or implicitly, a route should be either one of them but never both.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.