在目前的软件开发的潮流中,无论是先后端分离仍是服务化改造,后端更多的是经过构建 API 接口服务从而为 web、app、desktop 等各类客户端提供业务支持,如何构建一个符合规范、容易理解的 API 接口是咱们后端开发人员须要考虑的。在本篇文章中,我将列举一些我在使用 ASP.NET Core Web API 构建接口服务时使用到的一些小技巧,因才疏学浅,可能会存在不对的地方,欢迎指出。css
仓储地址:github.com/Lanesra712/…前端
由于本篇文章中涉及到的一些知识点在以前的文章中也已经有具体的解释了,因此这里只会说明如何在 ASP.NET Core Web API 中如何去使用,不会作过多的详细介绍。若是你须要详细了解的话,能够跳转到文章中给出的外链地址去查看。nginx
本篇文章中使用的代码是基于 .NET Core 2.2 + .NET Standard 2.0 进行构建的,若是你采用的版本与我使用的不一样,可能最终实现起来的代码会有所不一样,请提早知悉。同时,本篇文章中全部示例代码都会存在于前言中所列出的 github repo 中,我会尝试将每一个功能点的开发做为一次 commit,而且也会在后续进行不按期的更新完善,最终搭建一个基于领域驱动思想的后端项目模板,若是对你有帮助的话,欢迎持续关注。git
1、使用小写路由程序员
在我以前的一篇文章中(构建可读性更高的 ASP.NET Core 路由)有提到过,由于 .NET 默认采用 Pascal 的类命名方式,若是采用默认生成的路由,最终构建出的路由地址会存在大小写混在一块儿的状况,虽然在 .NET Core 中大小写的路由地址最终都会对于到正确的资源上,可是为了更好的符合前端的规范,因此这里咱们首先按照以前的文章中所列出的方法去修改默认生成的路由地址格式。github
由于这里咱们最终想要实现的是符合 Restful 风格的 API 接口,因此这里咱们首先须要将默认生成的 URL 地址改成全小写模式。web
public void ConfigureServices(IServiceCollection services) {
// 采用小写的 URL 路由模式
services.AddRouting(options =>
{
options.LowercaseUrls = true;
});
}
复制代码
若是你有查看 .NET Core 默认模板中生成的 API Controller,仔细看下,这里实际上是使用的特性路由,因此这里咱们并不能经过 Startup.UseMvc 定义的传统路由模板,或是直接在 Startup.Configure 中的 UseMvcWithDefaultRoute 方法去修改咱们的生成的路由地址格式。shell
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
}
复制代码
2、容许跨域请求编程
无论是后端接口的服务化改造,仍是只是单纯的先后端分离项目开发,咱们的前端项目与后端接口一般不会部署在一块儿,因此咱们须要解决前端访问接口时会涉及到的跨域访问的问题。json
针对跨域请求,咱们能够采用 jsonp、或者是经过给 nginx 服务器配置响应的 header 参数头信息、或者是使用 CORS,又或是其它的解决方案。你能够自由选择,这里我采用在后端接口中直接配置对于 CORS 的支持。
在 .NET Core 中,已经在 Microsoft.AspNetCore.Cors 这个类库中添加了对于 CORS 的支持,由于这个类库是存在于咱们已经安装的 .NET Core SDK 中,因此这里咱们并不须要经过 Nuget 进行安装,能够直接使用。
在 .NET Core 中配置 CORS 规则,咱们能够经过在 Startup.ConfigureServices 这个方法中添加不一样的受权策略,以后再针对某个 Controller 或是 Action 经过添加 EnableCors 这个 Attribute 的方式进行配置,这里若是指定了 policy 策略名称,则会使用指定的策略,若是没有指定,则适用于系统的默认配置。一样的,咱们也能够只设置一个策略,直接针对整个项目进行配置,这里我采用对整个项目采用通用的跨域请求配置方案。
在配置 CORS 策略时,咱们能够设置只容许来源于某些 URL 地址的请求能够访问,或者是指定接口只容许某些 HTTP 方法进行访问,或者是在请求的 header 中必须包含某些信息才能够访问咱们的接口。
在下面的代码中,我定义了针对整个项目的跨域请求策略,这里我只是设置了对于接口请求方 URL 地址的控制,经过读取配置文件中的数据,从而达到只容许某些 IP 能够访问的咱们接口的目的。
public class Startup
{
// 默认的跨域请求策略名称
private const string _defaultCorsPolicyName = "Ingos.Api.Cors";
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
services.AddMvc(
// 添加 CORS 受权过滤器
options => options.Filters.Add(new CorsAuthorizationFilterFactory(_defaultCorsPolicyName))
).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// 配置 CORS 受权策略
services.AddCors(options => options.AddPolicy(_defaultCorsPolicyName,
builder => builder.WithOrigins(
Configuration["Application:CorsOrigins"]
.Split(",", StringSplitOptions.RemoveEmptyEntries).ToArray()
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
// 容许跨域请求访问
app.UseCors(_defaultCorsPolicyName);
}
}
复制代码
例如在下面的设置中,我只容许这一个地址能够访问咱们的接口,若是须要指定多个的话,则能够经过英文的 , 进行分隔。
"Application": {
"CorsOrigins": "http://127.0.0.1:5050"
}
复制代码
某些状况下,若是咱们不想进行限制的话,只须要将值改成 * 便可。
"Application": {
"CorsOrigins": "*"
}
复制代码
3、添加接口版本控制
在一些涉及到接口功能升级的场景下,当咱们须要修改接口逻辑而旧版本的接口没法停用的状况时,为了减小对于原有接口的影响,咱们能够采起为接口添加版本信息的形式,从而下降因采用不一样版本而形成的影响。若是你想要详细了解的话,能够查看这篇文章,电梯直达 =》ASP.NET Core 实战:构建带有版本控制的 API 接口。
在实现具备版本控制的接口前,首先咱们须要经过 Nuget 添加下面的两个 dll,由于我是在 Ingos.Api.Core 这个类库中进行配置的,因此我安装到了这个类库下,你须要根据你本身的状况选择最终是安装到 Api 接口项目中仍是在别的类库下。
Install-Package Microsoft.AspNetCore.Mvc.Versioning
Install-Package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
复制代码
在安装完成以后,咱们就能够在 Startup.ConfigureServices 方法中,为项目中的接口配置版本信息,这里我采用的方案是将版本号添加到接口的 URL 地址中。
由于对于全部中间件的配置都会在 Startup.ConfigureServices 方法中,为了保持该方法的纯净性,这里我写了一个扩展方法用于配置咱们的 api 的版本,以后直接调用便可。
public static class ApiVersionExtension
{
/// <summary>
/// 添加 API 版本控制扩展方法
/// </summary>
/// <param name="services">生命周期中注入的服务集合 <see cref="IServiceCollection"/></param>
public static void AddApiVersion(this IServiceCollection services) {
// 添加 API 版本支持
services.AddApiVersioning(o =>
{
// 是否在响应的 header 信息中返回 API 版本信息
o.ReportApiVersions = true;
// 默认的 API 版本
o.DefaultApiVersion = new ApiVersion(1, 0);
// 未指定 API 版本时,设置 API 版本为默认的版本
o.AssumeDefaultVersionWhenUnspecified = true;
});
// 配置 API 版本信息
services.AddVersionedApiExplorer(option =>
{
// api 版本分组名称
option.GroupNameFormat = "'v'VVVV";
// 未指定 API 版本时,设置 API 版本为默认的版本
option.AssumeDefaultVersionWhenUnspecified = true;
});
}
}
复制代码
扩展方法最终实现方式如上面的代码所示,以后咱们就能够直接在 ConfigureServices 方法中直接进行调用这个扩展方法就能够了。
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
// Config api version
services.AddApiVersion();
}
复制代码
如今咱们删除项目建立时默认生成的 ValuesController,在 Controllers 目录下创建一个 v1 文件夹,表明此文件夹下都是 v1 版本的控制器。添加一个 UsersController 用来获取系统的用户资源,如今项目的文件结构以下图所示。
[ApiVersion("1.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
}
复制代码
4、添加对于 Swagger 接口文档的支持
在先后端分离开发的状况下,咱们须要提供给前端开发人员一个接口文档,从而让前端开发人员知道以什么样的 HTTP 方法或是传递什么样的参数给后端接口,从而获取到正确的数据,而 Swagger 则提供了一种自动生成接口文档的方式,同时也提供相似于 Postman 的功能,能够实现对于接口的实时调用测试。
首先,咱们须要经过 Nuget 添加 Swashbuckle.AspNetCore 这个 dll 文件,以后咱们就能够在此基础上实现对于 Swagger 的配置。
Install-Package Swashbuckle.AspNetCore
复制代码
与上面配置 API 接口的版本信息类似,这里我依旧采用构建扩展方法的方式来实现对于 Swagger 中间件的配置。具体的配置过程能够查看我以前写的文章(ASP.NET Core 实战:构建带有版本控制的 API 接口),这里只列出最终配置完成的代码。
public static void AddSwagger(this IServiceCollection services) {
// 配置 Swagger 文档信息
services.AddSwaggerGen(s =>
{
// 根据 API 版本信息生成 API 文档
//
var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
foreach (var description in provider.ApiVersionDescriptions)
{
s.SwaggerDoc(description.GroupName, new Info
{
Contact = new Contact
{
Name = "Danvic Wang",
Email = "danvic96@hotmail.com",
Url = "https://yuiter.com"
},
Description = "Ingos.API 接口文档",
Title = "Ingos.API",
Version = description.ApiVersion.ToString()
});
}
// 在 Swagger 文档显示的 API 地址中将版本信息参数替换为实际的版本号
s.DocInclusionPredicate((version, apiDescription) =>
{
if (!version.Equals(apiDescription.GroupName))
return false;
var values = apiDescription.RelativePath
.Split('/')
.Select(v => v.Replace("v{version}", apiDescription.GroupName)); apiDescription.RelativePath = string.Join("/", values);
return true;
});
// 参数使用驼峰命名方式
s.DescribeAllParametersInCamelCase();
// 取消 API 文档须要输入版本信息
s.OperationFilter<RemoveVersionFromParameter>();
// 获取接口文档描述信息
var basePath = Path.GetDirectoryName(AppContext.BaseDirectory);
var apiPath = Path.Combine(basePath, "Ingos.Api.xml");
s.IncludeXmlComments(apiPath, true);
});
}
复制代码
当咱们配置完成后就能够在 Startup 类中去启用 Swagger 文档。
public void ConfigureServices(IServiceCollection services) {
// 添加对于 swagger 文档的支持
services.AddSwagger();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider) {
// 启用 Swagger 文档
app.UseSwagger();
app.UseSwaggerUI(s =>
{
// 默认加载最新版本的 API 文档
foreach (var description in provider.ApiVersionDescriptions.Reverse())
{
s.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
$"Sample API {description.GroupName.ToUpperInvariant()}");
}
});
}
复制代码
public class RemoveVersionFromParameter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context) {
var versionParameter = operation.Parameters.Single(p => p.Name == "version");
operation.Parameters.Remove(versionParameter);
}
}
复制代码
当咱们实现自定义的接口后就能够在以前针对 Swagger 的扩展方法中调用这个过滤方法,从而实现移除版本信息的目的,扩展方法中的添加位置以下所示。
public static void AddSwagger(this IServiceCollection services) {
// 配置 Swagger 文档信息
services.AddSwaggerGen(s =>
{
// 取消 API 文档须要输入版本信息
s.OperationFilter<RemoveVersionFromParameter>();
});
}
复制代码
最终的实现效果以下图所示,能够看到,参数列表中已经没有版本信息这个参数,可是咱们在进行接口测试时会自动帮咱们添加上版本参数信息。
5、构建符合 Restful 风格的接口
在没有采用 Restful 风格来构建接口返回值时,咱们可能会习惯于在接口返回的信息中添加一个接口是否请求成功的标识,就像下面代码中示例的这种返回形式。
{
sueecss: true
msg: '',
data: [{
id: '20190720214402',
name: 'zhangsan'
}]
}
复制代码
可是,当咱们想要构建符合 Restful 风格的接口时,咱们就不能再这样进行设计了,咱们应该经过返回的 HTTP 响应状态码来标识此次访问是否成功。一些比较经常使用的 HTTP 状态码以下表所示。
HTTP 状态码 | 涵义 | 解释说明 |
---|---|---|
200 | OK | 用于通常性的成功返回,不可用于请求错误返回 |
201 | Created | 资源被建立 |
202 | Accepted | 用于资源异步处理的返回,仅表示请求已经收到。对于耗时比较久的处理,通常用异步处理来完成 |
204 | No Content | 此状态可能会出如今 PUT、POST、DELETE 的请求中,通常表示资源存在,但消息体中不会返回任何资源相关的状态或信息 |
400 | Bad Request | 用于客户端通常性错误信息返回, 在其它 4xx 错误之外的错误,也可使用,错误信息通常置于 body 中 |
401 | Unauthorized | 接口须要受权访问,为经过受权验证 |
403 | Forbidden | 当前的资源被禁止访问 |
404 | Not Found | 找不到对应的信息 |
500 | Internal Server Error | 服务器内部错误 |
咱们知道 HTTP 共有四个谓词方法,分别为 Get、Post、Put 和 Delete,在以前咱们可能更多的是使用 Get 和 Post,对于 Put 和 Delete 方法可能并不会使用。一样的,若是咱们须要建立符合 Restful 风格的接口,咱们则须要根据这四个 HTTP 方法谓词一些约定俗成的功能定义去定义对应接口的 HTTP 方法。
HTTP 谓词方法 | 解释说明 |
---|---|
GET | 获取资源信息 |
POST | 提交新的资源信息 |
PUT | 更新已有的资源信息 |
DELETE | 删除资源 |
例如,对于一个获取全部资源的方法,咱们可能会定义接口的默认返回 HTTP 状态码为 200 或是 400,当状态码为 200 时,表明数据获取成功,接口能够正常返回数据,当状态码为 400 时,则表明接口访问出现问题,此时则返回错误信息对象。
在 ASP.NET Core Web API 中,咱们能够经过在 Action 上添加 ProducesResponseType 特性来定义接口的返回状态码。经过 F12 按键咱们能够进入 ProducesResponseType 这个特性,能够看到这个特性存在两个构造方法,咱们能够只定义接口返回 HTTP 状态码或者是在定义接口返回的状态码时同时返回的具体对象信息。
/// <summary>
/// 获取所有的用户信息
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<UserListDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Get() {
// 一、获取资源数据
// 二、判断数据获取是否成功
if (true)
return Ok(new List<UserListDto>());
else return BadRequest(new { statusCode = StatusCodes.Status400BadRequest, description = "错误描述", msg = "错误信息" });
}
复制代码
固然,当接口的 HTTP 返回状态码为 400 时,最终仍是会返回咱们自定义的错误信息对象,因此这里为了避免形成先后端对接上的歧义,咱们最好将返回的对象信息也做为参数添加到 ProducesResponseType 特性中。
HTTP 状态码 | 方法名称 |
---|---|
200 | OK() |
201 | Created() |
202 | Accepted() |
204 | NoContent() |
400 | BadRequest() |
401 | Unauthorized() |
403 | Forbid() |
404 | NotFound() |
6、使用 Web API 分析器
在上面的示例中,由于咱们须要指定接口须要返回的 HTTP 状态码,因此咱们须要提早添加好 ProducesResponseType 特性,在某些时候咱们可能在代码中添加了一种 HTTP 状态码的返回结果,但是却忘了添加特性描述,那么有没有一种便捷的方式提示咱们呢?
在 ASP.NET Core 2.2 及之后更新的 ASP.NET Core 版本中,咱们能够经过 Nuget 去添加 Microsoft.AspNetCore.Mvc.Api.Analyze 这个包,从而实现对咱们的 API 进行分析,首先咱们须要将这个包添加到咱们的 API 项目中。
Install-Package Microsoft.AspNetCore.Mvc.Api.Analyzers
复制代码
例如在下面的接口代码中,咱们根据用户的惟一标识去寻找用户数据,当获取不到数据的时候,返回的 HTTP 状态码为 400,而咱们只添加了 HTTP 状态码为 200 的特性说明。此时,分析器将 HTTP 404 状态代码的缺失特性说明作为一个警告,并提供了修复此问题的选项,咱们进行修复后就能够自动添加特性。
/// <summary>
/// 获取用户详细信息
/// </summary>
/// <param name="id">用户惟一标识</param>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserEditDto), StatusCodes.Status200OK)]
public IActionResult Get(string id) {
// 一、根据 Id 获取用户信息
UserEditDto user = null;
if (user == null)
return NotFound();
else return Ok(user);
}
复制代码
在进行特性补齐的时候,分析器也帮咱们填加了一个 ProducesDefaultResponseType 特性。经过在微软的文档中指向的 Swagger 文档(Swagger Default Response)中能够了解到,若是咱们接口无论是什么状态,最终返回的 response 响应结构都是相同的,咱们就能够直接使用 ProducesDefaultResponseType 特性来指定 response 的响应结构,而不须要每一个 HTTP 状态都添加一个特性。
在本篇文章中,主要介绍了一些我在使用 ASP.NET Core Web API 的过程当中使用到的一些小技巧,以及在之前踩过坑后的一些解决方案,若是对你能有一点的帮助的话,不胜荣幸。同时,若是你有更好的解决方案,或者是针对一些你以前踩过的 Web API 坑的解决方案,也欢迎你在评论区中提出。
占坑
做者:墨墨墨墨小宇
我的简介:96年生人,出生于安徽某四线城市,毕业于Top 10000000 院校。.NET程序员,枪手死忠,喵星人。于2016年12月开始.NET程序员生涯,微软.NET技术的坚决坚持者,立志成为云养猫的少年中面向谷歌编程最厉害的.NET程序员。
我的博客:yuiter.com
博客园博客:www.cnblogs.com/danvic712