1. 程式人生 > >基於 abp vNext 和 .NET Core 開發部落格專案 - 統一規範API,包裝返回模型

基於 abp vNext 和 .NET Core 開發部落格專案 - 統一規範API,包裝返回模型

上一篇文章(https://www.cnblogs.com/meowv/p/12916613.html)使用自定義倉儲完成了簡單的增刪改查案例,有心的同學可以看出,我們的返回引數一塌糊塗,顯得很不友好。 在實際開發過程中,每個公司可能不盡相同,但都大同小異,我們的返回資料都是包裹在一個公共的模型下面的,而不是直接返回最終資料,在返回引數中,顯示出當前請求的時間戳,是否請求成功,如果錯誤那麼錯誤的訊息是什麼,狀態碼(狀態碼可以是我們自己定義的值)等等。可能顯得很繁瑣,沒必要,但這樣做的好處毋庸置疑,除了美化了我們的API之外,也方便了前端同學的資料處理。 我們將統一的返回模型放在`.ToolKits`層中,之前說過這裡主要是公共的工具類、擴充套件方法。 新建一個Base資料夾,新增響應實體類`ServiceResult.cs`,在Enum資料夾下單獨定義一個`ServiceResultCode`響應碼列舉,0/1。分別代表 成功和失敗。 ```CSharp //ServiceResultCode.cs namespace Meowv.Blog.ToolKits.Base.Enum { /// /// 服務層響應碼列舉 /// public enum ServiceResultCode { /// /// 成功 /// Succeed = 0, /// /// 失敗 /// Failed = 1, } } ``` ```CSharp //ServiceResult.cs using Meowv.Blog.ToolKits.Base.Enum; using System; namespace Meowv.Blog.ToolKits.Base { /// /// 服務層響應實體 ///
public class ServiceResult { /// /// 響應碼 /// public ServiceResultCode Code { get; set; } /// /// 響應資訊 /// public string Message { get; set; } /// /// 成功 /// public bool Success => Code == ServiceResultCode.Succeed; /// /// 時間戳(毫秒) ///
public long Timestamp { get; } = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; /// /// 響應成功 /// /// /// /// public void IsSuccess(string message = "") { Message = message; Code = ServiceResultCode.Succeed; } /// /// 響應失敗 ///
/// /// /// public void IsFailed(string message = "") { Message = message; Code = ServiceResultCode.Failed; } /// /// 響應失敗 /// /// /// public void IsFailed(Exception exception) { Message = exception.InnerException?.StackTrace; Code = ServiceResultCode.Failed; } } } ``` 可以看到,還定義了 string 型別的 Message,bool 型別的 Success,Success取決於`Code == ServiceResultCode.Succeed`的結果。還有一個當前的時間戳Timestamp。 其中還有`IsSuccess(...)`和`IsFailed(...)`方法,當我們成功返回資料或者當系統出錯或者引數異常的時候執行,這一點也不難理解吧。 這個返回模型暫時只支援無需返回引數的api使用,還需要擴充套件一下,當我們需要返回其它各種複雜型別的資料就行不通了。所以還需要新增一個支援泛型的返回模型,新建模型類:`ServiceResultOfT.cs`,這裡的T就是我們的返回結果,然後繼承我們的ServiceResult,指定T為class。並重新編寫一個`IsSuccess(...)`方法,程式碼如下: ```CSharp //ServiceResultOfT.cs using Meowv.Blog.ToolKits.Base.Enum; namespace Meowv.Blog.ToolKits.Base { /// /// 服務層響應實體(泛型) /// /// public class ServiceResult : ServiceResult where T : class { /// /// 返回結果 /// public T Result { get; set; } /// /// 響應成功 /// /// /// public void IsSuccess(T result = null, string message = "") { Message = message; Code = ServiceResultCode.Succeed; Result = result; } } } ``` 此時針對無需返回引數和需要返回引數的api都可以滿足要求了。但是還有一種就沒辦法了,那就是帶分頁的資料,我們都應該知道想要分頁,資料總數肯定是必不可少的。 所以此時還需要擴充套件一個分頁的響應實體,當我們使用的時候,直接將分頁響應實體作為上面寫的`ServiceResult`中的T引數,即可滿足需求。 新建資料夾Paged,新增總數介面`IHasTotalCount`、返回結果列表介面`IListResult` ```CSharp //IHasTotalCount.cs namespace Meowv.Blog.ToolKits.Base.Paged { public interface IHasTotalCount { /// /// 總數 /// int Total { get; set; } } } //IListResult.cs using System.Collections.Generic; namespace Meowv.Blog.ToolKits.Base.Paged { public interface IListResult { /// /// 返回結果 /// IReadOnlyList Item { get; set; } } } ``` `IListResult`接受一個引數,並將其指定為`IReadOnlyList`返回。 現在來實現`IListResult`介面,新建`ListResult`實現類,繼承`IListResult`,在建構函式中為其賦值,程式碼如下: ```CSharp //ListResult.cs using System.Collections.Generic; namespace Meowv.Blog.ToolKits.Base.Paged { public class ListResult : IListResult { IReadOnlyList item; public IReadOnlyList Item { get => item ?? (item = new List()); set => item = value; } public ListResult() { } public ListResult(IReadOnlyList item) { Item = item; } } } ``` 最後新建我們的分頁響應實體介面:`IPagedList`和分頁響應實體實現類:`PagedList`,它同時也要接受一個泛型引數 T。 介面繼承了`IListResult`和`IHasTotalCount`,實現類繼承`ListResult`和`IPagedList`,在建構函式中為其賦值。程式碼如下: ```CSharp //IPagedList.cs namespace Meowv.Blog.ToolKits.Base.Paged { public interface IPagedList : IListResult, IHasTotalCount { } } //PagedList.cs using Meowv.Blog.ToolKits.Base.Paged; using System.Collections.Generic; namespace Meowv.Blog.ToolKits.Base { /// /// 分頁響應實體 /// /// public class PagedList : ListResult, IPagedList { /// /// 總數 /// public int Total { get; set; } public PagedList() { } public PagedList(int total, IReadOnlyList result) : base(result) { Total = total; } } } ``` 到這裡我們的返回模型就圓滿了,看一下此時下我們的專案層級目錄。 ![1](https://img2020.cnblogs.com/blog/891843/202005/891843-20200520160152496-1654274153.png) 接下來去實踐一下,修改我們之前建立的增刪改查介面的返回引數。 ```CSharp //IBlogService.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System.Threading.Tasks; namespace Meowv.Blog.Application.Blog { public interface IBlogService { //Task InsertPostAsync(PostDto dto); Task> InsertPostAsync(PostDto dto); //Task DeletePostAsync(int id); Task DeletePostAsync(int id); //Task UpdatePostAsync(int id, PostDto dto); Task> UpdatePostAsync(int id, PostDto dto); //Task GetPostAsync(int id); Task> GetPostAsync(int id); } } ``` 介面全部為非同步方式,用`ServiceResult`包裹作為返回模型,新增和更新T引數為string型別,刪除就直接不返回結果,然後查詢為:`ServiceResult`,再看一下實現類: ```CSharp //BlogService.cs ... public async Task> InsertPostAsync(PostDto dto) { var result = new ServiceResult(); var entity = new Post { Title = dto.Title, Author = dto.Author, Url = dto.Url, Html = dto.Html, Markdown = dto.Markdown, CategoryId = dto.CategoryId, CreationTime = dto.CreationTime }; var post = await _postRepository.InsertAsync(entity); if (post == null) { result.IsFailed("新增失敗"); return result; } result.IsSuccess("新增成功"); return result; } public async Task DeletePostAsync(int id) { var result = new ServiceResult(); await _postRepository.DeleteAsync(id); return result; } public async Task> UpdatePostAsync(int id, PostDto dto) { var result = new ServiceResult(); var post = await _postRepository.GetAsync(id); if (post == null) { result.IsFailed("文章不存在"); return result; } post.Title = dto.Title; post.Author = dto.Author; post.Url = dto.Url; post.Html = dto.Html; post.Markdown = dto.Markdown; post.CategoryId = dto.CategoryId; post.CreationTime = dto.CreationTime; await _postRepository.UpdateAsync(post); result.IsSuccess("更新成功"); return result; } public async Task> GetPostAsync(int id) { var result = new ServiceResult(); var post = await _postRepository.GetAsync(id); if (post == null) { result.IsFailed("文章不存在"); return result; } var dto = new PostDto { Title = post.Title, Author = post.Author, Url = post.Url, Html = post.Html, Markdown = post.Markdown, CategoryId = post.CategoryId, CreationTime = post.CreationTime }; result.IsSuccess(dto); return result; } ... ``` 當成功時,呼叫`IsSuccess(...)`方法,當失敗時,呼叫`IsFailed(...)`方法。最終我們返回的是`new ServiceResult()`或者`new ServiceResult()`物件。 同時不要忘記在Controller中也需要修改一下,如下: ```CSharp //BlogController.cs ... ... public async Task> InsertPostAsync([FromBody] PostDto dto) ... ... public async Task DeletePostAsync([Required] int id) ... ... public async Task> UpdatePostAsync([Required] int id, [FromBody] PostDto dto) ... ... public async Task> GetPostAsync([Required] int id) ... ... ``` 此時再去我們的Swagger文件發起請求,這裡我們呼叫一下查詢介面看看返回的樣子,看看效果吧。 ![2](https://img2020.cnblogs.com/blog/891843/202005/891843-20200520162754540-370589984.png) 本篇內容比較簡單,主要是包裝我們的api,讓返回結果顯得比較正式一點。那麼,你學會了嗎?