Asp.Net Core基础篇之:集成Swagger文档与自定义Swagger UI


Swagger大家都不陌生,Swagger (OpenAPI) 是一个与编程语言无关的接口规范,用于描述项目中的 REST API。它的出现主要是节约了开发人员编写接口文档的时间,可以根据项目中的注释生成对应的可视化接口文档。

OpenAPI 规范 (openapi.json)

OpenAPI 规范是描述 API 功能的文档。该文档基于控制器和模型中的 XML属性注释。它是 OpenAPI 流的核心部分,用于驱动诸如 SwaggerUI 之类的工具。

.NET 平台下的两个主要实现Swagger的包是 Swashbuckle 和 NSwag。今天我们从 Swashbuckle 开始了解。

基础功能

1、在包管理器搜索Swashbuckle.AspNetCore并安装。

2、在Startup.cs文件内的ConfigureServices方法内添加代码。
public void ConfigureServices(IServiceCollection services)
{
	services.AddControllers();
	services.AddSwaggerGen();
}
3、在Startup.cs文件内的Configure方法内添加代码。
app.UseSwagger();
app.UseSwaggerUI(options =>
{
	options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
	options.RoutePrefix = string.Empty;
});
4、修改项目的launchSettings.json文件,将launchUrl的值改为:index.html
5、准备接口
    [ApiController]
    public class HomeController : ControllerBase
    {
        private readonly ILogger _logger;
        public HomeController(ILogger logger)
        {
            _logger = logger;
        }

        /// 
        /// 获取用户信息
        /// 
        /// 
        [HttpGet("home/getuser")]
        public string GetUser()
        {
            return "my name is dotnetboy";
        }

        /// 
        /// 登录成功
        /// 
        /// 
        [HttpPost("home/login")]
        public string Login()
        {
            return "login success";
        }

        /// 
        /// 删除用户
        /// 
        [HttpDelete("home/{id}")]
        public string DeleteUser(string id)
        {
            return $"delete success,id={id}";
        }
    }
6、开启xml文档输出然后启动项目

扩展功能

项目描述

services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
    	Title = "测试接口文档",
    	Version = "v1",
    	Description = "测试 webapi"
    });
});

接口分组

在实际开发中,如果所有接口都展示在一起非常不利于相关人员查找,我们可以根据业务逻辑对相关接口进行分组,比如:登录、用户、订单、商品等等。

1、准备分组信息特性
/// 
/// 分组信息特性
/// 
public class GroupInfoAttribute : Attribute
{
    /// 
    /// 标题
    /// 
    public string Title { get; set; }
    /// 
    /// 版本
    /// 
    public string Version { get; set; }
    /// 
    /// 描述
    /// 
    public string Description { get; set; }
}
2、准备分组枚举
/// 
/// 接口分组枚举
/// 
public enum ApiGroupNames
{
    [GroupInfo(Title = "登录认证", Description = "登录相关接口", Version = "v1")]
    Login,
    [GroupInfo(Title = "User", Description = "用户相关接口")]
    User,
    [GroupInfo(Title = "User", Description = "订单相关接口")]
    Order
}
3、准备接口特性
/// 
/// 分组接口特性
/// 
public class ApiGroupAttribute : Attribute, IApiDescriptionGroupNameProvider
{
    /// 
    /// 
    /// 
    /// 
    public ApiGroupAttribute(ApiGroupNames name)
    {
        GroupName = name.ToString();
    }
    /// 
    /// 分组名称
    /// 
    public string GroupName { get; set; }
}
4、给不同接口加上特性
[ApiController]
public class HomeController : ControllerBase
{
   private readonly ILogger _logger;
   public HomeController(ILogger logger)
    {
        _logger = logger;
    }
    /// 
    /// 获取用户信息
    /// 
    /// 
    [HttpGet("home/getuser")]
    [ApiGroup(ApiGroupNames.User)]
    public string GetUser()
    {
        return "my name is dotnetboy";
    }
    /// 
    /// 登录成功
    /// 
    /// 
    [HttpPost("home/login")]
    [ApiGroup(ApiGroupNames.Login)]
    public string Login()
    {
        return "login success";
    }
    /// 
    /// 删除订单
    /// 
    [HttpDelete("home/{id}")]
    [ApiGroup(ApiGroupNames.Order)]
    public string DeleteOrder(string id)
    {
        return $"delete success,id={id}";
    }
    /// 
    /// 留言
    /// 
    [HttpDelete("home/message")]
    public string DeleteUser(string msg)
    {
        return $"message:{msg}";
    }
}
5、修改 ConfigureServices 方法的 AddSwaggerGen
services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "接口文档",
        Version = "v1",
        Description = "测试 webapi"
    });

	// 遍历ApiGroupNames所有枚举值生成接口文档,Skip(1)是因为Enum第一个FieldInfo是内置的一个Int值
	typeof(ApiGroupNames).GetFields().Skip(1).ToList().ForEach(f =>
	{
        //获取枚举值上的特性
        var info = f.GetCustomAttributes(typeof(GroupInfoAttribute), false).OfType().FirstOrDefault();
        options.SwaggerDoc(f.Name, new OpenApiInfo
        {
            Title = info?.Title,
            Version = info?.Version,
            Description = info?.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;
    	}
    });
});
6、修改 Configure 方法的 UseSwaggerUI
app.UseSwaggerUI(options =>
{
    // 遍历ApiGroupNames所有枚举值生成接口文档
    typeof(ApiGroupNames).GetFields().Skip(1).ToList().ForEach(f =>
    {
        //获取枚举值上的特性
        var info = f.GetCustomAttributes(typeof(GroupInfoAttribute), false).OfType().FirstOrDefault();
        options.SwaggerEndpoint($"/swagger/{f.Name}/swagger.json", info != null ? info.Title : f.Name);
    });
    options.SwaggerEndpoint("/swagger/NoGroup/swagger.json", "无分组");
    options.RoutePrefix = string.Empty;
});

自定义UI

前几天,前端同事和我吐槽,Swagger的原生UI太丑了,又不够直观,想找个接口还得一个个收缩展开,总之就是很难用。

  1. 不够直观
  2. 不方便查找

有了上面的两点需求何不自己实现一套UI呢?(最终还是用了第三方现成的)

文章最开始有提到OpenAPI 对应的 json 内容,大家也可以在浏览器的控制台看看,swagger ui 的数据源都来自于一个叫 swagger.json 的文件,数据源都有了,根据数据源再做一套 UI 也就不是什么难事了。

1、准备一个美观的单页面(网上找的)

2、将单页面相关内容放到项目内(记得开启静态文件读取)
app.UseStaticFiles();

3、将单页面指定为 UI 页面。
app.UseSwaggerUI(options =>
{
	options.IndexStream = () => GetType().Assembly.GetManifestResourceStream("h.swagger.Swagger.index.html");
});
4、在单页面内处理 swagger.json 数据源。
5、最终效果

Swagger UI的功能还是比较多的,比如:详情调试。如果想自己实现一套UI要做的工作还很多。所以,拿来主义永不过时,最终我还是选择了第三方开源的项目:Knife4j

使用起来也是非常简单,先引用包:IGeekFan.AspNetCore.Knife4jUI,然后在Startup.Configure中将 app.UseSwaggerUI 替换为:

app.UseKnife4UI(c =>
{
	c.RoutePrefix = string.Empty;
	c.SwaggerEndpoint($"/swagger/v1/swagger.json", "h.swagger.webapi v1");
});