【问题标题】:How to set up Swashbuckle vs Microsoft.AspNetCore.Mvc.Versioning如何设置 Swashbuckle 与 Microsoft.AspNetCore.Mvc.Versioning
【发布时间】:2017-04-17 05:29:39
【问题描述】:

我们有 asp.net 核心 webapi。我们添加了Microsoft.AspNetCore.Mvc.VersioningSwashbuckle 以拥有招摇的用户界面。 我们将控制器指定为:

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ContactController : Controller
{

当我们运行 swagger ui 时,我们会在路由中获取版本作为参数:

如何为路由设置默认的“v1”? 如果版本 2 上台,两个版本如何支持 swagger ui?

【问题讨论】:

标签: c# asp.net-core swagger swashbuckle


【解决方案1】:

目前 Swashbuckle 和 Microsoft.AspNetCore.Mvc.Versioning 是朋友。它运作良好。我刚刚在 VS2017 中创建了测试项目并检查了它是如何工作的。

首先包含这两个nuget包:

<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="1.2.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />

Startup.cs 中配置所有内容(阅读我的 cmets):

public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();


        // Configure versions 
        services.AddApiVersioning(o =>
        {
            o.AssumeDefaultVersionWhenUnspecified = true;
            o.DefaultApiVersion = new ApiVersion(1, 0);
        });

        // Configure swagger
        services.AddSwaggerGen(options =>
        {
            // Specify two versions 
            options.SwaggerDoc("v1", 
                new Info()
                {
                    Version = "v1",
                    Title = "v1 API",
                    Description = "v1 API Description",
                    TermsOfService = "Terms of usage v1"
                });

            options.SwaggerDoc("v2",
                new Info()
                {
                    Version = "v2",
                    Title = "v2 API",
                    Description = "v2 API Description",
                    TermsOfService = "Terms of usage v2"
                });

            // This call remove version from parameter, without it we will have version as parameter 
            // for all endpoints in swagger UI
            options.OperationFilter<RemoveVersionFromParameter>();

            // This make replacement of v{version:apiVersion} to real version of corresponding swagger doc.
            options.DocumentFilter<ReplaceVersionWithExactValueInPath>();

            // This on used to exclude endpoint mapped to not specified in swagger version.
            // In this particular example we exclude 'GET /api/v2/Values/otherget/three' endpoint,
            // because it was mapped to v3 with attribute: MapToApiVersion("3")
            options.DocInclusionPredicate((version, desc) =>
            {
                var versions = desc.ControllerAttributes()
                    .OfType<ApiVersionAttribute>()
                    .SelectMany(attr => attr.Versions);

                var maps = desc.ActionAttributes()
                    .OfType<MapToApiVersionAttribute>()
                    .SelectMany(attr => attr.Versions)
                    .ToArray();

                return versions.Any(v => $"v{v.ToString()}" == version) && (maps.Length == 0 || maps.Any(v => $"v{v.ToString()}" == version));
            });

        });

    }

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        app.UseSwagger();
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint($"/swagger/v2/swagger.json", $"v2");
            c.SwaggerEndpoint($"/swagger/v1/swagger.json", $"v1");
        });
        app.UseMvc();
    }

有两个类可以解决问题:

public class RemoveVersionFromParameter : IOperationFilter
{
    public void Apply(Operation operation, OperationFilterContext context)
    {
        var versionParameter = operation.Parameters.Single(p => p.Name == "version");
        operation.Parameters.Remove(versionParameter);
    }
}

public class ReplaceVersionWithExactValueInPath : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
    {
        swaggerDoc.Paths = swaggerDoc.Paths
            .ToDictionary(
                path => path.Key.Replace("v{version}", swaggerDoc.Info.Version),
                path => path.Value
            );
    }
}

RemoveVersionFromParameter 从 swagger UI 中删除了这个文本框:

ReplaceVersionWithExactValueInPath 改变这个:

到这里:

Controller 类现在如下所示:

