关于 c#:Swagger 无法与多个版本的 ASP.NET WebApi 应用程序一起正常工作

Swagger not working correctly with multiple versions of ASP.NET WebApi app

请帮帮我,一开始看起来很容易,现在我在项目中迟到了:

我正在尝试为 ASP.NET WebApi 项目以及 Swagger 设置 API 版本控制。 API 版本控制工作正常,调用不同的版本会返回正确的结果(见下文)。

相反,Swagger 无法同时提供这两个版本。在调试时,我注意到当在 SwaggerConfig.cs 中调用 c.MultipleApiVersions(...) 时,apiDesc.ActionDescriptor.ControllerDescriptor 报告的控制器始终是 PingController 而不是 Ping11Controller.

有人可以指出需要做些什么来解决这个问题并让 Swagger 也适用于这两个版本吗?

下面是 API 版本控制的代码和证明,而 Swagger 仅适用于 v1.0。

谢谢!

调用 API v1.0 有效:
enter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
  "swagger":"2.0",
  "info":{
     "version":"v1.0",
     "title":"My API v1.0"
   },
  "host":"localhost:50884",
  "schemes":[
     "http"
   ],
  "paths":{
     "/api/ping":{
        "get":{
           "tags":[
              "Ping"
            ],
           "summary":"Get a pong.",
           "operationId":"GetAPong",
           "consumes":[
            ],
           "produces":[
              "application/json",
              "text/json",
              "application/xml",
              "text/xml"
            ],
           "responses":{
              "200":{
                 "description":"OK"
               },
              "404":{
                 "description":"NotFound"
               }
            }
         }
      }
   },
  "definitions":{
   }
}

v1.1 的 Swagger 为空:
(http://localhost:50884/v1.1/swagger)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "swagger":"2.0",
  "info":{
     "version":"v1.1",
     "title":"My API v1.1"
   },
  "host":"localhost:50884",
  "schemes":[
     "http"
   ],
  "paths":{
   },
  "definitions":{
   }
}

代码

