基於 abp vNext 和 .NET Core 開發博客項目 - 統一規範API,包裝返回模型

上一篇文章(http://www.javashuo.com/article/p-rscsbfwx-bv.html)使用自定義倉儲完成了簡單的增刪改查案例,有心的同窗能夠看出,咱們的返回參數一塌糊塗,顯得很不友好。html

在實際開發過程當中,每一個公司可能不盡相同,但都大同小異,咱們的返回數據都是包裹在一個公共的模型下面的,而不是直接返回最終數據,在返回參數中,顯示出當前請求的時間戳,是否請求成功,若是錯誤那麼錯誤的消息是什麼,狀態碼(狀態碼能夠是咱們本身定義的值)等等。可能顯得很繁瑣,不必,但這樣作的好處毋庸置疑,除了美化了咱們的API以外,也方便了前端同窗的數據處理。前端

咱們將統一的返回模型放在.ToolKits層中,以前說過這裏主要是公共的工具類、擴展方法。git

新建一個Base文件夾,添加響應實體類ServiceResult.cs,在Enum文件夾下單獨定義一個ServiceResultCode響應碼枚舉,0/1。分別表明 成功和失敗。github

//ServiceResultCode.cs
namespace Meowv.Blog.ToolKits.Base.Enum
{
    /// <summary>
    /// 服務層響應碼枚舉
    /// </summary>
    public enum ServiceResultCode
    {
        /// <summary>
        /// 成功
        /// </summary>
        Succeed = 0,

        /// <summary>
        /// 失敗
        /// </summary>
        Failed = 1,
    }
}
//ServiceResult.cs
using Meowv.Blog.ToolKits.Base.Enum;
using System;

namespace Meowv.Blog.ToolKits.Base
{
    /// <summary>
    /// 服務層響應實體
    /// </summary>
    public class ServiceResult
    {
        /// <summary>
        /// 響應碼
        /// </summary>
        public ServiceResultCode Code { get; set; }

        /// <summary>
        /// 響應信息
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// 成功
        /// </summary>
        public bool Success => Code == ServiceResultCode.Succeed;

        /// <summary>
        /// 時間戳(毫秒)
        /// </summary>
        public long Timestamp { get; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

        /// <summary>
        /// 響應成功
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public void IsSuccess(string message = "")
        {
            Message = message;
            Code = ServiceResultCode.Succeed;
        }

        /// <summary>
        /// 響應失敗
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public void IsFailed(string message = "")
        {
            Message = message;
            Code = ServiceResultCode.Failed;
        }

        /// <summary>
        /// 響應失敗
        /// </summary>
        /// <param name="exexception></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public void IsFailed(Exception exception)
        {
            Message = exception.InnerException?.StackTrace;
            Code = ServiceResultCode.Failed;
        }
    }
}

能夠看到,還定義了 string 類型的 Message,bool 類型的 Success,Success取決於Code == ServiceResultCode.Succeed的結果。還有一個當前的時間戳Timestamp。api

其中還有IsSuccess(...)IsFailed(...)方法,當咱們成功返回數據或者當系統出錯或者參數異常的時候執行,這一點也不難理解吧。異步

這個返回模型暫時只支持無需返回參數的api使用,還須要擴展一下,當咱們須要返回其它各類複雜類型的數據就行不通了。因此還須要添加一個支持泛型的返回模型,新建模型類:ServiceResultOfT.cs,這裏的T就是咱們的返回結果,而後繼承咱們的ServiceResult,指定T爲class。並從新編寫一個IsSuccess(...)方法,代碼以下:async

//ServiceResultOfT.cs
using Meowv.Blog.ToolKits.Base.Enum;

namespace Meowv.Blog.ToolKits.Base
{
    /// <summary>
    /// 服務層響應實體(泛型)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ServiceResult<T> : ServiceResult where T : class
    {
        /// <summary>
        /// 返回結果
        /// </summary>
        public T Result { get; set; }

        /// <summary>
        /// 響應成功
        /// </summary>
        /// <param name="result"></param>
        /// <param name="message"></param>
        public void IsSuccess(T result = null, string message = "")
        {
            Message = message;
            Code = ServiceResultCode.Succeed;
            Result = result;
        }
    }
}

此時針對無需返回參數和須要返回參數的api均可以知足要求了。可是還有一種就沒辦法了,那就是帶分頁的數據,咱們都應該知道想要分頁,數據總數確定是必不可少的。函數

因此此時還須要擴展一個分頁的響應實體,當咱們使用的時候,直接將分頁響應實體做爲上面寫的ServiceResult<T>中的T參數,便可知足需求。工具

新建文件夾Paged,添加總數接口IHasTotalCount、返回結果列表接口IListResultpost

//IHasTotalCount.cs
namespace Meowv.Blog.ToolKits.Base.Paged
{
    public interface IHasTotalCount
    {
        /// <summary>
        /// 總數
        /// </summary>
        int Total { get; set; }
    }
}

//IListResult.cs
using System.Collections.Generic;

namespace Meowv.Blog.ToolKits.Base.Paged
{
    public interface IListResult<T>
    {
        /// <summary>
        /// 返回結果
        /// </summary>
        IReadOnlyList<T> Item { get; set; }
    }
}

IListResult<T>接受一個參數,並將其指定爲IReadOnlyList返回。

如今來實現IListResult接口,新建ListResult實現類,繼承IListResult,在構造函數中爲其賦值,代碼以下:

//ListResult.cs
using System.Collections.Generic;

namespace Meowv.Blog.ToolKits.Base.Paged
{
    public class ListResult<T> : IListResult<T>
    {
        IReadOnlyList<T> item;

        public IReadOnlyList<T> Item
        {
            get => item ?? (item = new List<T>());
            set => item = value;
        }

        public ListResult()
        {
        }

        public ListResult(IReadOnlyList<T> item)
        {
            Item = item;
        }
    }
}

最後新建咱們的分頁響應實體接口:IPagedList和分頁響應實體實現類:PagedList,它同時也要接受一個泛型參數 T。

接口繼承了IListResult<T>IHasTotalCount,實現類繼承ListResult<T>IPagedList<T>,在構造函數中爲其賦值。代碼以下:

//IPagedList.cs
namespace Meowv.Blog.ToolKits.Base.Paged
{
    public interface IPagedList<T> : IListResult<T>, IHasTotalCount
    {
    }
}

//PagedList.cs
using Meowv.Blog.ToolKits.Base.Paged;
using System.Collections.Generic;

namespace Meowv.Blog.ToolKits.Base
{
    /// <summary>
    /// 分頁響應實體
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PagedList<T> : ListResult<T>, IPagedList<T>
    {
        /// <summary>
        /// 總數
        /// </summary>
        public int Total { get; set; }

        public PagedList()
        {
        }

        public PagedList(int total, IReadOnlyList<T> result) : base(result)
        {
            Total = total;
        }
    }
}

到這裏咱們的返回模型就圓滿了,看一下此時下咱們的項目層級目錄。

1

接下來去實踐一下,修改咱們以前建立的增刪改查接口的返回參數。

//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<bool> InsertPostAsync(PostDto dto);
        Task<ServiceResult<string>> InsertPostAsync(PostDto dto);

        //Task<bool> DeletePostAsync(int id);
        Task<ServiceResult> DeletePostAsync(int id);

        //Task<bool> UpdatePostAsync(int id, PostDto dto);
        Task<ServiceResult<string>> UpdatePostAsync(int id, PostDto dto);

        //Task<PostDto> GetPostAsync(int id);
        Task<ServiceResult<PostDto>> GetPostAsync(int id);
    }
}

