如何设置Swashbuckle与Microsoft.AspNetCore.Mvc.Versioning

时间:2016-12-02 10:24:54

标签: asp.net-core asp.net-core-mvc swagger swashbuckle

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

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

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

如何为路线设置默认“v1”? 如果版本2出现在哪个阶段,那两个版本如何支持swagger ui?

8 个答案:

答案 0 :(得分:17)

目前,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中的所有内容(阅读我的评论):

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移除此文本框:

enter image description here

ReplaceVersionWithExactValueInPath改变了这一点:

enter image description here

到此:

enter image description here

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

答案 1 :(得分:10)

如果使用.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;
    }
}

答案 2 :(得分:6)

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

下一步启动 configureServices 方法中添加以下代码:

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

                c.OperationFilter<ApiVersionOperationFilter>();
        });

然后启动中的配置方法中添加以下代码:

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

下一步启动 ConfigureServices 方法中添加以下代码:

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

然后启动中的配置方法中添加以下代码:

            app.UseSwagger();

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

               options.RoutePrefix = string.Empty;
           });

答案 3 :(得分:4)

您可以使用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;
    }
);

答案 4 :(得分:3)

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

DocInclusionPredicate

Swashbuckle.AspNetCore github项目对我有很大帮助。

答案 5 :(得分:3)

更新到.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 :(得分:0)

我发现使用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}

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

答案 7 :(得分:0)

@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页面上选择一个版本时,它将仅显示映射到该版本的控制器方法。