AspNetCoreOData
AspNetCoreOData copied to clipboard
Routing by convention for functions creates duplicate GET route
Assemblies affected ASP.NET Core OData 8.0.10
Describe the bug
While using routing by convention for OData Functions it is required to use [HttpGet] before the FunctionRoutingConvention handles the route generation. However, when the [HttpGet] is applied, it also appears to be generating a route for the blank path too.
According to https://github.com/OData/AspNetCoreOData/blob/main/src/Microsoft.AspNetCore.OData/Routing/Conventions/FunctionRoutingConvention.cs#L39 [HttpGet] must be specified on the route as well which may be the reason it is being picked up and handled "twice".
The same logic appears to be in the Action routing convention as well so I would expect the same issue to occur for OData Actions as well.
My ODataController:
[Route("api/v1/Component")]
[ApiController]
public class ComponentController : ODataController {
[EnableQuery]
[HttpGet] // must be specified
public ActionResult<IQueryable<Component>> GetComponents(string jobName) { ... }
}
Output in $odata debug:

Data Model
var entityType = builder.EntitySet<Component>(nameof(Component)).EntityType;
entityType.Select().Expand().OrderBy().Filter().Count().Page(ApiDefaultLimits.MaxTop, ApiDefaultLimits.PageSize);
var et2 = entityType
.Collection
.Function("GetComponents")
.ReturnsCollectionFromEntitySet<Component>(nameof(Component));
et2.Parameter<string>("jobName");
Expected behavior While using routing convention, it is expected that only a single route is generated for functions. However, a blank route (at /api/v1/Component) is also generated as shown in the screenshot above.
According to #428 we're not supposed to mix the usage of HTTP attributes (e.g. [HttpGet]) for methods handled by routing convention; however, the function will not be handled by the FunctionRoutingConvention without one being declared. This requirement appears to be inconsistent in behaviour compared to the other routing conventions.
Workaround
Rename the GetComponents method to something different like GetComponentsFunction and manually specify the route in the HTTP Get attribute like so. For instance:
[HttpGet("GetComponents(jobName={jobName})")]
public ActionResult<IQueryable<Component>> GetComponentsFunction(string jobName) { ... }
This workaround would be ignoring the utilization of routing by convention though.