
ASP.NET Web API快速入門介紹
{
// 采用小寫的 URL 路由模式
services.AddRouting(options =>
{
options.LowercaseUrls = true;
});
}
如果你有看過構建可讀性更高的 ASP.NET Core 路由這篇文章,你會發現其實我們最終實現的是 hyphen(-) 格式的 Url 地址,那么這里我們為什么不進行后續的修改了呢?
如果你有查看 .NET Core 默認模板中生成的 API Controller,仔細看下,這里其實是使用的特性路由,所以這里我們并不能通過 Startup.UseMvc 定義的傳統路由模板,或是直接在 Startup.Configure 中的 UseMvcWithDefaultRoute 方法去修改我們的生成的路由地址格式。
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
}
不管是后端接口的服務化改造,還是只是單純的前后端分離項目開發,我們的前端項目與后端接口通常不會部署在一起,所以我們需要解決前端訪問接口時會涉及到的跨域訪問的問題。
針對跨域請求,我們可以采用 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": "*"
}
在一些涉及到接口功能升級的場景下,當我們需要修改接口邏輯而舊版本的接口無法停用的情況時,為了減少對于原有接口的影響,我們可以采取為接口添加版本信息的形式,從而降低因采用不同版本而造成的影響。如果你想要詳細了解的話,可以查看這篇文章,電梯直達 =》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 用來獲取系統的用戶資源,現在項目的文件結構如下圖所示。
現在我們來改造我們的 UsersController,我們只需要在 Controller 或是 Action 上添加 ApiVersion 特性就可以指定當前 Controller/Action 的版本信息。同時,因為我需要將 API 的版本信息添加到生成的 URL 地址中,所以這里我們需要修改特性路由的模板,將我們的版本以占位符的形式添加到生成的路由 URL 地址中,修改完成后的代碼及實現的效果如下所示。
[ApiVersion("1.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
}
在前后端分離開發的情況下,我們需要提供給前端開發人員一個接口文檔,從而讓前端開發人員知道以什么樣的 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()}");
}
});
}
因為我們在之前設置構建的 API 路由時包含了版本信息,所以在最終生成的 Swagger 文檔中進行測試時,我們都需要在參數列表中添加 API 版本這個參數。這無疑是有些不方便,所以這里我們可以通過繼承 IOperationFilter 接口,控制在生成 API 文檔時移除 API 版本參數,接口的實現方法如下所示。
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>();
});
}
最終的實現效果如下圖所示,可以看到,參數列表中已經沒有版本信息這個參數,但是我們在進行接口測試時會自動幫我們添加上版本參數信息。
這里需要注意,因為我們需要在最終生成的 Swagger 文檔中顯示出我們對于 Controller 或是 Action 添加的注釋信息,所以這里我們需要在 Web Api 項目的屬性選項中勾選上輸出 XML 文檔文件。同時如果你不想 VS 一直提示你有方法沒有添加參數信息,這里我們可以在取消顯示警告這里添加上 1591 這個參數。
在沒有采用 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 狀態碼或者是在定義接口返回的狀態碼時同時返回的具體對象信息。
上面給出的接口案例的示例代碼如下所示,從下圖中可以看到,Swagger 會自動根據我們的 ProducesResponseType 特性來列出我們接口可能返回的 HTTP 狀態碼和對象信息。這里因為是示例程序,UserListDto 并沒有定義具體的屬性信息,所以這里顯示的是一個不包含任何屬性的對象數組。
/// <summary>
/// 獲取全部的用戶信息
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<UserListDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult Get()
{
// 1、獲取資源數據
// 2、判斷數據獲取是否成功
if (true)
return Ok(new List<UserListDto>());
else
return BadRequest(new
{
statusCode = StatusCodes.Status400BadRequest,
description = "錯誤描述",
msg = "錯誤信息"
});
}
可能這里你可能會有疑問,當接口返回的 HTTP 狀態碼為 400 時,返回的信息是什么鬼,與我們定義的錯誤信息對象字段不同啊?原來,在 ASP.NET Core 2.1 之后的版本中,對于 API 接口返回 400 的 HTPP 狀態碼會默認返回 ProblemDetails 對象,因為這里我們并沒有將接口中的返回 BadRequest 中的錯誤信息對象作為 ProducesResponseType 特性的構造函數的參數,所以這里就采用了默認的錯誤信息對象。
當然,當接口的 HTTP 返回狀態碼為 400 時,最終還是會返回我們自定義的錯誤信息對象,所以這里為了不造成前后端對接上的歧義,我們最好將返回的對象信息也作為參數添加到 ProducesResponseType 特性中。
同時,除了上面示例的接口中通過返回 OK 方法和 BadRequest 方法來表明接口的返回 HTTP 狀態碼,在 ASP.NET Core Web API 中還有下列繼承于 ObjectResult 的方法來表明接口返回的狀態碼,對應信息如下。
HTTP 狀態碼 | 方法名稱 |
---|---|
200 | OK() |
201 | Created() |
202 | Accepted() |
204 | NoContent() |
400 | BadRequest() |
401 | Unauthorized() |
403 | Forbid() |
404 | NotFound() |
在上面的示例中,因為我們需要指定接口需要返回的 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)
{
// 1、根據 Id 獲取用戶信息
UserEditDto user = null;
if (user == null)
return NotFound();
else
return Ok(user);
}
但是,在自動完成文檔補全后其實還是需要我們進行一些操作的,例如,如果我們需要指定返回值的 Type 類型,還是需要我們自己手動添加到 ProducesResponseType 特性上的。
在進行特性補齊的時候,分析器也幫我們填加了一個 ProducesDefaultResponseType 特性。通過在微軟的文檔中指向的 Swagger 文檔(Swagger Default Response)中可以了解到,如果我們接口不管是什么狀態,最終返回的 response 響應結構都是相同的,我們就可以直接使用 ProducesDefaultResponseType 特性來指定 response 的響應結構,而不需要每個 HTTP 狀態都添加一個特性。
? ?在本篇文章中,主要介紹了一些我在使用 ASP.NET Core Web API 的過程中使用到的一些小技巧,以及在以前踩過坑后的一些解決方案,如果對你能有一點的幫助的話,不勝榮幸。同時,如果你有更好的解決方案,或者是針對一些你之前踩過的 Web API 坑的解決方案,也歡迎你在評論區中提出。
本文章轉載微信公眾號@ZerekZhang聊編程