【问题标题】:Core 3.1 C# multiple API versions Swagger UI not separating endpoints properlyCore 3.1 C# 多个 API 版本 Swagger UI 没有正确分离端点
【发布时间】:2021-10-20 04:20:18
【问题描述】:

我遇到的问题与其他与 Swagger UI 中多个 API 文档有关的问题略有不同。

我按预期生成了两个 .json 文档,并且 UI 右上角的下拉菜单允许我在 v1 文档和 v2 文档之间切换。

这是我的问题: v1 文档显示 v1 API 的一个可用操作。但是,v2 文档显示了 v1 和 v2 的可用操作。

这是我的startup.cs 文件中的完整代码:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using netCoreV3_1ApiStarter.Entities;
using Microsoft.OpenApi.Models;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Versioning;

namespace netCoreV3_1ApiStarter
{
  public class Startup
  {
    public Startup(IConfiguration configuration)
    {
      Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
      #region API Versioning
      services.AddApiVersioning(c =>
      {
        c.DefaultApiVersion = new ApiVersion(1, 0);
        c.AssumeDefaultVersionWhenUnspecified = true;
        c.ReportApiVersions = true;
        c.ApiVersionReader = new UrlSegmentApiVersionReader();
      });
      #endregion

      services.AddControllers();

      services.AddSwaggerGen(c =>
      {
        c.SwaggerDoc("v1", new OpenApiInfo
        {
          Title = $"{Configuration.GetSection("ApplicationName").Value} API",
          Version = "v1",
          Description = $"API v1 methods available for the {Configuration.GetSection("ApplicationName").Value} system",
          Contact = new OpenApiContact
          {
            Email = "blah@blah.com",
            Name = "Nunya",
            Url = new System.Uri("http://www.apple.com")
          }
        });

        c.SwaggerDoc("v2", new OpenApiInfo
        {
          Title = $"{Configuration.GetSection("ApplicationName").Value} API",
          Version = "v2",
          Description = $"API v2 methods available for the {Configuration.GetSection("ApplicationName").Value} system",
          Contact = new OpenApiContact
          {
            Email = "blah@blah.com",
            Name = "Nunya",
            Url = new System.Uri("http://www.apple.com")
          }
        });

        c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
        c.DocInclusionPredicate((docName, apiDesc) => apiDesc.GroupName == docName);
      });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }

      app.UseSwagger();

      app.UseSwaggerUI(c =>
      {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", $"{Configuration.GetSection("ApplicationName").Value} API v1");
        c.SwaggerEndpoint("/swagger/v2/swagger.json", $"{Configuration.GetSection("ApplicationName").Value} API v2");
        c.RoutePrefix = string.Empty;
      });

      app.UseRouting();
      app.UseAuthorization();
      app.UseEndpoints(endpoints =>
      {
        endpoints.MapControllers();
      });
    }
  }
}

我将我的控制器按 API 版本分隔到子文件夹中,以尝试组织和防止命名空间冲突等。

v1 WeatherForecastController.cs 文件:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

namespace netCoreV3_1ApiStarter.Controllers.v1
{
  [ApiVersion("1.0")]
  [ApiExplorerSettings(GroupName = "v1")]
  [Route("api/v{version:apiVersion}/[controller]")]
  [ApiController]
  public class WeatherForecastController : ControllerBase
  {
    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
      _logger = logger;
    }

    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();
    }
  }
}

还有,我的 v2 WeatherForecastController.cs:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace netCoreV3_1ApiStarter.Controllers.v2
{
  [ApiVersion("2.0")]
  [ApiExplorerSettings(GroupName = "v2")]
  [Route("api/v{version:apiVersion}/[controller]")]
  [ApiController]
  public class WeatherForecastController : ControllerBase
  {
    // GET: api/<WeatherForecastController>
    [HttpGet]
    public IEnumerable<string> Get()
    {
      return new string[] { "value1", "value2" };
    }

    // GET api/<WeatherForecastController>/5
    [HttpGet("{id}")]
    public string Get(int id)
    {
      return $"API v2 {id}";
    }

    // POST api/<WeatherForecastController>
    [HttpPost]
    public void Post([FromBody] string value)
    {
    }

    // PUT api/<WeatherForecastController>/5
    [HttpPut("{id}")]
    public void Put(int id, [FromBody] string value)
    {
    }

    // DELETE api/<WeatherForecastController>/5
    [HttpDelete("{id}")]
    public void Delete(int id)
    {
    }
  }
}

关于让 v1 屏幕显示可用 v1 操作和 v2 屏幕显示可用 v2 操作的任何建议?

编辑 10/21/2021: 修复了第二个 SwaggerDoc 对象定义中的错字。当它应该引用 v2 时错误地引用了 v1。但是,性能没有差异。

【问题讨论】:

  • 您在两次调用 c.SwaggerDoc 时都有“Version = "v1"",不确定它是否有影响。
  • @Oscar 是的,这是一个错字。不幸的是,仍然没有改变行为。

标签: c# asp.net-core swagger


【解决方案1】:

根据您的要求,是不是在切换v2版本时只显示v2的部分API?

你可以这样做:

使用的版本

ASP.NET Core 3.1
Swashbuckle.AspNetCore: 5.4.1

首先我创建了2个版本的文件夹和控制器:

因此,每个控制器的命名空间都对应其文件夹,如下所示:

V1 版本

namespace WebApplication129.Controllers.V1
{
    [ApiController]
    [Route("api/v1/[controller]")]
    public class HomeController : ControllerBase
    {

       [Route("test")]
        [HttpGet]
        public string Test()
        {
            return "v1 test";
        }
    }
}

V2 版本

namespace WebApplication129.Controllers.V2
{
    [ApiController]
    [Route("api/v2/[controller]")]
    public class HomeController : ControllerBase
    {

        [Route("test")]
        [HttpGet]
        public string Test()
        {
            return "v2 test";
        }
    }
}

然后创建一个协议通知Swagger,这样我们就可以控制Swagger如何生成Swagger文档,从而控制UI。

创建以下类:

public class GroupingByNamespaceConvention : IControllerModelConvention
{
    public void Apply(ControllerModel controller)
    {
        var controllerNamespace = controller.ControllerType.Namespace;
        var apiVersion = controllerNamespace.Split(".").Last().ToLower();
        if (!apiVersion.StartsWith("v")) { apiVersion = "v1"; }
        controller.ApiExplorer.GroupName = apiVersion;
    }
}

我们所做的是根据命名空间的最后一段对控制器进行分组。因此,名称空间以 v1 结尾的控制器将被分组到名称“v1”下,以 v2 结尾的控制器将被分组为“v2”,等等。

现在我们必须应用约定。为此,我们转到 ConfigureServices 中的 AddControllers 并添加约定:

services.AddControllers(options =>
{
    options.Conventions.Add(new GroupingByNamespaceConvention());
});

最终完整的startup.cs配置如下:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using System;
using WebApplication129.Controllers.conf;

namespace WebApplication129
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers(options =>
            {
                options.Conventions.Add(new GroupingByNamespaceConvention());
            });

            services.AddSwaggerGen(config =>
            {
                var titleBase = "Test API";
                var description = "This is a Web API for Test operations";
                var TermsOfService = new Uri("https://xxxxxx");
                var License = new OpenApiLicense()
                {
                    Name = "Test"
                };
                var Contact = new OpenApiContact()
                {
                    Name = "Test",
                    Email = "Test@hotmail.com",
                    Url = new Uri("https://xxxxxx")
                };

                config.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1",
                    Title = titleBase + " v1",
                    Description = description,
                    TermsOfService = TermsOfService,
                    License = License,
                    Contact = Contact
                });

                config.SwaggerDoc("v2", new OpenApiInfo
                {
                    Version = "v2",
                    Title = titleBase + " v2",
                    Description = description,
                    TermsOfService = TermsOfService,
                    License = License,
                    Contact = Contact
                });
            });
        }

       
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseSwagger();
            app.UseSwaggerUI(config =>
            {
                config.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
                config.SwaggerEndpoint("/swagger/v2/swagger.json", "v2");
            });
            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

访问网址:https://localhost:yourport/swagger/index.html

结果:

【讨论】:

  • 感谢您的帮助。在我删除了两个原始控制器之后,那个额外的类就成功了。一定存在某种与命名空间的内部冲突,导致问题以及缺少缺少的GroupByNamespaceConvention。它现在按预期工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-14
  • 1970-01-01
  • 1970-01-01
  • 2020-09-28
相关资源
最近更新 更多