接口所有爲異步方式,用ServiceResult包裹做爲返回模型,添加和更新T參數爲string類型,刪除就直接不返回結果,而後查詢爲:ServiceResult<PostDto>,再看一下實現類:

//BlogService.cs
...
        public async Task<ServiceResult<string>> InsertPostAsync(PostDto dto)
        {
            var result = new ServiceResult<string>();

            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<ServiceResult> DeletePostAsync(int id)
        {
            var result = new ServiceResult();

            await _postRepository.DeleteAsync(id);

            return result;
        }

        public async Task<ServiceResult<string>> UpdatePostAsync(int id, PostDto dto)
        {
            var result = new ServiceResult<string>();

            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<ServiceResult<PostDto>> GetPostAsync(int id)
        {
            var result = new ServiceResult<PostDto>();

            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<T>()對象。

同時不要忘記在Controller中也須要修改一下,以下:

//BlogController.cs
...
        ...
        public async Task<ServiceResult<string>> InsertPostAsync([FromBody] PostDto dto)
        ...

        ...
        public async Task<ServiceResult> DeletePostAsync([Required] int id)
        ...

        ...
        public async Task<ServiceResult<string>> UpdatePostAsync([Required] int id, [FromBody] PostDto dto)
        ...

        ...
        public async Task<ServiceResult<PostDto>> GetPostAsync([Required] int id)
        ...
...

此時再去咱們的Swagger文檔發起請求,這裏咱們調用一下查詢接口看看返回的樣子,看看效果吧。

2

本篇內容比較簡單,主要是包裝咱們的api,讓返回結果顯得比較正式一點。那麼,你學會了嗎?😁😁😁

開源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

相關文章
相關標籤/搜索