基于.NetCore開發博客項目 StarBlog - (24) 統一接口數據返回格式

1前言

開發接口,是給客戶端(Web前端、App)用的,前面說的RESTFul,是接口的規范,有了統一的接口風格,客戶端開發人員在訪問后端功能的時候能更快找到需要的接口,能寫出可維護性更高的代碼。

而接口的數據返回格式也是接口規范的重要一環,不然一個接口返回JSON,一個返回純字符串,客戶端對接到數據時一臉懵逼,沒法處理啊。

合格的接口返回值應該包括狀態碼、提示信息和數據。

就像這樣:

{"statusCode":?200,"successful":?true,"message":?null,"data":?{}
}

默認AspNetCoreWebAPI模板是沒有特定的返回格式,因為這些業務性質的東西需要開發者自己來定義和完成。

在前面的文章中,可以看到本項目的接口返回值都是 ApiResponse 及其派生類型,這就是在StarBlog里定制的統一返回格式。事實上我的其他項目也在用這套接口返回值,這已經算是一個 Utilities 性質的組件了。

PS:今天寫這篇文章時,我順手把這個返回值發布了一個nuget包,以后在其他項目里使用就不用復制粘貼了~

2分析一下

在 AspNetCore 里寫 WebApi ,我們的 Controller 需要繼承 ControllerBase 這個類

接口 Action 可以設置返回值為 IActionResultActionResult<T> 類型,然后返回數據的時候,可以使用 ControllerBase 封裝好的 Ok(), NotFound() 等方法,這些方法在返回數據的同時會自動設置響應的HTTP狀態碼。

PS:關于 IActionResultActionResult<T> 這倆的區別請參考官方文檔。

本文只提關鍵的一點:ActionResult<T>返回類型可以讓接口在swagger文檔中直觀看出返回的數據類型。

所以我們不僅要封裝統一的返回值,還要實現類似 Ok(), NotFound(), BadRequest() 的快捷方法。

顯然當接口返回類型全都是 ApiResponse<T> 時,這樣返回的狀態碼都是200,不符合需求。

而且有些接口之前已經寫好了,返回類型是 List<T> 這類的,我們也要把這些接口的返回值包裝起來,統一返回格式。

要解決這些問題,我們得了解一下 AspNetCore 的管道模型。

AspNetCore 管道模型

最外層,是中間件,一個請求進來,經過一個個中間件,到最后一個中間件,生成響應,再依次經過一個個中間件走出來,得到最終響應。

781c46060927703bd70f9d6b63dde50a.png
image

常用的 AspNetCore 項目中間件有這些,如下圖所示:

e5afa20ebb9ca88577c597c172030b3b.png
image

最后的 Endpoint 就是最終生成響應的中間件。

在本項目中,Program.cs 配置里的最后一個中間件,就是添加了一個處理 MVC 的 Endpoint

app.MapControllerRoute(name:?"default",pattern:?"{controller=Home}/{action=Index}/{id?}");

這個 Endpoint 的結構又是這樣的:

e17d3b2eb66fd62f80adc35f5a5bf596.png
image

可以看到有很多 Filter 包圍在用戶代碼的前后。

所以得出結論,要修改請求的響應,我們可以選擇:

  • 寫一個中間件處理

  • 使用過濾器(Filter)

那么,來開始寫代碼吧~

3定義ApiResponse

首先是這個出現頻率很高的 ApiResponse,終于要揭曉了~

StarBlog.Web/ViewModels/Response 命名空間下,我創建了三個文件,分別是:

  • ApiResponse.cs

  • ApiResponsePaged.cs: 分頁響應

  • IApiResponse.cs: 幾個相關的接口

ApiResponse.cs 中,其實是兩個類,一個 ApiResponse<T> ,另一個 ApiResponse,帶泛型和不帶泛型。

PS:C#的泛型有點復雜,當時搞這東西搞得暈暈的,又復習了一些逆變和協變,不過最終沒有用上。

接口代碼

上代碼,先是幾個接口的代碼

public?interface?IApiResponse?{public?int?StatusCode?{?get;?set;?}public?bool?Successful?{?get;?set;?}public?string??Message?{?get;?set;?}
}public?interface?IApiResponse<T>?:?IApiResponse?{public?T??Data?{?get;?set;?}
}public?interface?IApiErrorResponse?{public?Dictionary<string,object>?ErrorData?{?get;?set;?}
}

保證了所有相關對象都來自 IApiResponse 接口。

ApiResponse<T>

接著看 ApiResponse<T> 的代碼。

public?class?ApiResponse<T>?:?IApiResponse<T>?{public?ApiResponse()?{}public?ApiResponse(T??data)?{Data?=?data;}public?int?StatusCode?{?get;?set;?}?=?200;public?bool?Successful?{?get;?set;?}?=?true;public?string??Message?{?get;?set;?}public?T??Data?{?get;?set;?}///?<summary>///?實現將?<see?cref="ApiResponse"/>?隱式轉換為?<see?cref="ApiResponse{T}"/>///?</summary>///?<param?name="apiResponse"><see?cref="ApiResponse"/></param>public?static?implicit?operator?ApiResponse<T>(ApiResponse?apiResponse)?{return?new?ApiResponse<T>?{StatusCode?=?apiResponse.StatusCode,Successful?=?apiResponse.Successful,Message?=?apiResponse.Message};}
}

這里使用運算符重載,實現了 ApiResponseApiResponse<T> 的隱式轉換。

等下就能看出有啥用了~

ApiResponse

繼續看 ApiResponse 代碼,比較長,封裝了幾個常用的方法在里面,會有一些重復代碼。

這個類實現了倆接口:IApiResponse, IApiErrorResponse

public?class?ApiResponse?:?IApiResponse,?IApiErrorResponse?{public?int?StatusCode?{?get;?set;?}?=?200;public?bool?Successful?{?get;?set;?}?=?true;public?string??Message?{?get;?set;?}public?object??Data?{?get;?set;?}///?<summary>///?可序列化的錯誤///?<para>用于保存模型驗證失敗的錯誤信息</para>///?</summary>public?Dictionary<string,object>??ErrorData?{?get;?set;?}public?ApiResponse()?{}public?ApiResponse(object?data)?{Data?=?data;}public?static?ApiResponse?NoContent(string?message?=?"NoContent")?{return?new?ApiResponse?{StatusCode?=?StatusCodes.Status204NoContent,Successful?=?true,?Message?=?message};}public?static?ApiResponse?Ok(string?message?=?"Ok")?{return?new?ApiResponse?{StatusCode?=?StatusCodes.Status200OK,Successful?=?true,?Message?=?message};}public?static?ApiResponse?Ok(object?data,?string?message?=?"Ok")?{return?new?ApiResponse?{StatusCode?=?StatusCodes.Status200OK,Successful?=?true,?Message?=?message,Data?=?data};}public?static?ApiResponse?Unauthorized(string?message?=?"Unauthorized")?{return?new?ApiResponse?{StatusCode?=?StatusCodes.Status401Unauthorized,Successful?=?false,?Message?=?message};}public?static?ApiResponse?NotFound(string?message?=?"NotFound")?{return?new?ApiResponse?{StatusCode?=?StatusCodes.Status404NotFound,Successful?=?false,?Message?=?message};}public?static?ApiResponse?BadRequest(string?message?=?"BadRequest")?{return?new?ApiResponse?{StatusCode?=?StatusCodes.Status400BadRequest,Successful?=?false,?Message?=?message};}public?static?ApiResponse?BadRequest(ModelStateDictionary?modelState,?string?message?=?"ModelState?is?not?valid.")?{return?new?ApiResponse?{StatusCode?=?StatusCodes.Status400BadRequest,Successful?=?false,?Message?=?message,ErrorData?=?new?SerializableError(modelState)};}public?static?ApiResponse?Error(string?message?=?"Error",?Exception??exception?=?null)?{object??data?=?null;if?(exception?!=?null)?{data?=?new?{exception.Message,exception.Data};}return?new?ApiResponse?{StatusCode?=?StatusCodes.Status500InternalServerError,Successful?=?false,Message?=?message,Data?=?data};}
}

ApiResponsePaged<T>

這個分頁是最簡單的,只是多了個 Pagination 屬性而已

public?class?ApiResponsePaged<T>?:?ApiResponse<List<T>>?where?T?:?class?{public?ApiResponsePaged()?{}public?ApiResponsePaged(IPagedList<T>?pagedList)?{Data?=?pagedList.ToList();Pagination?=?pagedList.ToPaginationMetadata();}public?PaginationMetadata??Pagination?{?get;?set;?}
}

4類型隱式轉換

來看這個接口

public?ApiResponse<Post>?Get(string?id)?{var?post?=?_postService.GetById(id);return?post?==?null???ApiResponse.NotFound()?:?new?ApiResponse<Post>(post);
}

根據上面的代碼,可以發現 ApiResponse.NotFound() 返回的是一個 ApiResponse 對象

