Search code examples
asp.net-core.net-coreodata.net-6.0asp.net-core-6.0

Using OData 8 and multiple controllers with the same name


This is a .NET 6.0 web application that's being upgraded from .NET Core 3.1, and as part of it, OData is being upgraded from 7.5.8 to 8.0.11.

With OData 7.5.8, I was able to have two different controllers on two different routes:

[Route("api/[controller]")]
[ApiController]
public class MessagesController : ControllerBase 
{
    [HttpGet("{id}")]
    public async Task<IActionResult> Get(Guid id)
    { ... }
}

public class MessagesController : ODataController
{
    [HttpGet]
    [MessagesEnableQuery(MaxExpansionDepth = 1)] // this is a custom EnableQuery attribute
    public IQueryable<Message> Get(ODataQueryOptions options)
    { ... }
}

// routing setup in Startup.cs
app.UseEndpoints(endpoints =>
{
    ...
    endpoints.Select().Expand().Filter().OrderBy().Count().MaxTop(null);
    endpoints.EnableDependencyInjection();
    endpoints.MapODataRoute("ODataRoute", "odata", GetEdmModel());
}

And these could be reached by two separate routes:

  • /api/Messages/GUID
  • /odata/Messages?$filter=...

However, with OData 8.0.11, it doesn't seem like I can do this anymore. If I try to hit the /odata/Messages route, I get a 404 response. For 8.0.11, I added [ODataRouteComponent("odata")] to the controller class, and the configuration is now:

services.AddMvc(...)
    .AddOData(options =>
    {
        options.EnableQueryFeatures();
        options.AddRouteComponents("odata", GetEdmModel());
    });

And my IEdmModel is built like so:

builder.EntitySet<Message>(nameof(Message) + "s");

From what I can tell, when I have two controllers of the same name, ASP.NET is sending the requests to the /api route even when the path starts with /odata. I was able to figure this out by putting break points in each controller's constructor and when I made a request for /odata/Messages the application paused in the API controller instead.

So, how do I get it so that I can use separate routes for separate controllers again?

Thanks in advance.


Solution

  • I found a relevant Github issue at https://github.com/OData/AspNetCoreOData/issues/238, and from that I was able to use only attribute routing, which did allow me to use both controllers, but with a significant drawback: the OData endpoint would only return the query result and no longer contained any of the query metadata, meaning that if I added $count=true to the query, it wouldn't actually include the item count. So that's not helpful.

    In the end, I decided to merge the two controllers together.

    [Route("api/[controller]")]
    [ApiController]
    [ODataRouteComponent("odata")]
    public class MessagesController : ControllerBase
    {
        [HttpGet]
        [MessagesEnableQuery(MaxExpansionDepth = 1, MaxTop = 100)] 
        public async Task<IQueryable<Message>> Get(ODataQueryOptions options)
        {
            return await _service.FindAsync();
        }
    }
    

    Not the best solution, but it works. The only problem is that the endpoint is reachable by both /api/Messages and /odata/Messages. Oh, and I had to add [ODataIgnored] to all the other API methods, but at this point I've spent enough time on this, so this is good enough.