一、前言 在上一篇的文章中,主要是搭建了我們的開發環境,同時創建了我們的項目模板框架。在整個前後端分離的項目中,後端的 API 介面至關重要,它是前端與後端之間進行溝通的媒介,如何構建一個 “好用” 的 API 介面,是需要我們後端人員好好思考的。 在系統迭代的整個過程中,不可避免的會添加新的資源, ...
一、前言
在上一篇的文章中,主要是搭建了我們的開發環境,同時創建了我們的項目模板框架。在整個前後端分離的項目中,後端的 API 介面至關重要,它是前端與後端之間進行溝通的媒介,如何構建一個 “好用” 的 API 介面,是需要我們後端人員好好思考的。
在系統迭代的整個過程中,不可避免的會添加新的資源,或是修改現有的資源,後端介面作為暴露給外界的服務,變動的越小,對服務的使用方造成的印象就越小,因此,如何對我們的 API 介面進行合適的版本控制,我們勢必需要首先考慮。
系列目錄地址:ASP.NET Core 項目實戰
倉儲地址:https://github.com/Lanesra712/Grapefruit.VuCore
二、Step by Step
項目總是在不斷迭代的,某些時候,因為業務發展的需要,需要將現有的介面進行升級,而原有的介面卻不能立刻停止使用。比如說,你開發了一個介面提供給愛啪啪 1.0 版本使用,後來愛啪啪的版本迭代了,需要介面返回的數據與原先 1.0 版本返回的數據不同了,這時候,介面肯定是需要升級的,可是如果直接升級原有的介面,還在使用 1.0 版本的用戶不就 GG 了,因此,如何做到既可以讓 1.0 版本的用戶使用,也可以讓 2.0 版本的用戶使用就需要好好考慮了,常見的解決方案,主要有以下幾種。
a)使用不同的 API 名稱
最簡單粗暴,需要變更介面邏輯時就重新起個 API 名稱,新的版本調用新的 API 名稱,舊的版本調用舊的 API 名稱。
https://yuiter.com/api/Secret/Login ##愛啪啪 1.0
https://yuiter.com/api/Secret/NewLogin ##愛啪啪 2.0
b)在 Url 中標明版本號
直接將 API 版本信息添加到請求的 Url 中,調用不同版本的 API ,就在 URL 中直接標明使用的是哪個版本。
https://yuiter.com/api/v1/Secret/Login ##愛啪啪 1.0
https://yuiter.com/api/v2/Secret/Login ##愛啪啪 2.0
c)請求參數中添加版本信息
將 API 的版本信息作為請求的一個參數傳遞,通過指定參數值來確定請求的 API 版本。
https://yuiter.com/api/Secret/Login?version=1 ##愛啪啪 1.0
https://yuiter.com/api/Secret/Login?version=2 ##愛啪啪 2.0
d)在 header 中標明版本號
前端在請求 API 介面時,在 header 中添加一個參數用來表明請求的版本信息,後端通過前端在 header 中設置的參數來判斷,從而執行不同的業務邏輯分支。
POST https://yuiter.com/api/Secret/Login
Host: yuiter.com
api-version: v1 ##愛啪啪 1.0
POST https://yuiter.com/api/Secret/Login
Host: yuiter.com
api-version: v2 ##愛啪啪 2.0
在 Grapefruit.VuCore 這個項目中,我選擇將 API 的版本信息添加到請求的地址中,從而明確的指出當前請求的介面版本信息。
1、Swagger 集成
後端完成了介面之後,肯定需要告訴前端,不管是整理成 txt/excel/markdown 文檔,亦或是寫完一個介面就直接發微信告訴前端,總是要多做一步的事情,而 Swagger 則可以幫我們省去這一步。通過配置之後,Swagger 就可以根據我們的介面自動生成 API 的介面文檔,省時,省力。當然,如果前端小姐姐單身可撩,而你碰巧有意的話,另談。
Swagger 是一個可以將介面文檔自動生成,同時可以對介面功能進行測試的開源框架,在 ASP.NET Core 環境下,主流的有 Swashbuckle.AspNetCore 和 NSwag 這兩個開源框架幫助我們生成 Swagger documents。這裡,我採用的是 Swashbuckle.AspNetCore。
在使用 Swashbuckle.AspNetCore 之前,首先我們需要在 API(Grapefruit.WebApi) 項目中添加對於 Swashbuckle.AspNetCore 的引用。你可以直接右鍵選中 API 項目選擇管理 Nuget 程式包進行載入引用,也可以通過程式包管理控制台進行添加引用,這裡註意,使用程式包管理控制台時,你需要將預設的項目修改成 API(Grapefruit.WebApi) 項目。當引用添加完成後,我們就可以在項目中配置 Swagger 了。
Install-Package Swashbuckle.AspNetCore
ASP.NET Core 的本質上可以看成是一個控制台程式,在我們創建好的 ASP.NET Core Web API 項目中,存在著兩個類文件:Program.cs 以及 Startup.cs。與控制台應用一樣,Program 類中的 Main 方法是整個程式的入口,在這個方法中,我們將配置好的 IWebHostBuilder 對象,構建成 IWebHost 對象,並運行該 IWebHost 對象從而達到運行 Web 項目的作用。
在框架生成的 Program 類文件中,在配置 IWebHostBuilder 的過程時,框架預設為我們添加了一些服務,當然,這裡你可以註釋掉預設的寫法,去自己創建一個 WebHostBuilder 對象。同時,對於一個 ASP.NET Core 程式來說,Startup 類是必須的(你可以刪除生成的 Startup 類,重新創建一個新的類,但是,這個新創建的類必須包含 Configure 方法,之後只需要在 UseStartup<Startup> 中將該類配置為 Startup 類即可),這裡如果不指定 Startup 類會導致啟動失敗。
在 Startup 類中,存在著 ConfigureServices 和 Configure 這兩個方法,在 ConfigureServices 方法中,我們將自定義服務通過依賴註入的方式添加到 IServiceCollection 容器中,而這些容器中的服務,最終都可以在 Configure 方法中進行使用;而 Configure 方法則用於指定 ASP.NET Core 應用程式將如何響應每一個 HTTP 請求,我們可以在這裡將我們自己創建的中間件(Middleware)綁定到 IApplicationBuilder 上,從而添加到 HTTP 請求管道中。
這裡只是很粗略的說明瞭 ASP.NET Core 項目的啟動過程,想要仔細瞭解啟動過程的推薦園子里的這篇文章 =》ASP.NET Core 2.0 : 七.一張圖看透啟動背後的秘密,因為 ASP.NET Core 2.1 版本相比於 2.0 版本又有些改變,這裡有一些不一樣的地方需要你去註意。
當我們簡單瞭解了啟動過程後,就可以配置我們的 Swagger 了。Swashbuckle.AspNetCore 幫我們構建好了使用 Swagger 的中間件,我們只需要直接使用即可。
首先我們需要在 ConfigureServices 方法中,將我們的服務添加到 IServiceCollection 容器中,這裡,我們需要為生成的 Swagger Document 進行一些配置。
services.AddSwaggerGen(s => { s.SwaggerDoc("v1", new Info { Contact = new Contact { Name = "Danvic Wang", Email = "[email protected]", Url = "https://yuiter.com" }, Description = "A front-background project build by ASP.NET Core 2.1 and Vue", Title = "Grapefruit.VuCore", Version = "v1" }); });
之後,我們就可以在 Configure 方法中啟用我們的 Swagger 中間件。
app.UseSwagger(); app.UseSwaggerUI(s => { s.SwaggerEndpoint("/swagger/v1/swagger.json", "Grapefruit.VuCore API V1.0"); });
此時,當你運行程式,在功能變數名稱後面輸入/swagger 即可訪問到我們的 API 文檔頁面。因為項目啟動時預設訪問的是我們 api/values 的 GET 請求介面,這裡我們可以打開 Properties 下的 launchSetting.json 文件去配置我們的程式預設打開頁面。
從上面的圖可以看出,不管是使用 IIS 或是程式自托管,我們預設打開的 Url 都是 api/values,這裡我們將兩種啟動方式的 launchUrl 值都修改成 swagger 之後再次運行我們的項目,可以發現,程式預設的打開頁面就會變成我們的 API 文檔頁面。
我們使用 API 文檔的目的,就是為了讓前端知道請求的方法地址是什麼,需要傳遞什麼參數,而現在,並沒有辦法顯示出我們對於參數以及方法的註釋,通過查看 Swashbuckle.AspNetCore 的 github 首頁可以看到,我們可以通過配置,將生成的 json 文件中包含我們對於 Controller or Action 的 Xml 註釋內容,從而達到顯示註釋信息的功能(最終呈現的 Swagger Doc 是根據之前我們定義的這個 “/swagger/v1/swagger.json” json 文件來生成的)。
右鍵我們的 API 項目,屬性 =》生產,勾選上 XML 文檔文件,系統會預設幫我們創建生成 XML 文件的地址,這時候,我們重新生成項目,則會發現,當前項目下會多出這個 XML 文件。在重新生成項目的過程中,你會發現,錯誤列表會顯示很多警告信息,提示我們一些方法沒有添加 XML 註釋。如果你和我一樣強迫症的話,可以把 1591 這個錯誤添加到上面的禁止顯示警告中,這樣就可以不再顯示這個警告了。
創建好 XML 的註釋文件後,我們就可以配置我們的 Swagger 文檔,從而達到顯示註釋的功能。這裡,因為我會在 Grapefruit.Application 類庫中創建各種的 Dto 對象,而介面中是會調用到這些 Dto 對象的。因此,為了顯示這些 Dto 上的註釋信息,這裡我們也需要生成 Grapefruit.Application 項目的 XML 註釋文件。
PS:這裡我是將每個項目生成的註釋信息 xml 文檔地址都放在了程式的基礎路徑下,如果你將 xml 文檔生成在別的位置,這裡獲取 xml 的方法就需要你進行修改。
services.AddSwaggerGen(s => { //... //Add comments description // var basePath = Path.GetDirectoryName(AppContext.BaseDirectory);//get application located directory var apiPath = Path.Combine(basePath, "Grapefruit.WebApi.xml"); var dtoPath = Path.Combine(basePath, "Grapefruit.Application.xml"); s.IncludeXmlComments(apiPath, true); s.IncludeXmlComments(dtoPath, true); });
當我們把 Swagger 配置完成之後,我們就可以創建具有版本控制的 API 介面了。
2、帶有版本控制的 API 介面實現
在請求的 API Url 中標明版本號,我不知道你第一時間看到這個實現方式,會想到什麼,對於我來說,直接在路由信息中添加版本號不就可以了。。。em,這樣過於投機取巧了。。。。
[Route("api/v1/[controller]")]//添加版本信息為v1 [ApiController] public class ValuesController : ControllerBase { }
想了想,在 Url 中添加版本號,這個版本號是不是很像我們在 MVC 中使用的 Area。
Area 是 MVC 中經常使用到的一個功能,我們通常會將某些小的模塊拆分成一個個的 Area,而這一個個的小 Area 其實就是這個 MVC 項目中的 MVC。通過為 controller 和 action 添加另一個路由參數 area,從而達到創建具有層次路由的結構。比如,這裡,我們可以創建一個 Area 叫 v1,用來存儲我們 1.x 版本的 API 介面,之後如果有新的 API 版本,新增一個 Area 即可,是不是很簡單,嗯,說乾就乾。
右擊我們的 API 項目,選擇添加區域,新增的 Area 名稱為 v1。
當 ASP.NET Core 的腳手架程式添加完成 Area 後,則會打開一個文件提示我們需要在 MVC 中間件中創建適用於 Area 的路由定義。
app.UseMvc(routes => { routes.MapRoute( name : "areas", template : "{area:exists}/{controller=Home}/{action=Index}/{id?}" ); });
當我們添加好路由規則定義後,我們在 Area 的 Controllers 文件夾下添加一個 WebAPI Controller。不同於 ASP.NET 中的 Area ,當我們在 ASP.NET Core 創建好一個 Area 之後,腳手架生成的文件中不再有 XXXAreaRegistration(XXX 為 Area 的名稱)文件去註冊這個 Area,而我們只需要在 Area 中的 Controller 中添加 Area 特性,即可告訴系統框架,這個 Controller 是在當前的 Area 下的。
如果你有自己嘗試的話,就會發現,當我們創建好一個 v1 的 Area 後,這個請求的地址並沒有按照我們的想法會體現在路由信息中,我們最後還是需要在 Route 中手動指明 API 版本。
[Area("v1")] [Route("api/v1/[controller]")] [ApiController] public class ValuesController : ControllerBase { }
這樣的話,和最開始直接在路由信息中寫死版本信息其實也就沒什麼差別了,上網搜了搜,發現巨硬爸爸,也早已為我們準備好了實現版本控制 API 的利器 - Microsoft.AspNetCore.Mvc.Versioning。
和上面使用 Swashbuckle.AspNetCore 的方式相同,在我們使用 Versioning 之前,需要在我們的 API 項目中添加對於該 dll 的引用。這裡需要註意下安裝的版本問題,因為 Grapefruit.VuCore 這個框架距離現在搭建也有幾個月的時間了,在這個月初的時候 .NET Core 2.2 也已經發佈了,如果你和我一樣還是採用的 .NET Core 2.1 版本的話,這裡安裝的 Versioning 版本最高只能到 2.3。
Install-Package Microsoft.AspNetCore.Mvc.Versioning
當我們安裝完成之後,就可以進行配置了。
public void ConfigureServices(IServiceCollection services) { services.AddApiVersioning(o => { o.ReportApiVersions = true;//return versions in a response header o.DefaultApiVersion = new ApiVersion(1, 0);//default version select o.AssumeDefaultVersionWhenUnspecified = true;//if not specifying an api version,show the default version }); }
ReportApiVersions:這個配置是可選的,當我們設置為 true 時,API 會在響應的 header 中返回版本信息。
DefaultApiVersion:指定在請求中未指明版本時要使用的預設 API 版本。這將預設版本為1.0。
AssumeDefaultVersionWhenUnspecified:這個配置項將用於在沒有指明 API 版本的情況下提供請求,預設情況下,會請求預設版本的 API,例如,這裡就會請求 1.0 版本的 API。
這裡,刪除我們之前的創建的 Area 和預設的 ValuesController,在 Controllers 文件夾下新增一個 v1 文件夾,將所有 v1 版本的 Controller 都建在這個目錄下。新建一個 Controller,添加上 ApiVersion Attribute 指明當前的版本信息。因為我採用的方案是在 Url 中指明 API 版本,所以,我們還需要在 Route 中修改我們的路由屬性以對應 API 的版本。這裡的 v 只是一個預設的慣例,你也可以不添加。
[ApiVersion("1.0")] [Route("api/v{version:apiVersion}/[controller]")] [ApiController] public class VaulesController : ControllerBase { }
當我們修改好我們的 Controller 之後,運行我們的項目,你會發現,API 文檔中顯示的請求地址是不對的,難道是我們的配置沒起作用嗎?通過 Swagger 自帶的 API 測試工具測試下我們的介面,原來這裡請求的 Url 中已經包含了我們定義的版本信息,當我們指定錯誤的版本信息時,工具也會告訴我們這個版本的介面不存在。
雖然我們請求的 Url 中已經帶上了版本信息,但是 API 文檔上顯示的請求地址卻是不准確的,強迫症,不能忍。這裡,需要我們修改生成 Swagger 文檔的配置代碼,將路由中的版本信息進行替換。重新運行我們的項目,可以發現,文檔顯示的 Url 地址也已經正確了,自此,我們創建帶有版本控制的 API 也就完成了。
public void ConfigureServices(IServiceCollection services) { services.AddSwaggerGen(s => { //... //Show the api version in url address s.DocInclusionPredicate((version, apiDescription) => { var values = apiDescription.RelativePath .Split('/') .Select(v => v.Replace("v{version}", version)); apiDescription.RelativePath = string.Join("/", values); return true; }); }); }
三、總結
本章使用了 Microsoft.AspNetCore.Mvc.Versioning 這一組件來實現我們對於 API 版本控制的功能實現,可能你會有疑問,我們直接在路由中寫明版本信息不是更簡單嗎?在我看來,使用這一組件的目的,在於我們可以以多種的方式實現 API 版本控制的目的,如果哪天你不想在 Url 中指明版本信息後,你可以很快的使用別的形式來完成 API 的版本控制。另外,直接在路由中寫上版本信息,是不是會顯得我們比較 ‘low’,哈哈哈,開玩笑,最後祝大家聖誕快樂~~~