但這接口的返回值明明是 ApiResponse<Post> 類型呀,這不是類型不一致嗎?

不過在 ApiResponse<T> 中,我們定義了一個運算符重載,實現了 ApiResponse 類型到 ApiResponse<T> 的隱式轉換,所以就完美解決這個問題,大大減少了代碼量。

不然原本是要寫成這樣的

return?post?==?null???new?ApiResponse<Post>?{StatusCode?=?StatusCodes.Status404NotFound,Successful?=?false,?Message?=?"未找到"}?:?new?ApiResponse<Post>(post);

現在只需簡簡單單的 ApiResponse.NotFound(),就跟 AspNetCore 自帶的一樣妙~

5包裝返回值

除了這些以 ApiResponseApiResponse<T> 作為返回類型的接口,還有很多其他返回類型的接口,比如

public?List<ConfigItem>?GetAll()?{return?_service.GetAll();
}

還有

public?async?Task<string>?Poem()?{return?await?_crawlService.GetPoem();
}

這些接口在 AspNetCore 生成響應的時候,會把這些返回值歸類為 ObjectResult ,如果不做處理,就會直接序列化成不符合我們返回值規范的格式。

這個不行,必須對這部分接口的返回格式也統一起來。

因為種種原因,最終我選擇使用過濾器來實現這個功能。

關于過濾器的詳細用法,可以參考官方文檔,本文就不展開了,直接上代碼。

創建文件 StarBlog.Web/Filters/ResponseWrapperFilter.cs

public?class?ResponseWrapperFilter?:?IAsyncResultFilter?{public?async?Task?OnResultExecutionAsync(ResultExecutingContext?context,?ResultExecutionDelegate?next)?{if?(context.Result?is?ObjectResult?objectResult)?{if?(objectResult.Value?is?IApiResponse?apiResponse)?{objectResult.StatusCode?=?apiResponse.StatusCode;context.HttpContext.Response.StatusCode?=?apiResponse.StatusCode;}else?{var?statusCode?=?objectResult.StatusCode????context.HttpContext.Response.StatusCode;var?wrapperResp?=?new?ApiResponse<object>?{StatusCode?=?statusCode,Successful?=?statusCode?is?>=?200?and?<?400,Data?=?objectResult.Value,};objectResult.Value?=?wrapperResp;objectResult.DeclaredType?=?wrapperResp.GetType();}}await?next();}
}

在代碼中進行判斷,當響應的類型是 ObjectResult 時,把這個響應結果拿出來,再判斷是不是 IApiResponse 類型。

前面我們介紹過,所有 ApiResponse 都實現了 IApiResponse 這個接口,所以可以判斷是不是 IApiResponse 類型來確定這個返回結果是否包裝過。

沒包裝的話就給包裝一下,就這么簡單。

之后在 Program.cs 里注冊一下這個過濾器。

var?mvcBuilder?=?builder.Services.AddControllersWithViews(options?=>?{?options.Filters.Add<ResponseWrapperFilter>();?}
);

6搞定

這樣就完事兒啦~

最后所有接口(可序列化的),返回格式就都變成了這樣

{"statusCode":?200,"successful":?true,"message":?null,"data":?{}
}

強迫癥表示舒服了~

PS:對了,返回文件的那類接口除外。

7在其他項目中使用

這個 ApiRepsonse ,我已經發布了nuget包

需要在其他項目使用的話,可以直接安裝 CodeLab.Share 這個包

引入 CodeLab.Share.ViewModels.Response 命名空間就完事了~

不用每次都復制粘貼這幾個類,還得改命名空間。

PS:這個包里不包括過濾器!

8參考資料

  • https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-7.0

9系列文章

  • 基于.NetCore開發博客項目 StarBlog - (1) 為什么需要自己寫一個博客?

  • 基于.NetCore開發博客項目 StarBlog - (2) 環境準備和創建項目

  • 基于.NetCore開發博客項目 StarBlog - (3) 模型設計

  • 基于.NetCore開發博客項目 StarBlog - (4) markdown博客批量導入

  • 基于.NetCore開發博客項目 StarBlog - (5) 開始搭建Web項目

  • 基于.NetCore開發博客項目 StarBlog - (6) 頁面開發之博客文章列表

  • 基于.NetCore開發博客項目 StarBlog - (7) 頁面開發之文章詳情頁面

  • 基于.NetCore開發博客項目 StarBlog - (8) 分類層級結構展示

  • 基于.NetCore開發博客項目 StarBlog - (9) 圖片批量導入

  • 基于.NetCore開發博客項目 StarBlog - (10) 圖片瀑布流

  • 基于.NetCore開發博客項目 StarBlog - (11) 實現訪問統計

  • 基于.NetCore開發博客項目 StarBlog - (12) Razor頁面動態編譯

  • 基于.NetCore開發博客項目 StarBlog - (13) 加入友情鏈接功能

  • 基于.NetCore開發博客項目 StarBlog - (14) 實現主題切換功能

  • 基于.NetCore開發博客項目 StarBlog - (15) 生成隨機尺寸圖片

  • 基于.NetCore開發博客項目 StarBlog - (16) 一些新功能 (監控/統計/配置/初始化)

  • 基于.NetCore開發博客項目 StarBlog - (17) 自動下載文章里的外部圖片

  • 基于.NetCore開發博客項目 StarBlog - (18) 實現本地Typora文章打包上傳

  • 基于.NetCore開發博客項目 StarBlog - (19) Markdown渲染方案探索

  • 基于.NetCore開發博客項目 StarBlog - (20) 圖片顯示優化

  • 基于.NetCore開發博客項目 StarBlog - (21) 開始開發RESTFul接口

  • 基于.NetCore開發博客項目 StarBlog - (22) 開發博客文章相關接口

  • 基于.NetCore開發博客項目 StarBlog - (23) 文章列表接口分頁、過濾、搜索、排序

  • 基于.NetCore開發博客項目 StarBlog - (24) 統一接口數據返回格式

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/280019.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/280019.shtml
英文地址,請注明出處:http://en.pswp.cn/news/280019.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何將C# 7類庫升級到C# 8?使用可空引用類型

這篇文章將介紹將C# 7類庫升級到C# 8&#xff08;支持可空引用類型&#xff09;的一個案例。本案例中使用的項目Tortuga Anchor由一組MVVM風格的基類、反射代碼和各種實用程序函數組成。之所以選擇這個項目&#xff0c;是因為它很小&#xff0c;并且同時包含了慣用和不常用的C#…

android 設備名稱_如何更改您的Android TV的設備名稱

android 設備名稱Android TV is Google’s attempt at taking over the living room, and with some units being available for under $99, it’s not unheard of for users to have more than one box. The problem is, when multiple devices identify themselves identical…

AD-查找符合指定條件的用戶Get-User

以下服務器為Exchange 2010一、使用 Get-User 命令查找部門為IT的用戶Get-User -ResultSize Unlimited | ? { $_.Department -Eq "IT" } | ft Name,Department二、查找注釋為多行內容的指定用戶如下圖&#xff1a;注釋Notes信息為多行要使用 match 和 (?*) 來做匹配…

目標檢測算法之Fast R-CNN算法詳解

在介紹Fast R-CNN之前我們先介紹一下SPP Net 一、SPP Net SPP&#xff1a;Spatial Pyramid Pooling&#xff08;空間金字塔池化&#xff09; 眾所周知&#xff0c;CNN一般都含有卷積部分和全連接部分&#xff0c;其中&#xff0c;卷積層不需要固定尺寸的圖像&#xff0c;而全連…

RGB-D(深度圖像) 圖像深度

RGB-D&#xff08;深度圖像&#xff09; 深度圖像 普通的RGB三通道彩色圖像 Depth Map 在3D計算機圖形中&#xff0c;Depth Map&#xff08;深度圖&#xff09;是包含與視點的場景對象的表面的距離有關的信息的圖像或圖像通道。其中&#xff0c;Depth Map 類似于灰度圖像&…

WPF-21 基于MVVM員工管理-01

接下來我們通過兩節課程使用MVVM來開發一個簡單的Demo&#xff0c;首先我們創建一個項目名稱WPF-22-MVVM-Demo&#xff0c;目錄結構如下&#xff1a;我們在Models文件下創建Employee類并讓該類實現INotifyPropertyChanged接口&#xff0c;該類中定義編號、姓名和角色三個基本屬…

qt 蘋果應用程序_什么是蘋果的電視應用程序,您應該使用它嗎?

qt 蘋果應用程序Apple’s TV app, which recently appeared on iOS devices and Apple TV, is meant to help users discover and watch shows across an increasingly expanding lineup of television channels, as well as iTunes movies and shows, in one central app. App…

細說flush、ob_flush的區別

