Swashbuckle 使 asp.net core 3.1 api swagger ui 中具有相同动词但多个路由的端点的所有路由参数都是强制性的

Swashbuckle makes all route parameter mandatory for endpoints with same verb but multiple routes in asp.net core 3.1 api swagger ui

我正在处理 asp.net core 2.2 项目并升级到 asp.net core 3.1,还将 Swashbuckle.AspNetCore 升级到 5.0.0。升级后,我可以看到 swagger 生成的端点发生了变化。

我有一个 [HttpDelete] 的端点,有两条不同的路线,如下所示:

[HttpDelete("{id}")]
[HttpDelete("{id}/some/{anotherId}")]
public IActionResult Delete(int id, int anotherId) 
{
    return NoContent();
}

[HttpDelete("{id}")]

这里只需要 id 参数。但是 idanotherId 参数也在这里标记为必需。 这是错误的.

[HttpDelete("{id}/some/{anotherId}")]

idanotherId 参数在这里都应该是必需的。 这是正确的。

这是我的 Startup.cs:

配置服务:

services.AddVersionedApiExplorer(options =>
{
    options.GroupNameFormat = "'v'VV";
});

services.AddApiVersioning(options =>
{
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.ReportApiVersions = true;
    options.ApiVersionReader = new HeaderApiVersionReader("x-api-version");
});

var apiVersionDescriptionProvider =
    services.BuildServiceProvider().GetService<IApiVersionDescriptionProvider>();

services
    .AddSwaggerGen(options =>
{
    foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
    {
        options.SwaggerDoc(
            $"TestDocumentOpenAPISpecification{description.GroupName}",
            new Microsoft.OpenApi.Models.OpenApiInfo
            {
                Title = "Test Document API",
                Version = description.ApiVersion.ToString(),
                Description = "Test",
                Contact = new Microsoft.OpenApi.Models.OpenApiContact
                {
                    Email = "Test@test.com",
                    Name = "Test Team",
                    Url = new Uri("https://www.test.com")
                }
            });
    }

    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "Input your JWT Authorization header to access this API. Example: \"Authorization: Bearer {token}\"",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });
    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            new string[] { }
        }
    });

    options.DocInclusionPredicate((documentName, apiDescription) =>
    {
        var actionApiVersionModel = apiDescription.ActionDescriptor
        .GetApiVersionModel(ApiVersionMapping.Explicit | ApiVersionMapping.Implicit);

        if (actionApiVersionModel == null)
        {
            return true;
        }

        if (actionApiVersionModel.DeclaredApiVersions.Any())
        {
            return actionApiVersionModel.DeclaredApiVersions.Any(v =>
            $"TestDocumentOpenAPISpecificationv{v.ToString()}" == documentName);
        }

        return actionApiVersionModel.ImplementedApiVersions.Any(v =>
            $"TestDocumentOpenAPISpecificationv{v.ToString()}" == documentName);
    });

    //var xmlCommentsFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    //var xmlCommentsFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentsFile);

    //options.IncludeXmlComments(xmlCommentsFullPath);
});

配置:

app.UseSwagger();

app.UseSwaggerUI(options =>
{
    foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions)
    {
        options.SwaggerEndpoint(
            $"/swagger/TestDocumentOpenAPISpecification{description.GroupName}/swagger.json",
            $"Test Document API - {description.GroupName.ToUpperInvariant()}");
    }
    options.RoutePrefix = string.Empty;

    options.DefaultModelExpandDepth(2);
    options.DefaultModelRendering(Swashbuckle.AspNetCore.SwaggerUI.ModelRendering.Model);
    options.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
    options.DisplayRequestDuration();
    options.EnableValidator();
    options.EnableFilter();
    options.EnableDeepLinking();
    options.DisplayOperationId();
});

生成的 swagger 使得 anotherId 在两条路由中都是强制性的。以前不是这样的。我尝试将 Name 添加到两条路线,但仍然失败。请协助我哪里错了。

经过一些分析后,我使用 IOperationFilter.

使它可以工作
public class DeleteOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (context.ApiDescription.HttpMethod == "DELETE" && context.MethodInfo.Name == "Delete")
        {
            foreach (var parameter in context.ApiDescription.ParameterDescriptions)
            {
                if (parameter.RouteInfo == null)
                {
                    operation.Parameters.Single(x => x.Name.Equals(parameter.Name)).Required = false;
                }
            }
            return;
        }
    }
}

并将其添加到 ConfigureServices

services.AddSwaggerGen(options =>
{
    ...
    options.OperationFilter<DeleteOperationFilter>();
};

我不确定这是最好的方法,但它确实有效。如有错误请指正