OData / AspNetCoreOData

ASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core
Other
454 stars 160 forks source link

Cast segment after navigation property not supported conventionally #1113

Open gathogojr opened 9 months ago

gathogojr commented 9 months ago

We currently support the following routes conventionally via the PropertyRoutingConvention:

Where,

However, we DON'T support the following routes conventionally via the NavigationRoutingConvention:

Where,

I can't think of any reason why we would support a cast segment after a structural property conventionally and not support for navigation property.

Assemblies affected

Reproduce steps

Create a simple repro project with the characteristics described above:

// Data model

namespace NS.Models
{
    public class Customer
    {
        public int Id { get; set; }
        public List<Order>? Orders { get; set; }
    }

    public class Order
    {
        public int Id { get; set; }
        public decimal Amount { get; set; }
    }

    public class VipOrder : Order
    {
    }
}

// Controllers

namespace NS.Controllers
{
    public class CustomersController : ODataController
    {
        private static readonly List<Customer> customers = new List<Customer>
        {
            new Customer
            {
                Id = 1,
                Orders = new List<Order>
                {
                    new VipOrder { Id = 1, Amount = 290 },
                    new Order { Id = 2, Amount = 170 }
                };
            }
        };

        [EnableQuery]
        public ActionResult<IEnumerable<VipOrder>> GetOrdersOfVipOrder(int key)
        {
            var customer = customers.SingleOrDefault(d => d.Id == key);

            if (customer == null)
            {
                return NotFound();
            }

            if (customer.Orders == null)
            {
                return Enumerable.Empty<VipOrder>().ToList();
            }

            return customer.Orders.OfType<VipOrder>().ToList();
        }
    }
}

// Startup code

using Microsoft.AspNetCore.OData;
using Microsoft.OData.ModelBuilder;
using NS.Models;

var builder = WebApplication.CreateBuilder(args);

var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customers");
modelBuilder.EntitySet<Order>("Orders");

builder.Services.AddControllers().AddOData(
    options => options.EnableQueryFeatures().AddRouteComponents(
        modelBuilder.GetEdmModel()));

var app = builder.Build();

app.UseODataRouteDebug();
app.UseRouting();
app.MapControllers();

app.Run();

Expected result

The following routes to be supported conventionally:

Actual result

The following routes are not conventionally supported:

Additional detail

Workaround is to use attribute routing, e.g.:

[HttpGet("Customers({key})/Orders/NS.Models.VipOrder")]
public ActionResult<IEnumerable<VipOrder>> GetOrdersOfVipOrder(int key)
{
    // ...
}
julealgon commented 9 months ago

@gathogojr have you guys every discussed the possibility of deprecating conventional routing for OData in favor of explicit attribute routing?

I might've asked this somewhere else before (if so, my apologies), but conventional routing existing alongside attribute routing has brought so many issues it's crazy. It is also fairly confusing to a lot of people how they play together, and usually most people will end up with multiple duplicate routes because both mechanisms are always on.

If I could vote on this, I think conventional routing should be deprecated eventually.