ob_flush/flush在手冊中的描述, 都是刷新輸出緩沖區, 并且還需要配套使用, 所以會導致很多人迷惑… 其實, 他們倆的操作對象不同, 有些情況下, flush根本不做什么事情.. ob_*系列函數, 是操作PHP本身的輸出緩沖區. 所以, ob_flush是刷新PHP自身的緩沖區. 而flush, 嚴格來講, 這…

關于jHipster框架在構建中的出現的error修復

jhipster The JDL object and the database type are both mandatory.這個錯誤應該是在構建基于jHipster的spring-cloud項目中經常遇到的&#xff0c;因為這個在這個過程中會讀取.yo-rc文件&#xff0c;之后生成相關的.json文件&#xff0c;再之后生成相關的.java文件&#xff…

protobuf編碼

proto2Protocol Buffers 是一種輕便高效的結構化數據存儲格式&#xff0c;可以用于結構化數據序列化&#xff0c;適合做數據存儲或 RPC 數據交換格式。可用于通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。 字段規則 required: 字段必須存在opti…

定制.NET 6.0的Middleware中間件

大家好&#xff0c;我是張飛洪&#xff0c;感謝您的閱讀&#xff0c;我會不定期和你分享學習心得&#xff0c;希望我的文章能成為你成長路上的墊腳石&#xff0c;讓我們一起精進。在本文中&#xff0c;我們將學習中間件&#xff0c;以及如何使用它進一步定制應用程序。我們將快…

Python-循環控制--個人課堂筆記

Python中的兩種循環方式&#xff08;目前學到&#xff09;&#xff1a;for循環和while循環 for循環和while循環的區別&#xff1a; for循環一般用于控制循環的次數&#xff0c;while循環則是條件循環。 操作實例-猜數字小游戲&#xff08;3次猜錯提示游戲結束&#xff09;&…

刪除microsoft_如何從您的Microsoft帳戶中刪除設備

刪除microsoftWhen you sign into Windows 8 or 10 using your Microsoft account (and other Microsoft devices, like an Xbox), those devices become associated with your account. If you want to remove an old device you’ve gotten rid of, you’ll have to pay a vi…

線程的語法 (event,重要)

Python threading模塊 2種調用方式 直接調用 12345678910111213141516171819import threadingimport timedef sayhi(num): #定義每個線程要運行的函數print("running on number:%s" %num)time.sleep(3)if __name__ __main__:t1 threading.Thread(targetsayhi,args(…

求最大值和下標值

本題要求編寫程序&#xff0c;找出給定的n個數中的最大值及其對應的最小下標&#xff08;下標從0開始&#xff09;。 輸入格式: 輸入在第一行中給出一個正整數n&#xff08;1<n≤10&#xff09;。第二行輸入n個整數&#xff0c;用空格分開。 輸出格式: 在一行中輸出最大值及…

windows應用商店修復_如何修復Windows應用商店中的卡死下載

windows應用商店修復Though it’s had its share of flaky behavior since being introduced in Windows 8, the Windows Store has gotten more reliable over time. It still has the occasional problems, though. One of the more irritating issues is when an app update…

OpenWrt:Linux下生成banner

Linux下有三個小工具可以生成banner&#xff1a;1、banner使用#生成banner&#xff1b;2、figlet使用一些普通字符生成banner&#xff1b;3、toilet使用一些復雜的彩色特殊字符生成banner。使用apt-get安裝的時候需要輸入以下命令&#xff1a; $ sudo apt-get install sysvbann…

新冠病毒中招 | 第二天

今天跟大家分享我個人感染奧密克戎毒株第二天的經歷和感受。早上7點多自然醒來&#xff0c;已經沒有四肢乏力的感覺&#xff0c;但是身體的本能還是告訴我不愿意動彈。由于第一天躺著睡了一天&#xff0c;確實是躺得腰酸背疼的。起床量了一下體溫36.4正常&#xff0c;決定今天不…

輸出到Excel

HSSFWorkbook oBook new HSSFWorkbook(); NPOI.SS.UserModel.ISheet oSheet oBook.CreateSheet(); #region 輸出到Excel MemoryStream ms new MemoryStream(); oBook.Write(ms);string sExportPath ""; using (SaveFileDialog saveFileDialog1 new SaveFileDial…

JavaScript 精粹 基礎 進階(5)數組

轉載請注明出處 原文連接 blog.huanghanlian.com/article/5b6… 數組是值的有序集合。每個值叫做元素&#xff0c;每個元素在數組中都有數字位置編號&#xff0c;也就是索引。JS中的數組是弱類型的&#xff0c;數組中可以含有不同類型的元素。數組元素甚至可以是對象或其它數組…