準備工作:在此之前你需要了解關於.NET .Core的基礎,前面幾篇文章已經介紹:https://www.cnblogs.com/hcyesdo/p/12834345.html
首先需要明確一點的就是REST Api它不是一個標準,而是一種架構風格
什麼是WebApi?
WebApi通常是指“使用HTTP協議並通過網路呼叫的API”,由於它使用了HTTP協議,所以需要通過URI資訊來指定端點。
WebApi就是一個Web系統,通過訪問URI可以與其進行資訊互動。
而常用的MVC模式是主要用來構建UI的架構模式。
特點:鬆耦合,關注點分離、MVC不是一個完整的應用程式框架
MVC對映為API呢?
Model:它賦值處理程式資料的邏輯
View:它是程式裡複製展示資料的那部分。構建API的時候,VView就是資料或資源的展示。通常使用JSON格式。
Controller,它復負責View和Model之間的互動。
需要注意的是,在配置服務的時候在core3.0以前可能寫的是AddMvc,但是這個服務涉及了View檢視以及TagHelper的一些功能,所以在做WebApi的時候用不到
public void ConfigureServices(IServiceCollection services) { //services.AddMvc(); core 3.0以前是這樣寫的,這個服務包括了 TageHelper等 WebApi不需要的東西,所有3.0以後可以不這樣寫 services.AddControllers(); }
注意配置中介軟體的區域管道順序不能隨意改動。
管道就是客戶端通過一些指令指向伺服器端,在這個過程中呢,會經過一些手動配置的中介軟體,比如說路由中介軟體、靜態資源中介軟體等,從客戶端出發到伺服器端,將資料處理後,再由伺服器端原路返回到客戶端這樣的一個過程。但是在請求的過程中也不排除中介軟體出現短路的情況,這樣也就不會進入到第二個中介軟體了,而是直接返回到客戶端。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
API對外合約:API消費者需要使用到三個概念
- 資源的標識(URI)
- HTTP方法(GET、POST)
- 有效載荷
API對外提供統一資源介面,業界對RESTful資源命名也有規則
關於RESTful API約束
使用名詞而不是動詞
需求:“我想獲得系統裡的所有使用者”
常見錯誤:api/getusers
分析:這裡的“獲取”就是一個動詞,而我們的目的應該是“使用者”,即使用者是一個名詞
正確做法:GET api/user
要體現資源的結構/關係
通過id獲取單個使用者應該是:api/user/{userId},而不是 api/user/users。這樣寫就是讓API具有很好的可讀性和可預測性
需求案例1:
系統存在兩個資源:Company(公司)、Employee(員工),現在需要獲取某個公司下的所有員工
分析:應該使用HTTP GET。API在設計的時候需要體現公司與員工的一個包含關係
常見錯誤做法:api/employees,api/employee/{companyId} 。這兩個URI都沒有體現公司和員工的一個包含關係
建議做法:api/companies/{companyId}/employees
需求案例2:
需要獲取某個公司下的某個員工
常見錯誤做法:api/employees/{employeeId}
建議做法:api/companies/{companyId}/employees/{employeeId}
自定義查詢怎麼命名?
需求:獲取所有使用者資訊,並且按年齡從大到小排序
常見錯誤做法:api/user/orderby/age
建議做法:api/user?orderby=age (通過QueryString查詢字串,多條件使用 & 符號)
HTTP狀態碼
請求是否成功?如果請求失敗了,誰來為此負責
2xx 開頭狀態碼
200 - OK,表示請求成功
201 - Created,表示請求成功並建立了資源
204 - No Content,請求成功,但是不應該返回任何物件,例如刪除操作
3xx 開頭狀態碼
用於跳轉。例如告訴瀏覽器搜尋引擎,某個頁面的網址已經永久改變,絕大多數的WebApi都不需要這類的狀態碼
4xx 開頭:客戶端錯誤
400 - Bad Request,表示API消費者傳送到伺服器的請求是有錯誤的
401 - Unauthorized,表示沒有提供授權資訊或者提供的授權資訊有誤
403 - Forbidden,表示身份認證已經通過,但是已認證的使用者卻無法訪問請求的資源
404 - NotFound,表示請求的資源不存在
405 - Method not allowed,當嘗試傳送請求到資源的時候,使用了不被支援的HTTP方法
406 - Not acceptable,表示API消費者請求的表述格式並不被WebApi所支援,並且API不會提供預設的表述格式
5xx 開頭狀態碼
500 - Internal serever error,表示伺服器出現了錯誤,客戶端無能為力,只能以後再試試
還有就是RESTful API 返回的結果不一定Json格式的
關於如何標註路由屬性 uri ?
先看控制器程式碼:
using Microsoft.AspNetCore.Mvc; using Routine.Api.Service; using System; using System.Threading.Tasks; namespace Routine.Api.Controllers { [ApiController] //好處:ApiController不是強制的 //1.會啟用使用屬性路由(Attribute Routing) //2.自動HTTP 400響應 //3.推斷引數的繫結源 //4.Multipart/form-data 請求推斷 //5.錯誤狀態程式碼的問題詳細資訊 [Route("api/companies")] //寫法一 //[Route("api/[controller]")] //寫法二:意思是相當於刨除了Controller字尾,獲取前面的 Companies C可以是小寫,如果你改名了那麼你路由的uri也跟著變了(不建議這樣寫) public class CompaniesController:ControllerBase { private readonly ICompanyRepository _companyRepository; public CompaniesController(ICompanyRepository companyRepository) { _companyRepository = companyRepository ?? throw new ArgumentNullException(nameof(companyRepository)); } [HttpGet] //IActionResult定義了一些合約,它可以代表ActionResult返回的結果 public async Task<IActionResult> GetCompanies() { var companies =await _companyRepository.GetCompaniesAsync();//讀取出來的是List return Ok(companies); } [HttpGet("{companyId}")] // Controller標註了ApiController => uri=> api/companies/{companyId} public async Task<IActionResult> GetCompany(Guid companyId) { //判斷該公司是否存在方法一:這種方法在處理併發請求時可能會出現錯誤,原因是查到之後,進行刪除,進入company後也可能是404找不到了 //var exists =await _companyRepository.CompanyExistsAsync(compamyId); //if (!exists) //{ // //不存在應該返回404 // return NotFound(); //} var company = await _companyRepository.GetCompanyAsync(companyId);//讀取出來的是List //方法二 if (company==null) { return NotFound(); } return Ok(company); } } }
為了更好的構建RESTful API 對於 uri 的設計規則也有很嚴格的要求。
在控制器標註 ApiController,它會自動啟用路由屬性
通過 [Route] 設計路由規則
比如:介面一:GetCompanies,請求的方式:GET,通過Route 去設定路由規則 [Router("api/companies")],即查詢所有公司資訊
[Router("api/companies")] => api/companies
介面二:GetCompany,請求方式:GET,只不過在 新增了 [HTTPGET("{companyId}")] =>api/companies/{companyId},即查詢某一公司的資訊
關於第二種路由寫法請看註釋
通過Postman工具測試一下
測試一:介面一
測試二:介面二
以上兩個介面測試完畢!!!
對於ASP.NET Core 3.x以前對於 404 NotFound請求狀態碼輸出的格式不太友好,而ASP.NET Core 3.x對於404請求狀態碼也做了友好的提示。
現在將介面偽造錯誤資訊,提示 404 如圖:
關於構建 RESTful API 存在的內容協商:
所謂內容協商就是這樣一個過程,針對一個響應,當有多種表述格式可用時,選取最佳的一種表述格式,這些表述可以是XML,JSON,甚至是自定義的格式規則
Accept Header:負責指定輸出型別
Media Type(媒體型別)
- application/json
- application/xml
404 Not Acceptable
輸出格式:ASP.NET Core 裡面對應的就是 Output Formatters 我們稱為:輸出型別的格式化器
也就是說如果一個API消費者,設定了Accept Header的媒體型別為Json,那麼這個RESTful API也應該返回的是JSON,
但是呢如果伺服器只接收XML的格式,這個時候請求的媒體型別不被伺服器所接受,那麼就會返回 406 這個狀態碼
總而言之,儘量避免不寫Accept Header,避免客戶端和伺服器端接收和返回的型別不一致導致錯誤。
有輸出那麼就會有輸入了!!!
Content-Type-Header:負責指定輸入
Media Type(媒體型別)
- application/json
- application/xml
輸出格式:ASP.NET Core裡面對應的就是 Input Formatters
比如說:對於一個客戶端的POST請求,即新增資源資訊,那麼就需要輸入引數,這些引數可能是放在Body裡面,那麼在Body裡面的這些引數可能是物件的那種格式。那麼我們就需要通過 Content-Type-Header來確定Body裡面的引數是什麼樣的型別,可能是Json也可能是Xml或者是自定義的格式,指明之後,RESTful API才能更好的對這些引數進行處理。
看Startup類程式碼:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Routine.Api.Data; using Routine.Api.Service; namespace Routine.Api { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { //services.AddMvc(); core 3.0以前是這樣寫的,這個服務包括了TageHelper等 WebApi不需要的東西,所有3.0以後可以不這樣寫 services.AddControllers(setup => { //setup.ReturnHttpNotAcceptable=false;//如果客戶端預設為xml格式,伺服器端為json,false就不會返回406 setup.ReturnHttpNotAcceptable = true;//如果請求的型別和伺服器請求的型別不一致就返回406 //setup.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); //setup.OutputFormatters.Insert(0, new XmlDataContractSerializerOutputFormatter()); }).AddXmlDataContractSerializerFormatters(); //配置介面服務:涉及到這個服務註冊的生命週期這裡採用AddScoped,表示每次的Http請求 services.AddScoped<ICompanyRepository, CompanyRepository>(); //獲取配置檔案中的資料庫字串連線 var sqlConnection = Configuration.GetConnectionString("SqlServerConnection"); //配置上下文類DbContext,因為它本身也是一套服務 services.AddDbContext<RoutineDbContext>(options => { options.UseSqlServer(sqlConnection); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } }
setup.ReturnHttpNotAcceptable就是處理是在客戶端與伺服器端資料產生衝突時,是否要即將產生 406 的狀態碼。
- true:產生
- false:不產生
setup.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter())
分析:實際上OutputFormatters 是一個集合 ,通過Add方法新增伺服器允許接受XML格式的資料功能。因為集合中預設只有Json
setup.OutputFormatters.Insert(0,new XmlDataContractSerializerOutputFormatter())
分析:實際上剛剛寫的是一種方法。Insert就是指明格式順序,預設是JSON,通過Insert設定 0 ,就是指明XML為預設接受的資料格式
實際上以上兩種寫法都是 ASP.NET Core 3.x以前的寫法。
ASP.NET Core 3.x的實際寫法:就是在AddControllers後面新增XmlDataContractSerializerOutputFormatter方法。這樣不管是輸入輸出都已經設定好了XML的格式資料
postman介面測試:取消setup.OutputFormatters.Insert(0,new XmlDataContractSerializerOutputFormatter())的註釋
預設xml:
最後,關於構建RESTFUL Api的URI規則及原理
動作 | HTTP方法 | 請求的引數(Payload) | 引數位置 | URI | 請求前 | 請求後 | 響應內容 |
查詢 | GET | 查詢引數 | 可含查詢字串(QueryString) |
/api/companies/{companyId} /api/companies |
無修改 |
單個資源 多個資源的集合 |
|
建立/新增 | POST | 要建立的單個資源資訊 | Body | /api/companies | 舊 | 新 | 新建立的單個資源 |
區域性修改/更新 | PATCH |
待修改的資源 JsonPatchDocument |
Body | /api/companies/{companyId} |
a:1 b:2 |
a:1 b:3 |
無須返回 |
替換 | PUT | 要替換的單個資源資訊 | Body | /api/companies/{companyId} |
a:1 b:2 |
a:2 b:3 |
無須返回 |
使用預定義的表示進行建立 | PUT | 要建立的單個資源資訊 | Body | /api/companies/{companyId} | 舊 | 新 | 返回新建立的資源 |
移除/刪除 | DELETE | 無 | 可含查詢字串(QueryString) | /api/companies/{companyId} |
a b |
a | 無須返回 |
.NET Core 3.x構建RESTful API 待續!!!