[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1")]
[ApiVersion("2")]
public class ValuesController : Controller
{
    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET api/values/5
    [HttpGet("{id}")]
    public string Get(int id)
    {
        return "value";
    }

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

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

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


    [HttpGet("otherget/one")]
    [MapToApiVersion("2")]
    public IEnumerable<string> Get2()
    {
        return new string[] { "value1", "value2" };
    }

    /// <summary>
    /// THIS ONE WILL BE EXCLUDED FROM SWAGGER Ui, BECAUSE v3 IS NOT SPECIFIED. 'DocInclusionPredicate' MAKES THE
    /// TRICK 
    /// </summary>
    /// <returns></returns>
    [HttpGet("otherget/three")]
    [MapToApiVersion("3")]
    public IEnumerable<string> Get3()
    {
        return new string[] { "value1", "value2" };
    }
}

代码:https://gist.github.com/Alezis/bab8b559d0d8800c994d065db03ab53e

【讨论】:

  • 我正在使用 swashbuckle.aspnetcore v3.0.0 并在 ConfigureServices 方法中更改了版本读取代码。但我收到以下错误“无法加载 API 定义。获取错误内部服务器错误 /swagger/v1/swagger.json”。可能是什么问题?
  • 这个答案已经过时了。您应该更新它,或编辑您的答案以声明它仅适用于 Swashbuckle 4.x。现在安装 Swashbuckle 的任何人都将获得最新的 5.x,并且此解决方案将不起作用:github.com/domaindrivendev/Swashbuckle.AspNetCore/releases/tag/…
【解决方案2】:

如果使用 .Net Core 3,基本上我已经采用了 @Alezis 的解决方案并将其更新为使用 .Net Core 3:

public void ConfigureServices(IServiceCollection services)
    {
     ....
        services.AddSwaggerGen(options =>
        {
            options.SwaggerDoc("v1", new OpenApiInfo() { Title = "My API", Version = "v1" });
            options.OperationFilter<RemoveVersionFromParameter>();

            options.DocumentFilter<ReplaceVersionWithExactValueInPath>();

        });
      ...
    }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    });
   ...
}

public class RemoveVersionFromParameter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var versionParameter = operation.Parameters.Single(p => p.Name == "version");
        operation.Parameters.Remove(versionParameter);
    }
}

public class ReplaceVersionWithExactValueInPath : IDocumentFilter
{
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        var paths = new OpenApiPaths();
        foreach (var path in swaggerDoc.Paths)
        {
            paths.Add(path.Key.Replace("v{version}", swaggerDoc.Info.Version), path.Value);
        }
        swaggerDoc.Paths = paths;
    }
}

【讨论】:

    【解决方案3】:

    @Alezis 不错的方法,但如果您使用的是最新版本的 Microsoft.AspNetCore.Mvc.Versioning (2.3.0) 库,ControllerAttributes()ActionAttributes() 已弃用,您可以更新 DocInclusionPredicate 如下:

    options.DocInclusionPredicate((version, desc) =>
    {
        if (!desc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
        var versions = methodInfo.DeclaringType
            .GetCustomAttributes(true)
            .OfType<ApiVersionAttribute>()
            .SelectMany(attr => attr.Versions);
         return versions.Any(v => $"v{v.ToString()}" == version);
    });
    

    Swashbuckle.AspNetCoregithub 项目对我帮助很大。

    【讨论】:

      【解决方案4】:

      Asp.core 2.+中添加这个类:

      public class ApiVersionOperationFilter : IOperationFilter
          {
              public void Apply(Operation operation, OperationFilterContext context)
              {
                  var actionApiVersionModel = context.ApiDescription.ActionDescriptor?.GetApiVersion();
                  if (actionApiVersionModel == null)
                  {
                      return;
                  }
      
                  if (actionApiVersionModel.DeclaredApiVersions.Any())
                  {
                      operation.Produces = operation.Produces
                          .SelectMany(p => actionApiVersionModel.DeclaredApiVersions
                              .Select(version => $"{p};v={version.ToString()}")).ToList();
                  }
                  else
                  {
                      operation.Produces = operation.Produces
                          .SelectMany(p => actionApiVersionModel.ImplementedApiVersions.OrderByDescending(v => v)
                              .Select(version => $"{p};v={version.ToString()}")).ToList();
                  }
              }
         }
      

      下一步startupconfigureServices方法中添加以下代码:

      services.AddSwaggerGen(c =>
                  {
                      c.SwaggerDoc("v1", new Info { Title = "Versioned Api v1", Version = "v1" });
      
                      c.OperationFilter<ApiVersionOperationFilter>();
              });
      

      然后startupconfigure方法中添加以下代码:

                  app.UseSwagger();
                  app.UseSwaggerUI(c =>
                  {                
                          c.SwaggerEndpoint("/swagger/v1/swagger.json", "Versioned Api v1");
                          c.RoutePrefix = string.Empty;
      

      Asp.core 3.+ 中添加这些类:

      public class RemoveVersionFromParameter : IOperationFilter
          {
              public void Apply(OpenApiOperation operation, OperationFilterContext context)
              {
                      if (!operation.Parameters.Any())
                          return;
      
                      var versionParameter = operation.Parameters
                          .FirstOrDefault(p => p.Name.ToLower() == "version");
      
                      if (versionParameter != null)
                          operation.Parameters.Remove(versionParameter);
              }
          }
      
       public class ReplaceVersionWithExactValueInPath : IDocumentFilter
          {
              public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
              {
                  if (swaggerDoc == null)
                      throw new ArgumentNullException(nameof(swaggerDoc));
      
                  var replacements = new OpenApiPaths();
      
                  foreach (var (key, value) in swaggerDoc.Paths)
                  {
                      replacements.Add(key.Replace("v{version}", swaggerDoc.Info.Version,
                              StringComparison.InvariantCulture), value);
                  }
      
                  swaggerDoc.Paths = replacements;
              }
          }
      

      下一步startupConfigureServices方法中添加以下代码:

      protected virtual IEnumerable<int> Versions => new[] {1};
      
       services.AddSwaggerGen(options =>
                  {
                      Versions.ToList()
                          .ForEach(v =>
                              options.SwaggerDoc($"v{v}",
                                  new OpenApiInfo
                                  {
                                      Title = $"Versioned Api:v{v}", Version = $"v{v}"
                                  }));
      
                      options.OperationFilter<RemoveVersionFromParameter>();
                      options.DocumentFilter<ReplaceVersionWithExactValueInPath>();
                      options.RoutePrefix = string.Empty;
                  });
      

      然后startupconfigure方法中添加以下代码:

                  app.UseSwagger();
      
                  app.UseSwaggerUI(options =>
                 {
                     Versions.ToList()
                         .ForEach(v => options.SwaggerEndpoint($"/swagger/v{v}/swagger.json", $"Versioned Api:v{v}"));
      
                     options.RoutePrefix = string.Empty;
                 });
      

      【讨论】:

        【解决方案5】:

        更新到 .net core 3 时出现以下错误:

        '无法将'System.Collections.Generic.Dictionary`2[System.String,Microsoft.OpenApi.Models.OpenApiPathItem]'类型的对象转换为'Microsoft.OpenApi.Models.OpenApiPaths'类型。'

        通过将代码更改为:

        public class ReplaceVersionWithExactValueInPath : IDocumentFilter
        {
            public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
            {
                if (swaggerDoc == null)
                    throw new ArgumentNullException(nameof(swaggerDoc));
        
                var replacements = new OpenApiPaths();
        
                foreach (var (key, value) in swaggerDoc.Paths)
                {
                    replacements.Add(key.Replace("{version}", swaggerDoc.Info.Version, StringComparison.InvariantCulture), value);
                }
        
                swaggerDoc.Paths = replacements;
            }
        }
        

        【讨论】:

          【解决方案6】:

          您可以使用 Microsoft 提供的库来向 API Explorer 添加版本,而不是调整 OpenAPI 文档。这样,在 Swashbuckle(或其他工具链)需要之前提供版本,并允许您避免自定义代码。

          Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer

          添加软件包和这段代码后,我能够正确配置版本。

          services.AddVersionedApiExplorer(
              options =>
              {
              // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
              // note: the specified format code will format the version as "'v'major[.minor][-status]"
              options.GroupNameFormat = "'v'VVV";
          
              // note: this option is only necessary when versioning by url segment. the SubstitutionFormat
              // can also be used to control the format of the API version in route templates
              options.SubstituteApiVersionInUrl = true;
              }
          );
          

          【讨论】:

          • 很酷更简单的解决方案,完美运行:) 谢谢。
          • 这样一个更好的整体解决方案,希望它更高。
          【解决方案7】:

          @ArlanG 它帮助了我,谢谢。它适用于 Asp.Net Core 3.1。从我的角度来看,有一个小的澄清。如果您想获得更多类似的行为,例如 DocInclusionPredicate() 的主要答案@Alezis 方法实现可以是:

          options.DocInclusionPredicate((version, desc) =>
                      {
          
                          if (!desc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
                          var versions = methodInfo.DeclaringType
                              .GetCustomAttributes(true)
                              .OfType<ApiVersionAttribute>()
                              .SelectMany(attr => attr.Versions);
          
          
                          var maps = methodInfo
                              .GetCustomAttributes(true)
                              .OfType<MapToApiVersionAttribute>()
                              .SelectMany(attr => attr.Versions)
                              .ToArray();
          
                          return versions.Any(v => $"v{v.ToString()}" == version)
                                 && (!maps.Any() || maps.Any(v => $"v{v.ToString()}" == version));
                      });
          

          在这种情况下,当您在 SwaggerUi 页面上选择版本时,它将仅显示映射到该版本的控制器方法。

          【讨论】:

            【解决方案8】:

            我发现使用the method ArlanG highlighted 需要{00:00:00.0001905} 在运行时完成

            var versions = methodInfo.DeclaringType.GetConstructors().SelectMany(x =>
                x.DeclaringType.CustomAttributes.Where(y => 
                    y.AttributeType == typeof(ApiVersionAttribute))
                .SelectMany(z => z.ConstructorArguments.Select(i=>i.Value)));
            

            拍了{00:00:00.0000626}

            我知道我们谈论的是细微差别,但仍然如此。

            【讨论】:

              猜你喜欢
              • 2019-02-15
              • 2019-09-22
              • 2019-03-10
              • 2017-10-26
              • 1970-01-01
              • 1970-01-01
              • 2020-10-09
              • 2018-09-26
              • 1970-01-01
              相关资源
              最近更新 更多