App_Start\\\\\\\\WebApiConfig.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.AddApiVersioning(options => {
            options.ReportApiVersions = true;
        });

        var constraintResolver = new System.Web.Http.Routing.DefaultInlineConstraintResolver();
        constraintResolver.ConstraintMap.Add("apiVersion", typeof(Microsoft.Web.Http.Routing.ApiVersionRouteConstraint));
        config.MapHttpAttributeRoutes(constraintResolver);

        config.Routes.MapHttpRoute(
            name:"DefaultApi",
            routeTemplate:"api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

App_Start\\\\\\\\SwaggerConfig.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class SwaggerConfig
{
    static string XmlCommentsFilePath
    {
        get
        {
            var basePath = System.AppDomain.CurrentDomain.RelativeSearchPath;
            var fileName = typeof(SwaggerConfig).GetTypeInfo().Assembly.GetName().Name +".xml";
            return Path.Combine(basePath, fileName);
        }
    }

    public static void Register()
    {
        var configuration = GlobalConfiguration.Configuration;
        GlobalConfiguration.Configuration.EnableSwagger("{apiVersion}/swagger", c => {
                c.OperationFilter<SwaggerDefaultValues>();
                c.MultipleApiVersions((System.Web.Http.Description.ApiDescription apiDesc, string targetApiVersion) =>
                {
                    var attr = apiDesc.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<Microsoft.Web.Http.ApiVersionAttribute>().FirstOrDefault();
                    if (attr == null && (targetApiVersion =="v1" || targetApiVersion =="v1.0")) return true;
                    var match = (attr != null) && (attr.Versions.FirstOrDefault(v =>"v" + v.ToString() == targetApiVersion) != null);
                    return match;
                },
                (vc) =>
                {
                    vc.Version("v1.1","My API v1.1");
                    vc.Version("v1.0","My API v1.0");
                });

                c.IncludeXmlComments(SwaggerConfig.XmlCommentsFilePath);
            })
            .EnableSwaggerUi(c => {
                c.DocExpansion(DocExpansion.List);
                c.EnableDiscoveryUrlSelector();
            });
    }
}

v1.0 和 v1.1 的控制器(位于同一命名空间中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[ApiVersion("1.0")]
[RoutePrefix("api")]
[ControllerName("Ping")]
public class PingController : ApiController
{
    [HttpGet]
    [Route("ping")]
    [SwaggerOperation("GetAPong")]
    [SwaggerResponse(HttpStatusCode.OK)]
    [SwaggerResponse(HttpStatusCode.NotFound)]
    public string Get()
    {
        return"Pong v1.0";
    }
}

[ApiVersion("1.1")]
[RoutePrefix("api")]
[ControllerName("Ping")]
public class Ping11Controller : ApiController
{
    [HttpGet]
    [Route("ping")]
    [SwaggerOperation("GetAPong")]
    [SwaggerResponse(HttpStatusCode.OK)]
    [SwaggerResponse(HttpStatusCode.NotFound)]
    public string Get()
    {
        return"Pong v1.1";
    }
}

包裹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net46" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net46" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net46" />
<package id="Microsoft.AspNet.WebApi.Versioning" version="2.1.0" targetFramework="net46" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net46" />
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.7" targetFramework="net46" />
<package id="Microsoft.IdentityModel.Logging" version="1.1.4" targetFramework="net46" />
<package id="Microsoft.IdentityModel.Tokens" version="5.1.4" targetFramework="net46" />
<package id="Microsoft.Net.Compilers" version="2.3.2" targetFramework="net46" developmentDependency="true" />
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net46" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net46" />
<package id="NLog" version="4.4.12" targetFramework="net46" />
<package id="Swashbuckle" version="5.6.0" targetFramework="net46" />
<package id="Swashbuckle.Core" version="5.6.0" targetFramework="net46" />
<package id="System.IdentityModel.Tokens.Jwt" version="5.1.4" targetFramework="net46" />
<package id="WebActivatorEx" version="2.2.0" targetFramework="net46" />
</packages>


已解决:

  • 添加 Microsoft.AspNet.WebApi.Versioning.ApiExplorer 包
  • 使用如下版本的 API 资源管理器(请注意,由于初始化问题,我不得不将代码从 SwaggerConfig.cs 移至 WebApiConfig.cs):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
        var apiExplorer = config.AddVersionedApiExplorer(options => {
            options.GroupNameFormat ="'v'VVV";
        });

        var versionSupportResolver = new Func<ApiDescription, string, bool>((apiDescription, version) => apiDescription.GetGroupName() == version);

        var versionInfoBuilder = new Action<VersionInfoBuilder>(info => {
            foreach (var group in apiExplorer.ApiDescriptions)
            {
                info.Version(group.Name, $"MyAPI v{group.ApiVersion}");
            }
        });

        config
            .EnableSwagger("{apiVersion}/swagger", swagger => {
                swagger.OperationFilter<SwaggerDefaultValues>();
                swagger.MultipleApiVersions(versionSupportResolver, versionInfoBuilder);
                swagger.IncludeXmlComments(WebApiConfig.XmlCommentsFilePath);
            })
            .EnableSwaggerUi(swaggerUi => {
                swaggerUi.EnableDiscoveryUrlSelector();
                swaggerUi.DocExpansion(DocExpansion.List);
            });

  • enter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [RoutePrefix("api/v1/value")]
    public class ValueV1Controller : ApiController
    {
        [Route("get")]
        public IEnumerable<string> Get()
        {
            return new string[] {"value1","value2" };
        }
    }

    ValueV2Controller.cs

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [RoutePrefix("api/v2/value")]
    public class ValueV2Controller : ApiController
    {
        [Route("get")]
        public IEnumerable<string> Get()
        {
            return new string[] {"value1.2","value2.2" };
        }
    }

    SwaggerConfig.cs

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class SwaggerConfig
    {
        public static void Register()
        {
            var thisAssembly = typeof(SwaggerConfig).Assembly;

            GlobalConfiguration.Configuration
                .EnableSwagger(c =>
                {
                    c.MultipleApiVersions(
                        (apiDesc, version) =>
                        {
                            var path = apiDesc.RelativePath.Split('/');
                            var pathVersion = path[1];

                            return CultureInfo.InvariantCulture.CompareInfo.IndexOf(pathVersion, version, CompareOptions.IgnoreCase) >= 0;
                        },
                        (vc) =>
                        {
                            vc.Version("v2","Swashbuckle Dummy API V2");
                            vc.Version("v1","Swashbuckle Dummy API V1");
                        });
                })
                .EnableSwaggerUi(c =>
                {
                    c.EnableDiscoveryUrlSelector();
                });
        }
    }


    我遇到了同样的错误,但为我解决的是将 EnableSwagger 扩展方法中的 templateRoute 从 swagger/docs/{apiVersion} 更改为 swagger/{apiVersion}/docs。
    我认为当 api 版本位于路由末尾时,次要版本存在解析问题(可能是因为它包含 \\'.\\')。