OData / AspNetCoreOData

ASP.NET Core OData: A server library built upon ODataLib and ASP.NET Core

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Conflicting routes when using NSwag and OData

Gaven-F opened this issue · comments

Assemblies affected
ASP.NET Core OData 8.2.5
image

Describe the bug
When I use NSwag, I use OData to set up Get (id) and find an error: Nswag cannot generate openapi, which seems to mean that Get routes are created multiple times

Reproduce steps
Use the sample code and load Nswag (I don't know if it's the only one that contains this error)

Data Model

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

EDM (CSDL) Model
image

Screenshots
image

Additional context

CustomerController.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;

namespace Server.Controllers;

[Route("[controller]/[action]")]
public class CustomerController
{
    [EnableQuery]
    public ActionResult<IEnumerable<Customer>> Get() => new List<Customer>
    {
        new() { Id = 1, Name = "Customer 1" },
        new() { Id = 2, Name = "Customer 2" }
    };

    public ActionResult<Customer> Get([FromRoute] int key)
    {
        return new Customer { Id = key, Name = $"Customer {key}" };
    }
}

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

program.cs


using Microsoft.AspNetCore.OData;
using Microsoft.OData.ModelBuilder;
using Server.Controllers;
using Server.Models;
using System.Text.Json;

var builder = WebApplication.CreateBuilder(args);

var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customer");
modelBuilder.EntitySet<TestModel>("Test");

builder.Services
    .AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
    })
    .AddOData(options =>
    {
        options
        .EnableQueryFeatures()
        .AddRouteComponents("oData", modelBuilder.GetEdmModel());
    });

builder.Services.AddOpenApiDocument(confi =>
{
    confi.DocumentName = "default";

    confi.PostProcess = doc =>
    {
        doc.Info.Title = "BMS API";
    };
});


var app = builder.Build();

app
    .UseOpenApi()
    .UseSwaggerUi()
    .UseReDoc(config => config.Path = "/redoc");

app.UseAuthorization();
app.UseODataBatching().UseODataQueryRequest().UseODataRouteDebug();

app.MapControllers();

app.Run();

Maybe I should ask Nswag again.

This is a NSwag limitation due to the way they "generate" operation names. You can provide your own delegate to generate those names the way you want so that no clashes happen.

I don't believe this is in any way related to OData. I've had this exact same problem in a normal MVC project before.

@xuzhg to follow up if this is related to OData or if there's configuration that needs to happen on the NSwag side.

@Gaven-F
Based on your "Edm Model" (You have entity set named 'Customer'), so, OData routing mechanism tries to build endpoints for all actions in Controller (CustomerController) based on the "OData Conventional rules".

So, public ActionResult<Customer> Get([FromRoute] int key) meets one rule: [
a) controller name is entity set name,
b) action name is Get,
c) the parameters contains "key" or "Id"

So, two endpoints for public ActionResult<Customer> Get([FromRoute] int key) are created as:

~/oData/Customer({key})
~/oData/Customer/{key}

Meanwhile, since CustomerController has attribute routing as '[Route("[controller]/[action]")]', so I assume another 'non-odata' endpoint is created as:

~/customer/get (This is from ASP.NET Core attribute routing using [RouteAttribute])

So, I am using your above sample codes to test it, it seems the endpoints work fine:

image

I deleted the RouteAttribute and it worked fine, I think it's because the route attribute creates a route once and then OData creates a route itself ...... I think that's why

In addition, I seem to have found a new problem:
The $count route gave me a wrong report, but it is more inexplicable:

image
image
image

There is no httpcode return when using swagger. Access it directly using the browser. The httpcode is 200 but the content is empty.

My code hasn't changed much, but I took it with me just in case.

using Microsoft.AspNetCore.Mvc;

namespace Server.Controllers;

public class CustomerController
{
	private readonly List<Customer> data = [new() { Id = 1, Name = "Customer 1" }, new() { Id = 2, Name = "Customer 2" }];

	public ActionResult<IEnumerable<Customer>> Get() => data;

	public ActionResult<Customer> Get([FromRoute] int key) => new Customer { Id = key, Name = $"Customer {key}" };
}

public class Customer
{
	public int Id { get; set; }

	public string Name { get; set; } = string.Empty;
}
using System.Text.Json;
using Microsoft.AspNetCore.OData;
using Microsoft.OData.ModelBuilder;
using Server.Controllers;
using Server.Models;

var builder = WebApplication.CreateBuilder(args);

var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customer");
modelBuilder.EntitySet<TestModel>("Test");

var arg = Environment.GetEnvironmentVariables();


builder
	.Services.AddControllers()
	.AddJsonOptions(options =>
	{
		options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
	})
	.AddOData(options =>
	{
		options
		.EnableQueryFeatures()
		.AddRouteComponents("oData", modelBuilder.GetEdmModel());
	});

builder.Services.AddOpenApiDocument(confi =>
{
	confi.DocumentName = "default";

	confi.PostProcess = doc =>
	{
		doc.Info.Title = "BMS API";
	};
});

var app = builder.Build();

app.UseOpenApi().UseSwaggerUi().UseReDoc(config => config.Path = "/redoc");

app.UseAuthorization();
app
	.UseODataQueryRequest()
	.UseODataBatching()
	.UseODataRouteDebug();

app.MapControllers();

app.Run();

In addition, I didn't find a relatively new documentation (even the documentation on Microsoft seems to be an old version)

I deleted the RouteAttribute and it worked fine, I think it's because the route attribute creates a route once and then OData creates a route itself ...... I think that's why

Related: