右擊【解決方案】,然后點擊【管理Nuget包】,搜索【Swashbuckle.AspNetCore】包,點擊【安裝】即可,博主這里下載的是【最新穩(wěn)定版5.6.3】。
在【Startup.cs】文件中的【ConfigureService】類中引入命名空間,并注冊Swagger服務并配置文檔信息。
//引入命名空間
using Microsoft.OpenApi.Models;
//注冊Swagger
services.AddSwaggerGen(u=> {
u.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Version="Ver:1.0.0",//版本
Title="xxx后臺管理系統(tǒng)",//標題
Description="xxx后臺管理系統(tǒng):包括人員信息、團隊管理等。",//描述
Contact=new Microsoft.OpenApi.Models.OpenApiContact {
Name="西瓜程序猿",
Email="xxx@qq.com"
}
});
});
如果安裝的 Swashbuckle.AspNetCore Nuget包版本<=3.0.0,寫法略有不同。將 【OpenApiInfo】 替換成 【Info】 即可。
services.AddSwaggerGen(x=>
{
x.SwaggerDoc("v1", new Info() { Title="Web Api", Version="v1" });
});
在【Startup.cs】文件中的【Configure】類中啟用Swagger中間件,為生成的JSON文檔和SwaggerUI提供服務。
//啟用Swagger中間件
app.UseSwagger();
//配置SwaggerUI
app.UseSwaggerUI(u=>
{
u.SwaggerEndpoint("/swagger/v1/swagger.json", "WebAPI_v1");
});
2.5.1-如果我們直接運行項目,會出現(xiàn)這樣的界面,說明我們的Web API程序沒有問題。
2.5.2-然后我們在地址欄中輸入【swagger/v1/swagger.json】。
可以看到瀏覽器出現(xiàn)這樣的界面,如果出現(xiàn)這樣的界面,說明Swagger也沒有問題。
注意:按照2.5.1在地址欄中的【swagger/v1/swagger.json】需要與在【Startup.cs】文件中的【Configure】類中啟用Swagger中間件添加的代碼保持一致,因為這段代碼就是自動生成JSON文件的,你配置的路徑和JSON文件地址是什么,就在瀏覽器中輸入對應的即可。
2.5.3-以上步驟都沒問題的話,然后我們地址欄中輸入【swagger/index.html】。
如果能出現(xiàn)以下界面,說明SwaggerUI也沒有問題,都全部正常了。
2.6.1-打開【launchSettings.json】這個文件。
2.6.2-然后將【launchUrl】的值從【weatherforecast】修改成【swagger】。
2.6.3-然后運行項目就直接進入Swagger首頁了。
3.1-雙擊解決方案
3-2-然后進入這個頁面,加上這個代碼
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
3-3.在【Startup.cs】文件中的【ConfigureService】類中注冊讀取XML信息的Swagger。
#region 讀取xml信息
// 使用反射獲取xml文件,并構造出文件的路徑
var xmlFile=$"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath=Path.Combine(AppContext.BaseDirectory, xmlFile);
// 啟用xml注釋,該方法第二個參數(shù)啟用控制器的注釋,默認為false.
u.IncludeXmlComments(xmlPath, true);
#endregion
注意:
4.1-寫一個實例:在【W(wǎng)eatherForecastController】控制器中寫一個方法。
4.2-寫上以下代碼然后進行請求。
/// <summary>
/// 這是一個例子
/// </summary>
/// <remarks>
/// 描述:這是一個帶參數(shù)的GET請求
/// Web API
/// </remarks>
/// <param name="id">主鍵ID</param>
/// <returns>測試字符串</returns>
[HttpGet("{id}")]
public ActionResult<string> Get(int id) {
return $"你請求的ID是:{id}";
}
4.3-可以看到【XML注釋】起作用了,然后調(diào)用也成功了。
(1)在方法中加上以下特性:
[ProducesResponseType(typeof(xxx),200)]
(2)在Remarks節(jié)點下進行注釋:
在需要進行隱藏的接口上加上以下特性即可:
[ApiExplorerSettings(IgnoreApi=true)]
如果不想加上"swagger",而輸入5000即可訪問,也可以自定義路由前綴,加上以下代碼即可。
(1)新建一個【index.html】,右擊該文件,點擊【屬性】,進行設置相關操作。
(2)在Startup.cs進行配置。
(3)靜態(tài)頁面下載地址:
https://github.com/swagger-api/swagger-ui/blob/master/dist/index.html
(1)在Startup.cs進行配置。
#region 開啟JWT
u.OperationFilter<SecurityRequirementsOperationFilter>();
u.AddSecurityDefinition("oauth2", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Description="JWT授權(數(shù)據(jù)將在請求頭中進行傳輸)直接在下框中輸入Bearer { token } (注意兩者之間是一個空格) ",
Name="Authorization",
In=Microsoft.OpenApi.Models.ParameterLocation.Header,//jwt默認存放Authorazation信息的位置(請求頭中)
Type=Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey
});
#endregion
(2)下載包,注意版本號問題,盡量保持一致,不然會報錯。
(3)然后將接口加上權限,去請求該接口,可以看到請求頭中會有以下信息了。
目錄結構:
(1)在【index.html】文件導入abp.js/swagger.js文件。
(2)在【swagger.js】里面需要注意請求授權地址。
(3)后端授權邏輯。
小明最近又遇到麻煩了,小紅希望對接接口傳送的數(shù)據(jù)進行驗證,既然是小紅要求,那小明說什么都得滿足呀,這還不簡單嘛。
[HttpPost]
public async Task<ActionResult<Todo>> PostTodo(Todo todo)
{
if (string.IsNullOrEmpty(todo.Name))
{
return Ok("名稱不能為空");
}
context.Todo.Add(todo);
await context.SaveChangesAsync();
return CreatedAtAction("GetTodo", new { id=todo.Id }, todo);
}
小明寫著寫著發(fā)現(xiàn)這樣寫,很多接口相同得地方都要寫,使得代碼比較臃腫。
在參數(shù)模型上打上注解
namespace App001.Models
{
/// <summary>
/// 待辦事項
/// </summary>
public class Todo
{
/// <summary>
/// ID
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// 名稱
/// </summary>
[Required(ErrorMessage="名稱不能為空")]
public string Name { get; set; }
}
}
Postman測試Name傳值未空時,則返回:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|df184e36-4e11844dfd38a626.",
"errors": {
"Name": [
"名稱不能為空"
]
}
}
注意Web API 控制器具有 [ApiController] 特性,則它們不必檢查ModelState.IsValid。在此情況下,如果模型狀態(tài)無效,將返回包含錯誤詳細信息的自動 HTTP 400 響應。
通過驗證特性可以指定要為無效輸入顯示的錯誤消息。 例如:
[Required(ErrorMessage="名稱不能為空")]
有兩種方式:
首先,創(chuàng)建ModelValidateActionFilterAttribute過濾器。
public class ModelValidateActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
//獲取驗證失敗的模型字段
var errors=context.ModelState
.Where(e=> e.Value.Errors.Count > 0)
.Select(e=> e.Value.Errors.First().ErrorMessage)
.ToList();
var str=string.Join("|", errors);
//設置返回內(nèi)容
var result=new
{
Code=10000,
Msg="未通過數(shù)據(jù)驗證。",
FullMsg=str
};
context.Result=new BadRequestObjectResult(result);
}
}
}
然后,Startup.ConfigureServices將過濾器添加到控制器中并關閉默認模型驗證,另外我們還添加了AddNewtonsoftJson。
//關閉默認模型驗證
services.Configure<ApiBehaviorOptions>(opt=> opt.SuppressModelStateInvalidFilter=true);
services.AddControllers(opt=>
{
//添加過濾器
opt.Filters.Add(typeof(ModelValidateActionFilterAttribute));
}).AddNewtonsoftJson(opt=>
{
//json字符串大小寫原樣輸出
opt.SerializerSettings.ContractResolver=new DefaultContractResolver();
});
最后,我們看一下返回效果:
{
"Code": 10000,
"Msg": "未通過數(shù)據(jù)驗證。",
"FullMsg": "名稱不能為空。"
}
services.Configure<ApiBehaviorOptions>(opt=>
{
opt.InvalidModelStateResponseFactory=actionContext=>
{
//獲取驗證失敗的模型字段
var errors=actionContext.ModelState
.Where(e=> e.Value.Errors.Count > 0)
.Select(e=> e.Value.Errors.First().ErrorMessage)
.ToList();
var str=string.Join("|", errors);
//設置返回內(nèi)容
var result=new
{
Code=10000,
Msg="未通過數(shù)據(jù)驗證。",
FullMsg=str
};
return new BadRequestObjectResult(result);
};
});
目前為止,小明把數(shù)據(jù)驗證也搞定了,是不是so easy!
章涵蓋
幾乎所有應用程序都需要數(shù)據(jù),尤其是基于 Web 的應用程序,其中大量客戶端與集中式實體(通常是基于 HTTP 的服務)交互,以訪問信息并可能更新或操作它們。在本書中,我們將學習如何設計和開發(fā)一種特定類型的基于 HTTP 的服務,其唯一目的是向這些客戶端提供數(shù)據(jù),允許它們以統(tǒng)一、結構化和標準化的方式與所需的信息進行交互:Web API。
在本章的第一部分中,我們將了解 Web API 的獨特特征,并了解如何將其應用于多個實際場景。在第二部分中,我們將熟悉 ASP.NET Core,我們將在本書中用于創(chuàng)建Web API的Web框架。
應用程序編程接口 (API) 是一種軟件接口,它公開計算機程序用于相互交互和交換信息的工具和服務。執(zhí)行此類交換所需的連接是通過通用通信標準(協(xié)議)、給定的可用操作集(規(guī)范)和數(shù)據(jù)交換格式(JSON、XML 等)建立的。
從這個定義中,我們可以很容易地看到,API 的主要目的是允許各方使用通用語法(或抽象)進行通信,該語法(或抽象)簡化和標準化每個引擎蓋下發(fā)生的事情。整體概念類似于現(xiàn)實世界的接口,后者也提供了一個通用的“語法”,允許不同的各方進行操作。一個完美的例子是插頭插座,這是所有國家電氣系統(tǒng)使用的抽象概念,允許家用電器和電子設備通過給定的電壓、頻率和插頭類型標準與電源交互。
圖1.1顯示了形成電網(wǎng)的主要組成部分:一個互連的網(wǎng)絡,用于從生產(chǎn)者向住宅消費者發(fā)電、傳輸和輸送電力。正如我們所看到的,每個組件都以不同的方式處理電力,并使用各種“協(xié)議”和“適配器”(電纜線、變壓器等)與其他組件進行通信,最終目標是將其帶到人們的家中。當住宅單元連接到電網(wǎng)時,家用電器(電視機、烤箱、冰箱等)可以通過安全、受保護、易于使用的接口(交流電源插頭)使用電力。如果我們考慮這些插座的工作原理,我們可以很容易地理解它們在多大程度上簡化了底層電網(wǎng)的技術方面。家用電器不必知道這樣的系統(tǒng)是如何工作的,只要它們能夠處理接口。
圖1.1 接口的常見示例:交流電源插頭插座
Web API 與引入萬維網(wǎng)的概念相同:可通過 Web 訪問的接口,該接口公開一個或多個插頭(端點),其他各方(客戶端)可以使用這些接口通過通用通信協(xié)議 (HTTP) 和標準(JSON/XML 格式)與電源(數(shù)據(jù))進行交互。
注意在本書中,術語Web API將互換使用,以表示接口和實際的Web應用程序。
圖 1.2 說明了典型面向服務的體系結構 (SOA) 環(huán)境中 Web API 的用途。SOA 是一種圍繞通過網(wǎng)絡進行通信的各種獨立服務的責任分離而構建的架構風格。
圖 1.2 Web API 在典型的基于 SOA 的環(huán)境中的角色
如我們所見,Web API 起著關鍵作用,因為它負責從底層數(shù)據(jù)庫管理系統(tǒng) (DBMS) 中檢索數(shù)據(jù)并使其可用于服務:
我們剛剛描述的是一個有趣的場景,可以幫助我們理解 Web API 在一個相當常見的基于服務的生態(tài)系統(tǒng)中的角色。但我們?nèi)栽谟懻撘环N非個人的理論方法。讓我們看看如何使相同的體系結構適應特定的現(xiàn)實場景。
在本節(jié)中,我們將圖 1.1 中描述的抽象概念實例化為具體、可信和現(xiàn)實的場景。每個人都知道什么是棋盤游戲,對吧?我們談論的是帶有骰子、紙牌、棋子和類似東西的桌面游戲。假設我們在棋盤游戲俱樂部的 IT 部門工作。俱樂部有一個棋盤游戲數(shù)據(jù)庫,其中包括游戲信息,例如姓名、出版年份、最小最大玩家、游戲時間、最低年齡、復雜性和機制,以及俱樂部成員、客人和其他玩家隨時間給出的一些排名統(tǒng)計數(shù)據(jù)(評級數(shù)量和平均評級)。我們還假設俱樂部希望使用此數(shù)據(jù)庫來提供一些基于 Web 的服務和應用程序,例如:
正如我們很容易猜到的那樣,滿足這些要求的一個好方法是實現(xiàn)一個專用的 Web API。這種方法允許我們向所有這些服務提供,而無需將數(shù)據(jù)庫服務器及其底層數(shù)據(jù)模型暴露給其中任何一個。圖 1.3 顯示了體系結構。
圖 1.3 由單個 MyBGList Web API 提供的多個與棋盤游戲相關的 Web 應用程序和服務
同樣,Web API 是組織者,獲取包含所有相關數(shù)據(jù)的數(shù)據(jù)源(MyBGList DBMS)并將其提供給其他服務:
此 Web API 是我們將在以下章節(jié)中使用的 API。
什么是 Jamstack?
Jamstack(JavaScript,API和Markup)是一種現(xiàn)代架構模式,基于將網(wǎng)站預呈現(xiàn)為靜態(tài)HTML頁面并使用JavaScript和Web API加載內(nèi)容。有關 Jamstack 方法的更多信息,請參閱 https://jamstack.org。有關使用 Jamstack 方法開發(fā)基于標準的靜態(tài)網(wǎng)站的綜合指南,請查看 Raymond Camden 和 Brian Rinaldi (https://www.manning.com/books/the-jamstack-book) 的 The Jamstack Book: Beyond Static Sites with JavaScript, API and Markup。
現(xiàn)在我們已經(jīng)了解了大致情況,我們可以花一些時間探索當今 Web API 開發(fā)人員可用的各種體系結構和消息傳遞協(xié)議。正如我們將能夠看到的,每種類型都有特點、優(yōu)缺點,使其適用于不同的應用程序和業(yè)務。
你在這本書中找不到的東西
出于篇幅的原因,我有意將本書的主題限制在HTTP Web API,跳過其他應用層協(xié)議,如高級消息隊列協(xié)議(AMQP)。如果您有興趣通過AMQP了解有關基于消息的應用程序的更多信息,我建議您閱讀Gavin M. Roy(https://www.manning.com/books/rabbitmq-in-depth)的RabbitMQ in Depth。
在查看各種體系結構之前,讓我們簡要總結每個 Web API 通常屬于的四個主要使用范圍:
為了履行其在各方之間實現(xiàn)數(shù)據(jù)交換的角色,Web API 需要定義一組清晰、明確的規(guī)則、約束和格式。本書介紹了四種最常用的Web API架構風格和消息協(xié)議:REST、RPC、SOAP 和 GraphQL。每種方法都有獨特的特征、權衡和支持的數(shù)據(jù)交換格式,使其可用于不同的目的。
休息
具象狀態(tài)傳輸 (REST) 是一種體系結構樣式,專為基于網(wǎng)絡的應用程序設計,這些應用程序使用標準 HTTP GET、POST、PATCH、PUT 和 DELETE 請求方法(最初在現(xiàn)已取代的 RFC 2616 https://www.w3.org/Protocols/rfc2616/rfc2616.xhtml 中定義)來訪問和操作數(shù)據(jù)。更具體地說,GET 用于讀取數(shù)據(jù),POST 用于創(chuàng)建新資源或執(zhí)行狀態(tài)更改,PATCH 用于更新現(xiàn)有資源,PUT 用于用新資源替換現(xiàn)有資源(如果不存在則創(chuàng)建它),DELETE 用于永久擦除資源。這種辦法不需要額外的公約來允許當事人進行通信,這使得它易于采用和快速實施。
然而,REST遠不止于此。它的體系結構范例依賴于六個指導約束,如果正確實現(xiàn),可以極大地使多個 Web API 屬性受益:
實現(xiàn)所有這些約束的 Web API 可以描述為 RESTful。
介紹 Roy Fielding,REST 無可爭議的父親
Roy Fielding描述了六個REST約束,他是HTTP規(guī)范的主要作者之一,被廣泛認為是REST之父,在他的論文Architectural Styles and the Design of Network-based Software Architectures中,他在2000年獲得了計算機科學博士學位。菲爾丁論文的原始文本可在 http://mng.bz/19ln 的加利福尼亞大學出版物檔案中找到。
廣泛接受的規(guī)則和指南,HTTP協(xié)議經(jīng)過驗證的可靠性以及實現(xiàn)和開發(fā)的整體簡單性使REST成為當今最流行的Web API架構。出于這個原因,我們將在本書中開發(fā)的大多數(shù) Web API 項目都使用 REST 架構風格,并遵循 RESTful 方法以及本節(jié)中介紹的六個約束。
SOAP
簡單對象訪問協(xié)議 (SOAP) 是一種消息傳遞協(xié)議規(guī)范,用于通過可擴展標記語言 (XML) 跨網(wǎng)絡交換結構化信息。大多數(shù) SOAP Web 服務都是通過使用 HTTP 進行消息協(xié)商和傳輸來實現(xiàn)的,但也可以使用其他應用層協(xié)議,例如簡單郵件傳輸協(xié)議 (SMTP)。
SOAP規(guī)范由Microsoft于1999年24月發(fā)布,但直到2003年3月1日才成為Web標準,當時它最終獲得了W2C推薦狀態(tài)(SOAP v3.12,https://www.w<>.org/TR/soap<>)。接受的規(guī)范定義了 SOAP 消息傳遞框架,該框架由以下內(nèi)容組成:
SOAP 的主要優(yōu)點是其可擴展性模型,它允許開發(fā)人員使用其他官方消息級標準(WS-Policy、WS-Security、WS-Federation 等)擴展基本協(xié)議,以執(zhí)行特定任務,以及創(chuàng)建自己的擴展。生成的 Web 服務可以用 Web 服務描述語言 (WSDL) 進行記錄,這是一種描述各種端點支持的操作和消息的 XML 文件。
但是,此方法也是一個缺陷,因為用于發(fā)出請求和接收響應的 XML 可能會變得極其復雜,并且難以閱讀、理解和維護。多年來,許多開發(fā)框架和 IDE 都緩解了此問題,包括 ASP.NET 和 Visual Studio,它們開始提供快捷方式和抽象來簡化開發(fā)體驗并自動生成所需的 XML 代碼。即便如此,與REST相比,該協(xié)議也有幾個缺點:
由于所有這些原因,今天在REST與SOAP的辯論中,普遍的共識是,除非有特定的原因使用SOAP,否則REST Web API是首選的方式。這些原因可能包括硬件、軟件或基礎結構的限制,以及現(xiàn)有實現(xiàn)的要求,這就是為什么我們不會在本書中使用 SOAP 的原因。
GraphQL
2015年,隨著GraphQL的公開發(fā)布,REST的至高無上地位受到質(zhì)疑,GraphQL是Facebook開發(fā)的API的開源數(shù)據(jù)查詢和操作語言。這兩種方法之間的主要區(qū)別不在于體系結構樣式,而在于它們發(fā)送和檢索數(shù)據(jù)的不同方式。
事實上,GraphQL 遵循大多數(shù) RESTful 約束,依賴于相同的應用層協(xié)議 (HTTP),并采用相同的數(shù)據(jù)格式 (JSON)。但是,它不是使用不同的端點來獲取不同的數(shù)據(jù)對象,而是允許客戶端執(zhí)行動態(tài)查詢并詢問單個端點的具體數(shù)據(jù)要求。
我將嘗試使用從棋盤游戲 Web API 場景中獲取的實際示例來解釋此概念。假設我們要檢索對 Citadels 棋盤游戲給予積極反饋的所有用戶的名稱和唯一 ID。當我們處理典型的 REST API 時,完成這樣的任務需要以下操作:
完整的請求/響應周期如圖1.4所示。
圖 1.4 REST 中的 HTTP 請求-響應周期
該計劃是可行的,但不可否認的是,它涉及大量工作。具體來說,我們必須執(zhí)行多次往返(三個HTTP請求)和大量的過度獲取 - Citadels游戲信息,所有反饋的數(shù)據(jù)以及所有給予正面評價的用戶的數(shù)據(jù) - 以獲得一些名字。唯一的解決方法是實現(xiàn)額外的 API 端點以返回我們需要的內(nèi)容或簡化一些中間工作。我們可以添加一個端點來獲取給定棋盤游戲 ID 甚至給定棋盤游戲名稱的正面反饋,包括基礎實現(xiàn)中的全文查詢。如果我們愿意,我們甚至可以實現(xiàn)一個專用的端點,例如 api/positiveFeedbacksByName,來執(zhí)行整個任務。
然而,不可否認的是,這種方法會影響后端開發(fā)時間,并增加我們 API 的復雜性,并且不夠通用,無法在類似的、不相同的情況下提供幫助。如果我們想檢索負面反饋或多個游戲或給定作者創(chuàng)建的所有游戲的正面反饋怎么辦?正如我們很容易理解的那樣,克服這些問題可能并不簡單,特別是當我們在客戶端數(shù)據(jù)獲取要求方面需要高水平的多功能性時。
現(xiàn)在讓我們看看 GraphQL 會發(fā)生什么。當我們采用這種方法時,我們不是從現(xiàn)有(或附加)端點的角度來考慮,而是專注于我們需要發(fā)送到服務器的查詢以請求我們需要的內(nèi)容,就像我們對 DBMS 所做的那樣。完成后,我們將該查詢發(fā)送到(單個)GraphQL 端點,并在單個 HTTP 調(diào)用中精確地返回我們請求的數(shù)據(jù),而無需獲取任何我們不需要的字段。圖 1.5 顯示了 GraphQL 客戶端到服務器的往返行程。
圖 1.5 GraphQL 中的 HTTP 請求-響應周期
正如我們所看到的,性能優(yōu)化不僅限于 Web API。GraphQL 方法不需要客戶端迭代,因為服務器已經(jīng)返回了我們正在尋找的精確結果。
GraphQL 規(guī)范可在 https://spec.graphql.org 上找到。我將在第 10 章中廣泛討論 GraphQL,向您展示如何將其與 REST 一起使用以實現(xiàn)特定的面向查詢的目標。
現(xiàn)在我們已經(jīng)了解了什么是 Web API,以及如何使用它們在網(wǎng)絡中的 Web 應用程序和服務中交換數(shù)據(jù),現(xiàn)在是時候介紹我們將在本書中創(chuàng)建它們的框架了。該框架 ASP.NET Core,這是一個高性能,跨平臺,開源Web開發(fā)框架,由Microsoft于2016年推出,作為 ASP.NET 的繼任者。
在以下各節(jié)中,我將簡要介紹其最獨特的方面:整體體系結構、請求/響應管道管理、異步編程模式、路由系統(tǒng)等。
注意在本書中,我們將使用 .NET 6.0 和 ASP.NET Core Runtime 6.0.11,這是本文撰寫時最新的正式發(fā)布版本,也是繼 .NET Core 1.0、1.1、2.0、2.1、2.2、3.0、3.1 和 .NET 5.0 之后發(fā)布的第九部分。每個版本都引入了一些更新、改進和其他功能,但為了簡單起見,我將回顧最新版本附帶的結果特征。此外,從 .NET 5 開始,所有偶數(shù) .NET 版本(包括 .NET 6)都授予長期支持 (LTS) 狀態(tài),這意味著它們將在未來許多年內(nèi)得到支持,而不是具有較短時間范圍的奇數(shù)版本。目前,偶數(shù)版本的支持期為三年,奇數(shù)版本的支持期為 18 個月 (http://mng.bz/JVpa)。
我選擇將 ASP.NET Core 用于我們的 Web API 項目,因為新的 Microsoft 框架強制執(zhí)行了幾個現(xiàn)代架構原則和最佳實踐,使我們能夠構建輕量級、高度模塊化的應用程序,這些應用程序具有高水平的可測試性和源代碼可維護性。這種方法自然會指導開發(fā)人員構建(或采用)由離散和可重用組件組成的應用程序,這些組件執(zhí)行特定任務并通過框架提供的一系列接口進行通信。這些組件稱為服務和中間件,它們在 Web 應用程序的 Program.cs 文件中注冊和配置,該文件是應用的入口點。
注意如果您來自較舊的 ASP.NET Core 版本(如 3.1 或 5.0),您可能想知道 Startup 類(及其相應的 Startup.cs 文件)發(fā)生了什么變化,該文件用于包含服務和中間件配置設置。此類已從 .NET 6 引入的最小宿主模型中刪除,該模型將程序類和啟動類合并到單個 Program.cs 文件中。
服務業(yè)
服務是應用程序提供其功能所需的組件。我們可以將它們視為應用依賴項,因為我們的應用依賴于它們的可用性才能按預期工作。我在這里使用術語依賴關系是有原因的:ASP.NET Core 支持依賴注入 (DI) 軟件設計模式,這是一種架構技術,允許我們在類與其依賴關系之間實現(xiàn)控制反轉(zhuǎn)。以下是整個服務實現(xiàn)、注冊/配置和注入過程在 ASP.NET Core 中的工作方式:
框架提供的服務接口的典型示例包括可用于實現(xiàn)基于策略的權限的 IAuthorizationService,以及可用于發(fā)送電子郵件的 IEmailService。
中間件
中間件是一組在 HTTP 級別運行的組件,可用于處理整個 HTTP 請求處理管道。如果您還記得 ASP.NET 在 ASP.NET Core 出現(xiàn)之前使用的 HttpModules 和 HttpHandler,您可以很容易地看到中間件如何扮演類似的角色并執(zhí)行相同的任務。ASP.NET 核心 Web 應用程序中使用的中間件的典型示例包括 HttpsRedirectionMiddleware,它將非 HTTPS 請求重定向到 HTTPS URL,以及 AuthorizationMiddleware,它在內(nèi)部使用授權服務來處理 HTTP 級別的所有授權任務。
每種類型的中間件都可以將 HTTP 請求傳遞給管道中的下一個組件或提供 HTTP 響應,從而縮短管道本身并阻止其他中間件處理請求。這種類型的請求阻止中間件稱為終端中間件,通常負責處理各種應用端點的主要業(yè)務邏輯任務。
警告值得注意的是,中間件是按注冊順序(先進先出)處理的。始終在程序.cs文件中的非終端中間件之后添加終端中間件;否則,該文件將不起作用。
終端中間件的一個很好的例子是 StaticFileMiddleware,它最終處理指向靜態(tài)文件的端點 URL,只要它們是可訪問的。發(fā)生這種情況時,它會使用適當?shù)?HTTP 響應將請求的文件發(fā)送給調(diào)用方,從而終止請求管道。我之前簡要提到的 HttpsRedirectionMiddleware 也是終端中間件,因為它最終會響應所有非 HTTPS 請求的 HTTP-to-HTTPS 重定向。
注意StaticFileMiddleware 和 HttpsRedirectionMiddleware 只有在滿足某些情況時才會終止 HTTP 請求。如果請求的終結點與其激活規(guī)則不匹配(例如,對于前者,指向不存在的靜態(tài)文件的 URL,或者對于后者,指向已在 HTTPS 中),則會將其傳遞給管道中存在的下一個中間件,而無需執(zhí)行任何操作。出于這個原因,我們可以說它們可能是終端中間件,以區(qū)別于總是在 HTTP 請求到達請求管道時結束請求管道的中間件。
現(xiàn)在我已經(jīng)介紹了服務和中間件,我們終于可以看一下在應用程序開始時執(zhí)行的 Program.cs 文件:
var builder=WebApplication.CreateBuilder(args); ?
// Add services to the container.
builder.Services.AddControllers(); ?
// Learn more about configuring Swagger/OpenAPI
// at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); ?
builder.Services.AddSwaggerGen(); ?
var app=builder.Build(); ?
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger(); ?
app.UseSwaggerUI(); ?
}
app.UseHttpsRedirection(); ?
app.UseAuthorization(); ?
app.MapControllers(); ?
app.Run(); ?
? 創(chuàng)建 WebApplicationBuilder 工廠類
? 注冊和配置服務
? 構建 Web 應用程序?qū)ο?/p>
? 注冊和配置非終端和潛在的終端中間件
? 注冊和配置終端中間件
通過分析這段代碼,我們可以很容易地看到該文件負責以下初始化任務:
更準確地說,Web 應用程序由 WebApplicationBuilder 工廠類實例化,從而創(chuàng)建一個 WebApplication 對象。此實例存儲在應用局部變量中,用于注冊和配置所需的服務和中間件。
注意服務和中間件都通過專用的擴展方法注冊和配置,這是一種簡化設置過程并保持程序.cs文件盡可能簡潔的便捷方法。在 ASP.NET 核心命名約定下,服務主要通過使用帶有 Add 前綴的擴展方法進行配置;中間件前綴為“使用”、“映射”和“運行”。至少目前,我們關心的唯一區(qū)別是 Run 委托始終是 100% 終端和最后一個要處理的。稍后,我們將在試驗最小 API 時詳細討論這些約定及其含義。
為了簡單起見,我將中間件分為兩類:潛在終端和終端。在這一點上,區(qū)別應該很明顯。潛在的終端中間件只有在與某些規(guī)則匹配時才結束HTTP請求管道,而終端中間件總是這樣做(所以難怪它是最后一個)。
在 ASP.NET Core 中,控制器是一個類,用于對一組處理類似 HTTP 請求的操作方法(也稱為操作)進行分組。從這個角度來看,我們可以說控制器是可用于聚合具有共同點的操作方法的容器:路由規(guī)則和前綴、服務、實例、授權要求、緩存策略、HTTP 級過濾器等。這些常見要求可以直接在控制器類上定義,從而避免了為每個操作指定這些要求的需要。此方法由一些強大的內(nèi)置命名和編碼約定強制執(zhí)行,有助于保持代碼干燥并簡化應用的體系結構。
注意 DRY 是 Don't Repeat Yourself 的首字母縮寫詞,這是一個眾所周知的軟件開發(fā)原則,可幫助我們記住避免冗余、模式重復以及可能導致代碼異味的任何其他內(nèi)容。它與 WET 相反(將所有內(nèi)容寫入兩次或每次寫入)。
從 ASP.NET Core 開始,控制器可以從兩個內(nèi)置基類繼承:
正如我們很容易理解的那樣,控制器基類旨在用于采用模型-視圖-控制器 (MVC) 模式的 Web 應用程序,其中它們旨在返回來自業(yè)務邏輯(通常由依賴注入的服務處理)的數(shù)據(jù)。在典型的 ASP.NET Core Web 應用程序中,生成的數(shù)據(jù)通過視圖返回到客戶端,這些視圖使用客戶端標記和腳本語言(如 HTML、CSS、JavaScript 等)處理應用程序的數(shù)據(jù)表示層(或服務器端呈現(xiàn)的語法,如 Razor)。在處理 Web API 時,我們通常不使用視圖,因為我們希望直接從控制器返回 JSON 或 XML 數(shù)據(jù)(無 HTML)。在這種情況下,從 ControllerBase 基類繼承是在特定情況下的推薦方法。下面是處理兩種類型的 HTTP 請求的示例 Web API 控制器:
[ApiController] ?
[Route("api/[controller]")] ?
public class SampleController : ControllerBase
{
public SampleController()
{
}
[HttpGet] ?
public string Get()
{
return "TODO: return all items";
}
[HttpGet("{id}")] ?
public string Get(int id)
{
return $"TODO: return the item with id #{id}";
}
[HttpDelete("{id}")] ?
public string Delete(int id)
{
return $"TODO: delete the item with id #{id}";
}
}
? 添加特定于 API 的行為
? 默認路由規(guī)則
? 處理 HTTP GET 到 /api/Sample/ 的操作
? 處理 HTTP GET 到 /api/Sample/{id} 的操作
? 處理 HTTP DELETE 到 /api/Sample/{id} 的操作
正如我們通過查看此代碼所看到的,通過利用一些有用的內(nèi)置約定,我們能夠用幾行代碼完成給定的任務:
注意此外,由于我們使用了 [ApiController] 屬性,因此我們還需要一些特定于 Web API 的其他約定,這些約定會自動返回某些 HTTP 狀態(tài)代碼,具體取決于操作類型和結果。我們將在第6章中詳細討論它們,屆時我們將深入研究錯誤處理。
具有內(nèi)置約定的控制器是使用幾行代碼實現(xiàn) Web API 業(yè)務邏輯的好方法。然而,在 ASP.NET Core 6中,該框架引入了一個最小的范式,允許我們以更少的儀式來構建Web API。這個特性被稱為最小API,盡管它很年輕,但它得到了新的和經(jīng)驗豐富的 ASP.NET 開發(fā)人員的大量關注,因為它通常允許更小,更易讀的代碼庫。
解釋最小 API 的最好方法是在基于控制器的方法和塊上的新孩子之間執(zhí)行快速代碼比較。以下是我們使用 SampleController 處理的相同 HTTP 請求如何由最小 API 處理,并對程序.cs文件進行一些小的更新:
// app.MapControllers(); ?
app.MapGet("/api/Sample", ?
()=> "TODO: return all items"); ?
app.MapGet("/api/Sample/{id}", ?
(int id)=> $"TODO: return the item with id #{id}"); ?
app.MapDelete("/api/Sample/{id}", ?
(int id)=> $"TODO: delete the item with id #{id}"); ?
? 刪除此行(和 SampleController 類)
? 改為添加這些行
如我們所見,所有實現(xiàn)都傳輸?shù)?Program.cs 文件中,而無需單獨的控制器類。此外,代碼可以進一步簡化(或DRYfied),例如通過添加前綴變量,無需多次重復“/api/Sample”。在代碼可讀性、簡單性和減少開銷方面,其優(yōu)勢顯而易見。
提示任何最小 API 方法的業(yè)務邏輯都可以放在程序.cs文件之外;只需要 Map 方法才能到達那里。事實上,將實際實現(xiàn)移動到其他類幾乎總是很好的做法,除非我們處理的是單行代碼。
最小 API 范例不太可能取代控制器,但它肯定會簡化大多數(shù)小型 Web API 項目(如微服務)的編碼體驗,并吸引大多數(shù)尋求時尚方法和淺學習曲線的開發(fā)人員。出于所有這些原因,我們將在本書中經(jīng)常將其與控制器一起使用。
影響 Web 應用程序的大多數(shù)性能問題都是由于有限數(shù)量的線程需要處理可能無限量的并發(fā) HTTP 請求。在響應這些請求之前,這些線程必須執(zhí)行一些資源密集型(通常是不可緩存的)任務,在最終收到結果之前被阻止時,尤其如此。典型的示例包括通過 SQL 查詢從 DBMS 讀取或?qū)懭霐?shù)據(jù)、訪問第三方服務(如外部網(wǎng)站或 Web API)以及執(zhí)行任何其他需要大量執(zhí)行時間的任務。
當 Web 應用程序被迫同時處理大量這些請求時,可用線程的數(shù)量會迅速減少,從而導致響應時間變慢,并最終導致服務不可用(HTTP 503 等)。
注意這種情況是大多數(shù)基于 HTTP 的拒絕服務 (DoS) 攻擊的主要目標,在這種攻擊中,Web 應用程序被請求淹沒,使整個服務器不可用。線程阻塞、非緩存的 HTTP 請求是 DoS 攻擊者的金礦,因為他們很有可能迅速耗盡線程池。
處理資源密集型任務的一般經(jīng)驗法則是積極緩存生成的 HTTP 響應,如 REST 約束 3(可緩存性)所述。然而,在某些情況下,緩存不是一種選擇,例如當我們需要寫入DBMS(HTTP POST)或讀取可變或高度可定制的數(shù)據(jù)(帶有大量參數(shù)的HTTP GET)時。在這種情況下,我們該怎么辦?
ASP.NET Core 框架允許開發(fā)人員通過實現(xiàn) C# 語言級異步模型(通常稱為基于任務的異步模式 (TAP))來有效地處理此問題。了解TAP如何工作的最好方法是看到它的實際效果。請看一下以下源代碼:
[HttpGet]
public async Task<string> Get() ?
{
return await Task.Run(()=> { ?
return "TODO: return all items";
});
}
? 異步
? 等待
如我們所見,我們對之前用于實現(xiàn) TAP 的 SampleController 的第一個操作方法應用了一些小的更新。新模式依賴于使用 async 和 await 關鍵字以非阻塞方式處理任務:
值得注意的是,因為我們在 Get() 操作方法中使用了 await 關鍵字,所以我們也必須將該方法標記為異步。這完全沒問題;這意味著該方法將由調(diào)用線程等待而不是阻塞它,這是我們想要的。
我們的最小實現(xiàn)在性能方面沒有任何好處。我們絕對不需要等待像字面 TODO 字符串這樣微不足道的事情。但是它應該讓我們了解當示例 Task.Run 被需要服務器執(zhí)行一些實際工作(例如從 DBMS 檢索實際數(shù)據(jù))的內(nèi)容所取代時,異步/等待模式將為 Web 應用程序帶來的好處。我將在第 4 章中廣泛討論異步數(shù)據(jù)檢索任務,其中介紹了 Microsoft 最流行的 .NET 數(shù)據(jù)訪問技術:實體框架核心。
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。