在 C#/.NET Core 的 Web API 中使用 Swagger 按模块和版本分组并实现排序


前言

在开发 RESTful API 时,良好的文档是必不可少的。Swagger 是一种广泛使用的 API 文档工具,可以帮助我们生成交互式的 API 文档。然而,当项目规模增大,API 数量众多时,我们需要将 API 按照模块和版本进行分组,以便更好地管理和查找。本文将介绍如何在 .NET Core Web API 中使用 Swagger 按模块和版本分组,并使用自定义特性实现这一目标。


步骤一:安装 Swashbuckle.AspNetCore

首先,我们需要安装 Swashbuckle.AspNetCore 包,这是 .NET Core 中用于集成 Swagger 的库。可以在项目的根目录下运行以下命令进行安装:

dotnet add package Swashbuckle.AspNetCore

步骤二:创建自定义特性

为了实现按模块和版本分组,我们需要创建一个自定义特性 ApiDescriptionAttribute。这个特性将用于标记我们的控制器,并包含模块名称、版本号和描述信息。

using Microsoft.AspNetCore.Mvc.ApiExplorer;

namespace WebApplication.ApiAttributes
{
    public class ApiDescriptionAttribute : Attribute, IApiDescriptionGroupNameProvider
    {
        public ApiDescriptionAttribute(string title, string? version = null, string? desc = null, int position = int.MaxValue)
        {
            GroupName = version != null ? $"{title}-{version}" : title;
            Title = title;
            Version = version;
            Description = desc;
            Position = position;
        }

        /// <summary>
        /// 分组名称
        /// </summary>
        public string? GroupName { get; set; }
        /// <summary>
        /// Swagger 标题
        /// </summary>
        public string? Title { get; set; }
        /// <summary>
        /// 版本号
        /// </summary>
        public string? Version { get; set; }
        /// <summary>
        /// 描述
        /// </summary>
        public string? Description { get; set; }
        /// <summary>
        /// 分组顺序
        /// </summary>
        public int Position { get; set; }
    }
}

步骤三:配置 Swagger 生成文档

接下来,我们需要在 Program.cs 中配置 Swagger,使其能够根据我们的自定义特性生成多个文档。

public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);
    
    // 添加服务到容器
    builder.Services.AddControllers();
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen(options =>
    {
        // 根据模块和版本生成多个文档
        var apiAssembly = Assembly.GetExecutingAssembly();
        var apiDescriptions = apiAssembly.GetTypes()
            .Where(t => t.GetCustomAttributes<ApiDescriptionAttribute>().Any())
            .Select(t => t.GetCustomAttribute<ApiDescriptionAttribute>())
            .Distinct();

        foreach (var desc in apiDescriptions)
        {
            if (desc != null)
            {
                if (string.IsNullOrEmpty(desc.Version))
                {
                    options.SwaggerDoc($"{desc.Title}", new OpenApiInfo { Title = $"{desc.Title} API", Version = desc.Version, Description = desc.Description, });
                }
                else
                {
                    options.SwaggerDoc($"{desc.Title}-{desc.Version}", new OpenApiInfo
                    {
                        Title = $"{desc.Title} API",
                        Version = desc.Version,
                        Description = desc.Description,
                    });
                }
            }
        }
        //没有加特性的分到这个NoGroup上
        options.SwaggerDoc("NoGroup", new OpenApiInfo
        {
            Title = "无分组"
        });
        //判断接口归于哪个分组
        options.DocInclusionPredicate((docName, apiDescription) =>
        {
            if (docName == "NoGroup")
            {
                //当分组为NoGroup时,只要没加特性的都属于这个组
                return string.IsNullOrEmpty(apiDescription.GroupName);
            }
            else
            {
                return apiDescription.GroupName == docName;
            }
        });
    });

    var app = builder.Build();

    // 配置 HTTP 请求管道
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI(options =>
        {
            // 根据模块和版本生成多个文档
            var apiAssembly = Assembly.GetExecutingAssembly();
            var apiDescriptions = apiAssembly.GetTypes()
                .Where(t => t.GetCustomAttributes<ApiDescriptionAttribute>().Any())
                .Select(t => t.GetCustomAttribute<ApiDescriptionAttribute>())
                .OrderBy(t => t?.Position ?? int.MaxValue).ThenBy(t => t?.Title).ThenBy(t => t?.Version)
                .Distinct();

            foreach (var desc in apiDescriptions)
            {
                if (desc != null)
                {
                    if (string.IsNullOrEmpty(desc.Version))
                    {
                        options.SwaggerEndpoint($"/swagger/{desc.Title}/swagger.json", $"{desc.Title} API");
                    }
                    else
                    {
                        options.SwaggerEndpoint($"/swagger/{desc.Title}-{desc.Version}/swagger.json", $"{desc.Title} API {desc.Version}");
                    }
                }
            }

            options.SwaggerEndpoint("/swagger/NoGroup/swagger.json", "无分组");
        });
    }

    app.UseHttpsRedirection();
    app.UseAuthorization();
    app.MapControllers();
    app.Run();
}

步骤四:标记控制器和方法

使用我们创建的 ApiDescriptionAttribute 来标记控制器和方法。以下是一些示例:

using Microsoft.AspNetCore.Mvc;
using WebApplication.ApiAttributes;

namespace WebApplication.Controllers
{
    [ApiDescription("ModuleA", "v1", "A模组测试")]
    [Route("api/[controller]")]
    [ApiController]
    public class ModuleA1Controller : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("Module A, Version 1");
        }
    }
}
namespace WebApplication.Controllers
{
    [ApiDescription("ModuleA", "v2")]
    [Route("api/[controller]")]
    [ApiController]
    public class ModuleA2Controller : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("Module A, Version 2");
        }
    }
}
namespace WebApplication.Controllers
{
    [ApiDescription("ModuleB")]
    [Route("api/[controller]")]
    [ApiController]
    public class ModuleBController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("Module B, 仅有Title");
        }
    }
}
namespace WebApplication.Controllers
{
    [ApiDescription("ModuleC")]
    [Route("api/[controller]")]
    [ApiController]
    public class ModuleC1Controller : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("Module C1, 多Controller共用分组");
        }
    }
}
namespace WebApplication.Controllers
{
    [ApiDescription("ModuleC")]
    [Route("api/[controller]")]
    [ApiController]
    public class ModuleC2Controller : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("Module C2, 多Controller共用分组");
        }
    }
}
namespace WebApplication.Controllers
{
    [ApiDescription("ModuleD", desc: "D模组测试")]
    [Route("api/[controller]")]
    [ApiController]
    public class ModuleDController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("Module D, 指定参数设置");
        }
    }
}
namespace WebApplication.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ModuleNoGroupController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("Module NoGroup");
        }
    }
}
namespace WebApplication.Controllers
{
    [ApiDescription("Position A", desc: "指定顺序", position: 1)]
    [Route("api/[controller]")]
    [ApiController]
    public class PositionAController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("Position A, 指定Swagger分组顺序");
        }
    }
}
namespace WebApplication.Controllers
{
    [ApiDescription("Position Z", desc: "指定顺序", position: 0)]
    [Route("api/[controller]")]
    [ApiController]
    public class PositionZController : ControllerBase
    {
        [HttpGet]
        public IActionResult Get()
        {
            return Ok("Position Z, 指定Swagger分组顺序");
        }
    }
}
namespace WebApplication.Controllers
{
    [Route("[controller]")]
    [ApiDescription("天气", "v1", "天气预报")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

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

        [HttpGet(Name = "GetWeatherForecast")]
        public IEnumerable<WeatherForecast> Get()
        {
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

Swagger截图


总结

通过以上步骤,我们可以在 .NET Core Web API 项目中使用 Swagger 按模块和版本分组。这种实现方法使用了自定义特性来标记控制器,并在 Program.cs 中配置了 Swagger 以生成多个文档。这样,在 Swagger UI 中,我们可以根据模块和版本分别查看 API 文档,从而更好地管理和查找 API。这种方法不仅提升了文档的可读性,也增强了项目的可维护性,使开发者和使用者能更方便地交互与理解 API。

  • 21
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值