ASP.NET Core Web API使用小技巧

轉(zhuǎn)自:墨墨墨墨小宇 cnblogs.com/danvic712/p/11255423.html
一、前言
在目前的軟件開發(fā)的潮流中,不管是前后端分離還是服務(wù)化改造,后端更多的是通過構(gòu)建 API 接口服務(wù)從而為 web、app、desktop 等各種客戶端提供業(yè)務(wù)支持,如何構(gòu)建一個符合規(guī)范、容易理解的 API 接口是我們后端開發(fā)人員需要考慮的。
在本篇文章中,我將列舉一些我在使用 ASP.NET Core Web API 構(gòu)建接口服務(wù)時使用到的一些小技巧,因才疏學(xué)淺,可能會存在不對的地方,歡迎指出。
代碼倉儲:https://github.com/Lanesra712/ingos-server
二、Step by Step
因為本篇文章中涉及到的一些知識點在之前的文章中也已經(jīng)有具體的解釋了,所以這里只會說明如何在 ASP.NET Core Web API 中如何去使用,不會做過多的詳細(xì)介紹。
本篇文章中使用的代碼是基于 .NET Core 2.2 + .NET Standard 2.0 進行構(gòu)建的,如果你采用的版本與我使用的不同,可能最終實現(xiàn)起來的代碼會有所不同,請?zhí)崆爸ぁ?/span>
同時,本篇文章中所有示例代碼都會存在于前言中所列出的 github repo 中,我會嘗試將每個功能點的開發(fā)作為一次 commit,并且也會在后續(xù)進行不定期的更新完善,最終搭建一個基于領(lǐng)域驅(qū)動思想的后端項目模板,如果對你有幫助的話,歡迎持續(xù)關(guān)注。
1、使用小寫路由
在我之前的一篇文章中《構(gòu)建可讀性更高的 ASP.NET Core 路由》有提到過,因為 .NET 默認(rèn)采用 Pascal 的類命名方式,如果采用默認(rèn)生成的路由,最終構(gòu)建出的路由地址會存在大小寫混在一起的情況,雖然在 .NET Core 中大小寫的路由地址最終都會對于到正確的資源上,但是為了更好的符合前端的規(guī)范,所以這里我們首先按照之前的文章中所列出的方法去修改默認(rèn)生成的路由地址格式。
因為這里我們最終想要實現(xiàn)的是符合 Restful 風(fēng)格的 API 接口,所以這里我們首先需要將默認(rèn)生成的 URL 地址改為全小寫模式。
public void ConfigureServices(IServiceCollection services)
{
// 采用小寫的 URL 路由模式
services.AddRouting(options =>
{
options.LowercaseUrls = true;
});
}
如果你有看過構(gòu)建可讀性更高的 ASP.NET Core 路由這篇文章,你會發(fā)現(xiàn)其實我們最終實現(xiàn)的是 hyphen(-) 格式的 Url 地址,那么這里我們?yōu)槭裁床贿M行后續(xù)的修改了呢?
如果你有查看 .NET Core 默認(rèn)模板中生成的 API Controller,仔細(xì)看下,這里其實是使用的特性路由,所以這里我們并不能通過 Startup.UseMvc 定義的傳統(tǒng)路由模板,或是直接在 Startup.Configure 中的 UseMvcWithDefaultRoute 方法去修改我們的生成的路由地址格式。
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
}2、允許跨域請求
不管是后端接口的服務(wù)化改造,還是只是單純的前后端分離項目開發(fā),我們的前端項目與后端接口通常不會部署在一起,所以我們需要解決前端訪問接口時會涉及到的跨域訪問的問題。
針對跨域請求,我們可以采用 jsonp、或者是通過給 nginx 服務(wù)器配置響應(yīng)的 header 參數(shù)頭信息、或者是使用 CORS,又或是其它的解決方案。你可以自由選擇,這里我采用在后端接口中直接配置對于 CORS 的支持。
在 .NET Core 中,已經(jīng)在 Microsoft.AspNetCore.Cors 這個類庫中添加了對于 CORS 的支持,因為這個類庫是存在于我們已經(jīng)安裝的 .NET Core SDK 中,所以這里我們并不需要通過 Nuget 進行安裝,可以直接使用。
在 .NET Core 中配置 CORS 規(guī)則,我們可以通過在 Startup.ConfigureServices 這個方法中添加不同的授權(quán)策略,之后再針對某個 Controller 或是 Action 通過添加 EnableCors 這個 Attribute 的方式進行配置,這里如果指定了 policy 策略名稱,則會使用指定的策略,如果沒有指定,則適用于系統(tǒng)的默認(rèn)配置。同樣的,我們也可以只設(shè)置一個策略,直接針對整個項目進行配置,這里我采用對整個項目采用通用的跨域請求配置方案。
在配置 CORS 策略時,我們可以設(shè)置只允許來源于某些 URL 地址的請求可以訪問,或者是指定接口只允許某些 HTTP 方法進行訪問,或者是在請求的 header 中必須包含某些信息才可以訪問我們的接口。
在下面的代碼中,我定義了針對整個項目的跨域請求策略,這里我只是設(shè)置了對于接口請求方 URL 地址的控制,通過讀取配置文件中的數(shù)據(jù),從而達(dá)到只允許某些 IP 可以訪問的我們接口的目的。
public class Startup
{
// 默認(rèn)的跨域請求策略名稱
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 授權(quán)過濾器
options => options.Filters.Add(new CorsAuthorizationFilterFactory(_defaultCorsPolicyName)) ).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// 配置 CORS 授權(quán)策略
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);
}
}例如在下面的設(shè)置中,我只允許這一個地址可以訪問我們的接口,如果需要指定多個的話,則可以通過英文的 , 進行分隔。
"Application": {
"CorsOrigins": "http://127.0.0.1:5050"
}某些情況下,如果我們不想進行限制的話,只需要將值改為 * 即可。
"Application": {
"CorsOrigins": "*"
}3、添加接口版本控制
在一些涉及到接口功能升級的場景下,當(dāng)我們需要修改接口邏輯而舊版本的接口無法停用的情況時,為了減少對于原有接口的影響,我們可以采取為接口添加版本信息的形式,從而降低因采用不同版本而造成的影響。如果你想要詳細(xì)了解的話,可以查看這篇文章《ASP.NET Core 實戰(zhàn):構(gòu)建帶有版本控制的 API 接口》。
在實現(xiàn)具有版本控制的接口前,首先我們需要通過 Nuget 添加下面的兩個 dll,因為我是在 Ingos.Api.Core 這個類庫中進行配置的,所以我安裝到了這個類庫下,你需要根據(jù)你自己的情況選擇最終是安裝到 Api 接口項目中還是在別的類庫下。
Install-Package Microsoft.AspNetCore.Mvc.Versioning
Install-Package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer在安裝完成之后,我們就可以在 Startup.ConfigureServices 方法中,為項目中的接口配置版本信息,這里我采用的方案是將版本號添加到接口的 URL 地址中。
因為對于所有中間件的配置都會在 Startup.ConfigureServices 方法中,為了保持該方法的純凈性,這里我寫了一個擴展方法用于配置我們的 api 的版本,之后直接調(diào)用即可。
public static class ApiVersionExtension
{
/// <summary>
/// 添加 API 版本控制擴展方法
/// </summary>
/// <param name="services">生命周期中注入的服務(wù)集合 <see cref="IServiceCollection"/></param>
public static void AddApiVersion(this IServiceCollection services)
{
// 添加 API 版本支持
services.AddApiVersioning(o =>
{
// 是否在響應(yīng)的 header 信息中返回 API 版本信息
o.ReportApiVersions = true;
// 默認(rèn)的 API 版本
o.DefaultApiVersion = new ApiVersion(1, 0);
// 未指定 API 版本時,設(shè)置 API 版本為默認(rèn)的版本
o.AssumeDefaultVersionWhenUnspecified = true;
});
// 配置 API 版本信息
services.AddVersionedApiExplorer(option =>
{
// api 版本分組名稱
option.GroupNameFormat = " v VVVV";
// 未指定 API 版本時,設(shè)置 API 版本為默認(rèn)的版本
option.AssumeDefaultVersionWhenUnspecified = true;
});
}
}擴展方法最終實現(xiàn)方式如上面的代碼所示,之后我們就可以直接在 ConfigureServices 方法中直接進行調(diào)用這個擴展方法就可以了。
// 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();
}現(xiàn)在我們刪除項目創(chuàng)建時默認(rèn)生成的 ValuesController,在 Controllers 目錄下建立一個 v1 文件夾,代表此文件夾下都是 v1 版本的控制器。添加一個 UsersController 用來獲取系統(tǒng)的用戶資源,現(xiàn)在項目的文件結(jié)構(gòu)如下圖所示。

現(xiàn)在我們來改造我們的 UsersController,我們只需要在 Controller 或是 Action 上添加 ApiVersion 特性就可以指定當(dāng)前 Controller/Action 的版本信息。同時,因為我需要將 API 的版本信息添加到生成的 URL 地址中,所以這里我們需要修改特性路由的模板,將我們的版本以占位符的形式添加到生成的路由 URL 地址中,修改完成后的代碼及實現(xiàn)的效果如下所示。
[ApiVersion("1.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
}
4、添加對于 Swagger 接口文檔的支持
在前后端分離開發(fā)的情況下,我們需要提供給前端開發(fā)人員一個接口文檔,從而讓前端開發(fā)人員知道以什么樣的 HTTP 方法或是傳遞什么樣的參數(shù)給后端接口,從而獲取到正確的數(shù)據(jù),而 Swagger 則提供了一種自動生成接口文檔的方式,同時也提供類似于 Postman 的功能,可以實現(xiàn)對于接口的實時調(diào)用測試。
首先,我們需要通過 Nuget 添加 Swashbuckle.AspNetCore 這個 dll 文件,之后我們就可以在此基礎(chǔ)上實現(xiàn)對于 Swagger 的配置。
Install-Package Swashbuckle.AspNetCore與上面配置 API 接口的版本信息相似,這里我依舊采用構(gòu)建擴展方法的方式來實現(xiàn)對于 Swagger 中間件的配置。具體的配置過程可以查看我之前寫的文章(ASP.NET Core 實戰(zhàn):構(gòu)建帶有版本控制的 API 接口),這里只列出最終配置完成的代碼。
public static void AddSwagger(this IServiceCollection services)
{
// 配置 Swagger 文檔信息
services.AddSwaggerGen(s =>
{
// 根據(jù) 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 = "[email protected]",
Url = "https://yuiter.com"
},
Description = "Ingos.API 接口文檔",
Title = "Ingos.API",
Version = description.ApiVersion.ToString()
});
}
// 在 Swagger 文檔顯示的 API 地址中將版本信息參數(shù)替換為實際的版本號
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;
});
// 參數(shù)使用駝峰命名方式
s.DescribeAllParametersInCamelCase();
// 取消 API 文檔需要輸入版本信息
s.OperationFilter<RemoveVersionFromParameter>();
// 獲取接口文檔描述信息
var basePath = Path.GetDirectoryName(AppContext.BaseDirectory);
var apiPath = Path.Combine(basePath, "Ingos.Api.xml");
s.IncludeXmlComments(apiPath, true);
});
}當(dāng)我們配置完成后就可以在 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 =>
{
// 默認(rèn)加載最新版本的 API 文檔
foreach (var description in provider.ApiVersionDescriptions.Reverse())
{ s.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
$"Sample API {description.GroupName.ToUpperInvariant()}");
}
});
}
因為我們在之前設(shè)置構(gòu)建的 API 路由時包含了版本信息,所以在最終生成的 Swagger 文檔中進行測試時,我們都需要在參數(shù)列表中添加 API 版本這個參數(shù)。這無疑是有些不方便,所以這里我們可以通過繼承 IOperationFilter 接口,控制在生成 API 文檔時移除 API 版本參數(shù),接口的實現(xiàn)方法如下所示。
public class RemoveVersionFromParameter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
var versionParameter = operation.Parameters.Single(p => p.Name == "version");
operation.Parameters.Remove(versionParameter);
}
}當(dāng)我們實現(xiàn)自定義的接口后就可以在之前針對 Swagger 的擴展方法中調(diào)用這個過濾方法,從而實現(xiàn)移除版本信息的目的,擴展方法中的添加位置如下所示。
public static void AddSwagger(this IServiceCollection services)
{
// 配置 Swagger 文檔信息
services.AddSwaggerGen(s =>
{
// 取消 API 文檔需要輸入版本信息
s.OperationFilter<RemoveVersionFromParameter>();
});
}最終的實現(xiàn)效果如下圖所示,可以看到,參數(shù)列表中已經(jīng)沒有版本信息這個參數(shù),但是我們在進行接口測試時會自動幫我們添加上版本參數(shù)信息。

