Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conflicting routes when using NSwag and OData #1218

Open
Gaven-F opened this issue Apr 18, 2024 · 6 comments
Open

Conflicting routes when using NSwag and OData #1218

Gaven-F opened this issue Apr 18, 2024 · 6 comments
Assignees
Labels
bug Something isn't working

Comments

@Gaven-F
Copy link

Gaven-F commented Apr 18, 2024

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.

@julealgon
Copy link
Contributor

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.

@corranrogue9
Copy link
Contributor

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

@xuzhg
Copy link
Member

xuzhg commented Apr 23, 2024

@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

@Gaven-F
Copy link
Author

Gaven-F commented Apr 25, 2024

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

@Gaven-F
Copy link
Author

Gaven-F commented Apr 25, 2024

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)

@julealgon
Copy link
Contributor

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:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants