使用 OData 8.x 和 Route 属性在同一控制器方法上重复端点路由

Jas*_*son 5 asp.net-core-webapi swashbuckle.aspnetcore asp.net-core-odata

我遇到了 OData 8.x 和属性路由的问题,其中同一控制器操作生成重复路由。此重复路线导致 Swagger / Swashbuckle 抛出“方法/路径组合冲突”错误。

我已经能够将其简化为Visual Studio 2019 中稍微调整的默认天气预报模板。

// Change name of controller to plural
// Added Route() attribute
// Changed ControllerBase to ODataController (issue happens with ControllerBase too)

[Route("api/[controller]")]
public class WeatherForecastsController : ODataController{

    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    [HttpGet()]
    public IEnumerable<WeatherForecast> Get() {
        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }

}
Run Code Online (Sandbox Code Playgroud)

添加 OData 就像将调用包含AddOData()在Startup.cs

private IEdmModel GetEdmModel() {
    var builder = new ODataConventionModelBuilder();

    builder.EntitySet<WeatherForecast>("WeatherForecasts");

    return builder.GetEdmModel();
}

public void ConfigureServices(IServiceCollection services) {
    services
        .AddControllers()

        // Added this AddOData call
        .AddOData(opt => {
            opt
                .Count()
                .Filter()
                .Select()
                .OrderBy()
                .SetMaxTop(20)
                .AddRouteComponents("api", GetEdmModel());
        });

    services.AddSwaggerGen(c => {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "SwaggerODataTest", Version = "v1" });
    });
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,控制器中只有一个默认方法——即定义了一个端点。Get()然而添加属性Route()会导致以下异常

Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException:
Conflicting method/path combination "GET api/WeatherForecasts" for actions - 
SwaggerODataTest.Controllers.WeatherForecastsController.Get (SwaggerODataTest),
SwaggerODataTest.Controllers.WeatherForecastsController.Get (SwaggerODataTest).
Actions require a unique method/path combination for Swagger/OpenAPI 3.0.
Use ConflictingActionsResolver as a workaround
Run Code Online (Sandbox Code Playgroud)

看起来 OData 和 ASP.NET Core (??) 都在处理同一个控制器两次并生成重复的(冲突的)端点。难道我做错了什么?有没有办法告诉 ASP.NET Core 不要解析控制器来生成路由端点并让 OData 来完成?

小智 3

AddRouteComponents 基于 OData 路由约定添加路由 ( https://learn.microsoft.com/en-us/odata/webapi/built-in-routing-conventions )

以下是一个很好的解释: /sf/answers/4743492561/

只需删除控制器中的 Route 和 HttpGet 属性,它就应该可以工作。

我刚刚开始使用 OData,所以我也很挣扎:-)。在这种情况下,以下信息对于其他正在挣扎的人来说是有用的信息......

我执行以下操作(在 ASP.Net Core 6program.cs 中,但逻辑可以轻松转移到 ASP.Net Core 5startup.cs)以拥有 $top、$count、[controller](key)、[controller]/钥匙等等。我在此示例中使用实体框架(.Net 6、Sql Server)。

string connectionString = builder.Configuration.GetConnectionString("ExampleDb");
builder.Services.AddDbContext<ExampleDbContext>(options => options.UseSqlServer(connectionString));

builder.Services.AddControllers().AddOData(options => {
    options.EnableQueryFeatures();
    var routeOptions = options.AddRouteComponents(EdmModelBuilder.GetEdmModel()).RouteOptions;
    
    routeOptions.EnableQualifiedOperationCall =
    routeOptions.EnableKeyAsSegment = 
    routeOptions.EnableKeyInParenthesis = true;
});
Run Code Online (Sandbox Code Playgroud)

这位于单独的 EdmModelBuilder.cs 文件中。(测试是.Net 6实体框架模型)

public static class EdmModelBuilder {
    public static IEdmModel GetEdmModel() {
        var builder = new ODataConventionModelBuilder();
        builder.EntitySet<Test>("Test");

        return builder.GetEdmModel();
    }
}
Run Code Online (Sandbox Code Playgroud)

这在我的控制器中。(键可以是另一个主要数据类型或字符串;IActionResult 与 swagger 配合良好;没有 Route 或 HttpGet 属性)

public class TestController : ODataController {
    private readonly ExampleDbContext _context;
    private readonly ILogger<TestController> _logger;

    public TestController(ILogger<TestController> logger, ExampleDbContext context) {
        _logger = logger;
        _context = context;
    }

    [EnableQuery(PageSize = 10)]
    public IActionResult Get() {
        return Ok(_context.Test);
    }
    public IActionResult Get(int key) {
        return Ok(_context.Test.SingleOrDefault(c => c.Id == key));
    }
}
Run Code Online (Sandbox Code Playgroud)