這里需要注意,因為我們需要在最終生成的 Swagger 文檔中顯示出我們對于 Controller 或是 Action 添加的注釋信息,所以這里我們需要在 Web Api 項目的屬性選項中勾選上輸出 XML 文檔文件。同時如果你不想 VS 一直提示你有方法沒有添加參數(shù)信息,這里我們可以在取消顯示警告這里添加上 1591 這個參數(shù)。

5、構(gòu)建符合 Restful 風(fēng)格的接口
在沒有采用 Restful 風(fēng)格來構(gòu)建接口返回值時,我們可能會習(xí)慣于在接口返回的信息中添加一個接口是否請求成功的標(biāo)識,就像下面代碼中示例的這種返回形式。
{
sueecss: true
msg: ,
data: [{
id: 20190720214402 ,
name: zhangsan
}]
}但是,當(dāng)我們想要構(gòu)建符合 Restful 風(fēng)格的接口時,我們就不能再這樣進行設(shè)計了,我們應(yīng)該通過返回的 HTTP 響應(yīng)狀態(tài)碼來標(biāo)識這次訪問是否成功。一些比較常用的 HTTP 狀態(tài)碼如下表所示。

我們知道 HTTP 共有四個謂詞方法,分別為 Get、Post、Put 和 Delete,在之前我們可能更多的是使用 Get 和 Post,對于 Put 和 Delete 方法可能并不會使用。
同樣的,如果我們需要創(chuàng)建符合 Restful 風(fēng)格的接口,我們則需要根據(jù)這四個 HTTP 方法謂詞一些約定俗成的功能定義去定義對應(yīng)接口的 HTTP 方法。

例如,對于一個獲取所有資源的方法,我們可能會定義接口的默認(rèn)返回 HTTP 狀態(tài)碼為 200 或是 400,當(dāng)狀態(tài)碼為 200 時,代表數(shù)據(jù)獲取成功,接口可以正常返回數(shù)據(jù),當(dāng)狀態(tài)碼為 400 時,則代表接口訪問出現(xiàn)問題,此時則返回錯誤信息對象。
在 ASP.NET Core Web API 中,我們可以通過在Action上添加 ProducesResponseType 特性來定義接口的返回狀態(tài)碼。通過 F12 按鍵我們可以進入 ProducesResponseType 這個特性,可以看到這個特性存在兩個構(gòu)造方法,我們可以只定義接口返回 HTTP 狀態(tài)碼或者是在定義接口返回的狀態(tài)碼時同時返回的具體對象信息。
上面給出的接口案例的示例代碼如下所示,從下圖中可以看到,Swagger 會自動根據(jù)我們的 ProducesResponseType 特性來列出我們接口可能返回的 HTTP 狀態(tài)碼和對象信息。這里因為是示例程序,UserListDto 并沒有定義具體的屬性信息,所以這里顯示的是一個不包含任何屬性的對象數(shù)組。
/// <summary>
/// 獲取全部的用戶信息
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<UserListDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Get()
{
// 1、獲取資源數(shù)據(jù)
// 2、判斷數(shù)據(jù)獲取是否成功
if (true)
return Ok(new List<UserListDto>());
else
return BadRequest(new
{
statusCode = StatusCodes.Status400BadRequest,
description = "錯誤描述",
msg = "錯誤信息"
});
}
可能這里你可能會有疑問,當(dāng)接口返回的 HTTP 狀態(tài)碼為 400 時,返回的信息是什么鬼,與我們定義的錯誤信息對象字段不同啊?原來,在 ASP.NET Core 2.1 之后的版本中,對于 API 接口返回 400 的 HTPP 狀態(tài)碼會默認(rèn)返回 ProblemDetails 對象,因為這里我們并沒有將接口中的返回 BadRequest 中的錯誤信息對象作為 ProducesResponseType 特性的構(gòu)造函數(shù)的參數(shù),所以這里就采用了默認(rèn)的錯誤信息對象。
當(dāng)然,當(dāng)接口的 HTTP 返回狀態(tài)碼為 400 時,最終還是會返回我們自定義的錯誤信息對象,所以這里為了不造成前后端對接上的歧義,我們最好將返回的對象信息也作為參數(shù)添加到 ProducesResponseType 特性中。

同時,除了上面示例的接口中通過返回 OK 方法和 BadRequest 方法來表明接口的返回 HTTP 狀態(tài)碼,在 ASP.NET Core Web API 中還有下列繼承于 ObjectResult 的方法來表明接口返回的狀態(tài)碼,對應(yīng)信息如下。

6、使用 Web API 分析器
在上面的示例中,因為我們需要指定接口需要返回的 HTTP 狀態(tài)碼,所以我們需要提前添加好 ProducesResponseType 特性,在某些時候我們可能在代碼中添加了一種 HTTP 狀態(tài)碼的返回結(jié)果,可是卻忘了添加特性描述,那么有沒有一種便捷的方式提示我們呢?
在 ASP.NET Core 2.2 及以后更新的 ASP.NET Core 版本中,我們可以通過 Nuget 去添加 Microsoft.AspNetCore.Mvc.Api.Analyze 這個包,從而實現(xiàn)對我們的 API 進行分析,首先我們需要將這個包添加到我們的 API 項目中。
Install-Package Microsoft.AspNetCore.Mvc.Api.Analyzers例如在下面的接口代碼中,我們根據(jù)用戶的唯一標(biāo)識去尋找用戶數(shù)據(jù),當(dāng)獲取不到數(shù)據(jù)的時候,返回的 HTTP 狀態(tài)碼為 400,而我們只添加了 HTTP 狀態(tài)碼為 200 的特性說明。此時,分析器將 HTTP 404 狀態(tài)代碼的缺失特性說明做為一個警告,并提供了修復(fù)此問題的選項,我們進行修復(fù)后就可以自動添加特性。
/// <summary>
/// 獲取用戶詳細(xì)信息
/// </summary>
/// <param name="id">用戶唯一標(biāo)識</param>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserEditDto), StatusCodes.Status200OK)]
public IActionResult Get(string id)
{
// 1、根據(jù) Id 獲取用戶信息
UserEditDto user = null;
if (user == null)
return NotFound();
else
return Ok(user);
}
但是,在自動完成文檔補全后其實還是需要我們進行一些操作的,例如,如果我們需要指定返回值的 Type 類型,還是需要我們自己手動添加到 ProducesResponseType 特性上的。
在進行特性補齊的時候,分析器也幫我們填加了一個ProducesDefaultResponseType特性。
通過在微軟的文檔中指向的 Swagger 文檔(Swagger Default Response)中可以了解到,如果我們接口不管是什么狀態(tài),最終返回的 response 響應(yīng)結(jié)構(gòu)都是相同的,我們就可以直接使用 ProducesDefaultResponseType 特性來指定 response 的響應(yīng)結(jié)構(gòu),而不需要每個 HTTP 狀態(tài)都添加一個特性。
三、總結(jié)
在本篇文章中,主要介紹了一些我在使用 ASP.NET Core Web API 的過程中使用到的一些小技巧,以及在以前踩過坑后的一些解決方案,如果對你能有一點的幫助的話,不勝榮幸。同時,如果你有更好的解決方案,或者是針對一些你之前踩過的 Web API 坑的解決方案,也歡迎你在評論區(qū)中提出


臥槽!微信可以改彩色昵稱了?。?!

臥槽,微信狀態(tài)被玩壞了,附大量微信狀態(tài)視屏素材!
