目录
新建项目
RestFul
Swagger配置
注释展示
版本控制
Token传值
方法封装
新建项目
打开visual studio创建新项目,这里我们选择.net core web api模板,然后输入项目名称及其解决方案创建新项目
这里使用配置一些其他信息,根据自己情况进行选择:
创建好项目之后我们可以看到 web api 模板运行的项目,相比于MVC模式开发的项目,初始文件是保留原有的控制器文件但是删除了模型和视图文件,而且还加上了两个文件,文件的作用相当于测试用例一一可以模拟发起请求,如下所示:
我们直接运行该项目,可以看到我们打开了一个Swagger文件,对于后端小白来说可能不太熟悉这个文件的作用,但是对于前端开发者来讲可谓是非常熟悉的东西,前端开发调用接口就需要经常和这个文件打交道,如下所示:
我们除了可以在Swagger上测试接口,也可以在本地代码的测试用例处点击发起请求进行测试:
这里做一个演示,比如说我们想在Swagger文档中新增一组接口的话,我们可以在控制器文件中设置一下get、post、delete、put请求操作,如下我们将原本的文件复制一份然后命名为First,然后设置一下接口类型,如下所示:
重新运行我们的项目然后打开Swagger,可以看到我们创建的一组接口成功出现了,这里接口的组名可以看到是 FirstController.cs 文件中Controller的前缀,非常好识别:
RestFul
背景:近些年来随着移动互联网的发展,前端设备层出不穷(手机、平板、桌面电脑、其他专用设备..),因此必须有一组统一的机制方便不同的前端设备与后端进行通信,于是RestFul诞生了,它可以通过一套统一的接口为Web,iOS和Android等各种各样的终端提供服务。
特点:RestFul把控制器作为维度当成一块资源,对于这个资源会进行增删改查等各种当作,这些动作必须都得有唯一的URL地址来匹配请求的Method的类型(get、post、put、delete),本质上就是用URL定位资源,用HTTP(get、post、put、delete)描述操作。说白了就是一种软件架构设计风格而不是标准,只是提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁有层次,更易于实现缓存等机制。
比如上文我们在控制器文件中设置了一组接口,如果我们再在文件中添加比如说一个get接口请求,运行项目其实是会报错的,为什么?就是因为这两个get请求的地址都是一样的,编辑器没法识别到底哪个get请求是你想要的,为了区分我们必须设置唯一的URL地址来避免报错,如下:
重新运行项目,可以看到我们在First组当中又新增了一个新的get请求,且两个get请求的URL路径是不一致的,如下所示:
对于路由约束的类型我们可以参考如下表格,这些都可以作为接口传参的一个类型限定:
限制 | 示例 | 匹配示例 | 说明 |
---|---|---|---|
int | {id: int} | 123456789, -123456789 | 匹配任何整数 |
bool | {active: bool} | true, false | 匹配true或false,忽略大小写 |
datetime | {dob: datetime} | 2024-12-11,2024-12-12 7:32pm | 匹配满足datetime类型的值 |
decimal | {price: decimal} | 49.99, -1,000.01 | 匹配满足decimal类型的值 |
double | {height: double} | 1234, -1001.01e8 | 匹配满足double类型的值 |
float | {height: float} | 1234, -1001.01e8 | 匹配满足float类型的值 |
long | {ticks: long} | 123456789, -123456789 | 匹配满足long类型的值 |
minlength(value) | {usename: minlength(4)} | KOBE | 字符串长度不能小于4个字符 |
maxlength(value) | {filename: maxlength(8)} | CURRY | 字符串长度不能超过8个字符 |
length(value) | {filename: length(12)} | somefile.txt | 字符串长度必须是12个字符 |
length(min,max) | {filename: length(8,16)} | somefile.txt | 字符串长度必须介于8和16之间 |
min(value) | {age: min(18)} | 20 | 整数值必须大于18 |
max(value) | {age: max(120)} | 119 | 整数值必须小于120 |
range(min, max) | {age: range(18, 120)} | 100 | 整数值必须介于18和120之间 |
alpha | {name: alpha} | Rick | 字符串必须由一或多a-z字母组成 |
regex(expression) | {ssn:regex(^\d{3})} | 3 | 字符串必须匹配指定的正则 |
required |
Swagger配置
虽然上面我设置了Swagger并成功运行了,但是设想一个场景,如果你是后端你写的代码你当然看的懂,生成的Swagger接口也是知道是什么意思,但是你把这个Swaager丢给前端,前端是否看的懂呢?如果看不懂是不是还要找后端沟通,这样就增加了时间成本。所以后端不仅仅要不接口写对,还要把Swagger文档写好,这里我们就需要对其进行相应配置,争取让其他人看懂自己写的接口,这才是真正意义上一名后端应该做的事情,如下所示:
注释展示
注释是很重要的,当我们写完一个接口之后我们就需要对这个接口的作用进行注释,用来告诉前端后端给的这个接口作用和意义是什么,如何设置Swagger注释展示呢?请往下看:
如下我们给接口代码写下注释,注释的快捷键是///,也就是三个斜杠即可生成,然后右键项目点击属性,勾选文档文件这个复选框:
我们右键项目重新生成解决方案然后打开我们的项目资源管理目录,可以看到在我们项目的obj目录下的最里层有生成了一个xml文件,右键选择文本打开可以看到我们的注释就在里面:
接下来如果说我们想把注释展示到Swagger文档中的话,我们需要来到入口文件Program.cs对我们的Swagger进行一个配置,给原本的AddSwaggerGen添加一个配置对象,代码如下:
builder.Services.AddSwaggerGen(option => {
#region 注释展示
{
// 获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径)
string basePath = AppContext.BaseDirectory;
string xmlPath = Path.Combine(basePath, "netCoreWebApi.xml");
option.IncludeXmlComments(xmlPath);
}
#endregion
});
回到Swagger文档中可以看到我们的注释已经成功的被渲染出来了,方便前端的理解:
版本控制
随着项目不断升级和开发的过程中,我们还可能需要去对项目进行开发多个版本,但是版本之间的Swagger是不同的,不会放置在同一个Swagger文档下面,我们打开Swagger文档也能看到右上角有个下拉框,其就是支持不同的版本的切换的,如何做?请往下看:
为了方便不同版本的切换,这里我们直接在解决方案中新建项目设置一个独立的文件夹出来,因为待会我们还要对其作一个扩展,所以这里我们选择一个类库即可,如下:
生成的文件我们重命名为 ApiVersionInfo ,然后设置静态成员V1到V5表明版本有5个,如下所示:
我们来到入口文件Program.cs文件处,这里我们开始设置支持Swagger版本控制的代码,这里我们借助 System.Reflection 中的FieldInfo反射机制中的类型,来获取类型ApiVersionInfo中的所有字段信息,然后通过OpenApiInfo来描述Swagger文档的元数据及包含了有关API的关键信息,比如标题、版本、描述等,如下所示:
builder.Services.AddSwaggerGen(option => {
#region 注释展示
{
// 获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径)
string basePath = AppContext.BaseDirectory;
string xmlPath = Path.Combine(basePath, "netCoreWebApi.xml");
option.IncludeXmlComments(xmlPath);
}
#endregion
#region 支持Swagger版本控制
{
foreach (FieldInfo filed in typeof(ApiVersionInfo).GetFields())
{
option.SwaggerDoc(filed.Name, new OpenApiInfo()
{
Title = $"netCoreWebApi API {filed.Name}",
Version = filed.Name,
Description = $"netCoreWebApi API {filed.Name} 版本"
});
}
}
#endregion
});
接下来配置Swagger UI使得每个API版本都有一个对应的Swagger UI页面,具体来说它会为每个API版本动态创建一个Swagger UI的入口从而允许用户查看不同版本的API文档,如下:
接下来我们来到编写接口代码的控制器文件,给不同版本设置如下代码,作用是将特定的API控制器或者方法标记为属于某个版本组,从而使得Swagger等工具能够按版本组织和展示API文档:
最终呈现的效果如下所示:
Token传值
如果项目的接口需要授权才能被访问的话,这里我们就需要通过token传值的方式来进行鉴权才能调试接口,如何做呢?请往下看:
这里我们仍然在Swagger配置函数中设置token传值以及添加对应的安全要求,如下所示:
#region 支持token传值
{
option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"", // 描述
Name = "Authorization", // 授权名称
In = ParameterLocation.Header, // 授权位置,放在头信息进行传值
Type = SecuritySchemeType.ApiKey, // 授权类型
BearerFormat = "JWT", // 格式是JWT
Scheme = "Bearer"
});
// 添加安全要求
option.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference()
{
Id = "Bearer", // 必须和上面一致
Type = ReferenceType.SecurityScheme
}
},
new string[] { }
}
});
}
#endregion
运行项目来到Swagger文档中,可以看到我们已经成功添加了身份验证,这里我们输入对应的JWT格式即可,如下所示我们输入token之后发起请求,如下已经带上了我们设置的token:
方法封装
根据上面对Swagger的配置,可以看到我们对Swagger的配置基本上都写在了入口Program.cs文件当中,这样就显得非常的冗余,入口文件代码量太多了,这里我们可以将对Swagger的配置抽离到我们创建的类库当中,新建一个文件夹用于专门存放Swagger配置的东西,如下所示:
这里设置一个函数然后把IServiceCollection设置为Service的类型,该类型用于配置依赖注入容器的接口,通常为Swagger配置服务,我们直接把入口文件当中配置Swagger的代码直接粘贴过来:
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
namespace netCoreWebApi.WebCore.SwaggerExt
{
public static class SwaggerExtension
{
public static void AddSwaggerExtension(this IServiceCollection Service)
{
Service.AddEndpointsApiExplorer();
Service.AddSwaggerGen(option => {
#region 注释展示
{
// 获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径)
string basePath = AppContext.BaseDirectory;
string xmlPath = Path.Combine(basePath, "netCoreWebApi.xml");
option.IncludeXmlComments(xmlPath);
}
#endregion
#region 支持Swagger版本控制
{
foreach (FieldInfo filed in typeof(ApiVersionInfo).GetFields())
{
option.SwaggerDoc(filed.Name, new OpenApiInfo()
{
Title = $"netCoreWebApi API {filed.Name}",
Version = filed.Name,
Description = $"netCoreWebApi API {filed.Name} 版本"
});
}
}
#endregion
#region 支持token传值
{
option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"", // 描述
Name = "Authorization", // 授权名称
In = ParameterLocation.Header, // 授权位置,放在头信息进行传值
Type = SecuritySchemeType.ApiKey, // 授权类型
BearerFormat = "JWT", // 格式是JWT
Scheme = "Bearer"
});
// 添加安全要求
option.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference()
{
Id = "Bearer", // 必须和上面一致
Type = ReferenceType.SecurityScheme
}
},
new string[] { }
}
});
}
#endregion
});
}
public static void UseSwaggerExtension(this WebApplication app)
{
app.UseSwagger();
app.UseSwaggerUI(option => {
foreach (FieldInfo filed in typeof(ApiVersionInfo).GetFields())
{
option.SwaggerEndpoint($"/swagger/{filed.Name}/swagger.json", filed.Name);
}
});
}
}
}
这里还需要在类库中安装一下主程序当中包,这里注意意义名称和版本号:
因为this修饰静态类中的静态方法,上面创建的两个方法都是静态类中的静态方法同时第一个参数还用this进行修饰,这种扩展方法可以把传参提到前面当成实例方法来进行调用,接下来我们就在入口文件中调用这两个方法:
最后我们重新运行项目,如下可以看到我们的Swagger也可以被重新运行: