<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Loading

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

      前言

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

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

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

      就像這樣:

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

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

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

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

      分析一下

      在 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 管道模型

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

      image

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

      image

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

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

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

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

      image

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

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

      • 寫一個中間件處理
      • 使用過濾器(Filter)

      那么,來開始寫代碼吧~

      定義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; }
      }
      

      類型隱式轉換

      來看這個接口

      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 自帶的一樣妙~

      包裝返回值

      除了這些以 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>(); }
      );
      

      搞定

      這樣就完事兒啦~

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

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

      強迫癥表示舒服了~

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

      在其他項目中使用

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

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

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

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

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

      參考資料

      系列文章

      posted @ 2022-12-20 23:48  程序設計實驗室  閱讀(1652)  評論(2)    收藏  舉報
      主站蜘蛛池模板: 亚洲老熟女一区二区三区| 国产亚洲精品AA片在线爽| 国产69精品久久久久99尤物| 国产成人a在线观看视频| 偷拍精品一区二区三区| 娇小萝被两个黑人用半米长| 国产精品美女一区二区三| 绝顶丰满少妇av无码| 性少妇tubevⅰdeos高清| 日韩一区在线中文字幕| 国产亚洲欧洲AⅤ综合一区| 国厂精品114福利电影免费| 亚洲欧美日韩愉拍自拍| 在线a亚洲v天堂网2018| 国产精品久久国产精麻豆| 粉嫩少妇内射浓精videos| 亚洲国产精品无码一区二区三区| 久久精产国品一二三产品 | 九九热免费在线视频观看| 亚欧洲乱码视频一二三区| 日本熟妇人妻xxxxx人hd| 夜夜躁日日躁狠狠久久av| 国产激情一区二区三区四区| 亚洲欧洲∨国产一区二区三区| 亚洲色大成网站WWW永久麻豆| 日韩精品福利视频在线观看| 亚洲欧美日韩高清一区二区三区| 99精品国产兔费观看久久99| 国产一区二区丰满熟女人妻| 精品无码老熟妇magnet| 国产精品亚洲mnbav网站| 午夜福利免费视频一区二区| 久久久久无码精品国产h动漫| 国产成人亚洲综合图区| 激情综合色综合啪啪五月| 国产精品免费AⅤ片在线观看| 高清免费毛片| 麻豆精品一区二区三区蜜臀| 久久99精品久久久久久青青| 在线免费播放av观看| av一区二区中文字幕|