这是StarBlog系列在2023年的第一篇更新😃~
在之前的文章里,我们已经完成了部分接口的开发,接下来需要使用 curl、Postman 这类工具对这些接口进行测试,但接口一多,每次测试都要一个个填入地址和对应参数会比较麻烦…
我们需要一种直观的方式来汇总项目里的所有接口,并且如果能直接在里面调试接口,那就更好了。
Swagger:诶嘿,说的不就是我吗?😎
来一段官网的介绍
Simplify API development for users, teams, and enterprises with the Swagger open source and professional toolset.
翻译:Swagger 是开源和专业的工具集,可以简化用户、团队和企业的 API 开发。
一般来说,swagger用起来有两部分,一个是 OpenAPI 一个是 SwaggerUI
在Swagger官网上,OpenAPI 介绍得天花乱坠🙂
The OpenAPI Specification, formerly known as the Swagger Specification, is the world’s standard for defining RESTful interfaces. The OAS enables developers to design a technology-agnostic API interface that forms the basis of their API development and consumption.
翻译:OpenAPI 规范,以前称为 Swagger 规范,是定义 RESTful 接口的世界标准。 OAS 使开发人员能够设计一个与技术无关的 API 接口,该接口构成了他们 API 开发和使用的基础。
简单说 OpenAPI 是个标准,需要每种语言和框架自行实现一个工具,用来把项目里的接口都整合起来,生成 swagger.json
文件
然后 SwaggerUI 就是个网页,读取这个 swagger.json
就可以把所有接口以及参数显示出来,还可以很方便调试,效果如图。
前面说到每种框架都要自己实现一个工具来生成 swagger.json
,这个 Swashbuckle.AspNetCore 就是 .NetCore 平台的实现,用就完事了。
项目主页: https://github.com/domaindrivendev/Swashbuckle.AspNetCore
Tips:如果是创建 WebApi 项目,代码模板里面默认就有 Swagger 了,不用手动添加。
StarBlog项目一开始是使用MVC模板,所以没有自带Swagger,需要手动添加。
直接使用nuget添加 Swashbuckle.AspNetCore
这个包就完事了。
这个包功能很多,内置了 SwaggerUI 这个官方界面,还有一个 ReDoc 的纯静态接口文档网页(这个 ReDoc 只能看接口不能调试)。
为了保证 Program.cs
代码整洁,我们在 StarBlog.Web/Extensions
里面创建 ConfigureSwagger
类
public static class ConfigureSwagger {
public static void AddSwagger(this IServiceCollection services) {
services.AddSwaggerGen(options => {
options.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", Title = "APIs"});
// 在接口文档上显示 XML 注释
var filePath = Path.Combine(System.AppContext.BaseDirectory, $"{typeof(Program).Assembly.GetName().Name}.xml");
options.IncludeXmlComments(filePath, true);
});
}
public static void UseSwaggerPkg(this IApplicationBuilder app) {
app.UseSwagger();
app.UseSwaggerUI(options => {
options.RoutePrefix = "api-docs/swagger";
options.SwaggerEndpoint("/swagger/v1/swagger.json", "APIs");
});
app.UseReDoc(options => {
options.RoutePrefix = "api-docs/redoc";
options.SpecUrl = "/swagger/v1/swagger.json";
});
}
}
上面代码可以看到有三步
AddSwaggerGen
- 对应前文说的生成 swagger.json
UseSwagger
- 让浏览器可以访问到 /swagger/v1/swagger.json
这类路径UseSwaggerUI
- 提供 SwaggerUI 的网页访问然后回到 Program.cs
里面,分别注册服务和添加中间件就好了。
// 注册服务
builder.Services.AddSwagger();
// 添加中间件
app.UseSwaggerPkg();
现在启动项目,访问 http://[本地地址]/api-docs/swagger
就能看到接口文档了
效果大概这样
C# 的代码注释可以导出XML,然后显示在 swagger 文档上
注意需要手动在 .csproj
项目配置里面开启,才会输出XML文档
<!-- 输出XML -->
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
但是开启XML之后,IDE很蠢的要求我们所有public成员都写上注释,很烦,加上 <NoWarn>$(NoWarn);1591</NoWarn>
这行就可以关掉这个警告。
在 Swagger 里加载XML文档,既可以用本文前面写的方式
var filePath = Path.Combine(System.AppContext.BaseDirectory, $"{typeof(Program).Assembly.GetName().Name}.xml");
options.IncludeXmlComments(filePath, true);
还可以用第二种,加载目录里的全部XML
var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
foreach (var file in xmlFiles) {
options.IncludeXmlComments(file, true);
}
具体用哪种,都行吧,看心情~
AddEndpointsApiExplorer
在 AddSwagger
扩展方法这里可能有同学会有疑问
为啥创建 .Net6 项目后默认是这两行代码
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
而我这里只有一行代码
services.AddSwaggerGen();
先说结论:AddEndpointsApiExplorer
是为了支持 Minimal Api 的。
因为 StarBlog 项目使用的是MVC模板,在 Program.cs
的最开始可以看到这行代码,添加控制器和视图
builder.Services.AddControllersWithViews();
翻一下这个框架的源码,可以看到这个方法的套娃是这样的
AddControllersWithViews() -> AddControllersWithViewsCore() -> AddControllersCore()
而在 AddControllersCore
里面,又调用了 AddApiExplorer
private static IMvcCoreBuilder AddControllersCore(IServiceCollection services) {
// This method excludes all of the view-related services by default.
var builder = services
.AddMvcCore()
.AddApiExplorer()
.AddAuthorization()
.AddCors()
.AddDataAnnotations()
.AddFormatterMappings();
if (MetadataUpdater.IsSupported) {
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IActionDescriptorChangeProvider, HotReloadService>());
}
return builder;
}
就是说正常的项目已经有 ApiExplorer
这个东西了,但是 Minimal Api 项目没有,所以本项目不需要 builder.Services.AddEndpointsApiExplorer();
这行代码。
详情可以阅读参考资料的第一个链接。
接口文档有了,但项目里接口太多了,几十个接口全挤在一个页面上,找都找得眼花了😑
这时候可以给接口分个组
先来给 StarBlog 项目里面的接口分个类,根据不同用途,大致分成这五类:
还是在上面那个 ConfigureSwagger.cs
文件
修改 AddSwagger
方法,把这几个分组添加进去
services.AddSwaggerGen(options => {
options.SwaggerDoc("admin", new OpenApiInfo {
Version = "v1",
Title = "Admin APIs",
Description = "管理员相关接口"
});
options.SwaggerDoc("common", new OpenApiInfo {
Version = "v1",
Title = "Common APIs",
Description = "通用公共接口"
});
options.SwaggerDoc("auth", new OpenApiInfo {
Version = "v1",
Title = "Auth APIs",
Description = "授权接口"
});
options.SwaggerDoc("blog", new OpenApiInfo {
Version = "v1",
Title = "Blog APIs",
Description = "博客管理接口"
});
options.SwaggerDoc("test", new OpenApiInfo {
Version = "v1",
Title = "Test APIs",
Description = "测试接口"
});
});
这样就会生成五个 swagger.json
文件,路径分别是
/swagger/admin/swagger.json
/swagger/common/swagger.json
/swagger/auth/swagger.json
/swagger/blog/swagger.json
/swagger/test/swagger.json
所以下面的 UseSwaggerPkg
方法也要对应修改
public static void UseSwaggerPkg(this IApplicationBuilder app) {
app.UseSwagger();
app.UseSwaggerUI(options => {
options.RoutePrefix = "api-docs/swagger";
options.SwaggerEndpoint("/swagger/admin/swagger.json", "Admin");
options.SwaggerEndpoint("/swagger/blog/swagger.json", "Blog");
options.SwaggerEndpoint("/swagger/auth/swagger.json", "Auth");
options.SwaggerEndpoint("/swagger/common/swagger.json", "Common");
options.SwaggerEndpoint("/swagger/test/swagger.json", "Test");
});
}
接下来,要让 Swagger 知道每个接口都是属于哪个分组的。
具体方法是在 Controller 上添加 ApiExplorerSettings
特性。
比如 BlogController
是属于 blog 分组,在 class 定义前面添加一行代码
[ApiExplorerSettings(GroupName = "blog")]
public class BlogController : ControllerBase {
// ...
}
其他的 Controller 也是类似的操作,具体分组跟 StarBlog.Web/Apis
下的目录结构一样,这里就不赘述了。
做完之后,打开 swagger 接口文档页面
可以看到右上角可以选择接口分组了
搞定。
前文对于 Swagger 分组的实现其实是一种硬编码,不同分组的 Controller 上面需要加上 [ApiExplorerSettings(GroupName = "blog")]
特性,分组名全靠复制粘贴,在项目比较小的情况下还好,如果分组多起来了,有几百个接口的时候,估计人就麻了吧😂
Q:“你刚才干嘛不早说😑”
A:“循序渐进嘛😛”
A:“StarBlog项目也是最近才换到新版分组的😎”
在 StarBlog.Web/Models
里添加个新的类 SwaggerGroup
public class SwaggerGroup {
/// <summary>
/// 组名称(同时用于做URL前缀)
/// </summary>
public string Name { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public SwaggerGroup(string name, string? title = null, string? description = null) {
Name = name;
Title = title;
Description = description;
}
/// <summary>
/// 生成 <see cref="Microsoft.OpenApi.Models.OpenApiInfo"/>
/// </summary>
public OpenApiInfo ToOpenApiInfo(string version = "1.0") {
var item = new OpenApiInfo();
Title ??= Name;
Description ??= Name;
return new OpenApiInfo { Title = Title, Description = Description, Version = version };
}
}
然后改造一下 StarBlog.Web/Extensions/ConfigureSwagger.cs
在这个文件里面添加个新的类,这样就不会硬编码了😃
public static class ApiGroups {
public const string Admin = "admin";
public const string Auth = "auth";
public const string Common = "common";
public const string Blog = "blog";
public const string Test = "test";
}
在 ConfigureSwagger
里添加一些代码,创建 SwaggerGroup
列表
public static class ConfigureSwagger {
public static readonly List<SwaggerGroup> Groups = new() {
new SwaggerGroup(ApiGroups.Admin, "Admin APIs", "管理员相关接口"),
new SwaggerGroup(ApiGroups.Auth, "Auth APIs", "授权接口"),
new SwaggerGroup(ApiGroups.Common, "Common APIs", "通用公共接口"),
new SwaggerGroup(ApiGroups.Blog, "Blog APIs", "博客管理接口"),
new SwaggerGroup(ApiGroups.Test, "Test APIs", "测试接口")
};
}
然后把后面的 AddSwagger
方法改成这样,那一坨东西,现在一行代码就代替了😀
public static void AddSwagger(this IServiceCollection services) {
services.AddSwaggerGen(options => {
Groups.ForEach(group => options.SwaggerDoc(group.Name, group.ToOpenApiInfo()));
// XML注释
var filePath = Path.Combine(AppContext.BaseDirectory, $"{typeof(Program).Assembly.GetName().Name}.xml");
options.IncludeXmlComments(filePath, true);
});
}
接着是 UseSwaggerPkg
方法,简单😎
public static void UseSwaggerPkg(this IApplicationBuilder app) {
app.UseSwagger();
app.UseSwaggerUI(opt => {
opt.RoutePrefix = "api-docs/swagger";
// 分组
Groups.ForEach(group => opt.SwaggerEndpoint($"/swagger/{group.Name}/swagger.json", group.Name));
});
}
Controller里面也对应修改成这样
[ApiExplorerSettings(GroupName = ApiGroups.Blog)]
public class BlogController : ControllerBase {
}
完美🆒🥳
“Swagger之大,一锅炖不下”
关于Swagger还有其他的用法,但需要一些前置知识,因此本文不会把StarBlog项目中关于Swagger的部分全部介绍完
等把相关的前置知识写完,再来完善对应的用法~
这也跟StarBlog的开发过程是吻合的😀