OData / OData/AspNetCoreOData

Inconsistent route detection behavior between OData convention routing and OData attribute routing

Open
#431 1 comment 3 reactions 1 assignee View on GitHub

@xuzhg is already working on this.

Since Jan 12, 2022.

enhancement
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:
image

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:
image

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:

image

image

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:
image

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:

  1. AddOData always 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.
  2. AddOData + ODataController base class implied OData routes by default. For ControllerBase controllers, 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.