在实际开发过程中,每个公司可能不尽相同,但都大同小异,我们的返回数据都是包裹在一个公共的模型下面的,而不是直接返回最终数据,在返回参数中,显示出当前请求的时间戳,是否请求成功,如果错误那么错误的消息是什么,状态码(状态码可以是我们自己定义的值)等等。可能显得很繁琐,没必要,但这样做的好处毋庸置疑,除了美化了我们的API之外,也方便了前端同学的数据处理。

我们将统一的返回模型放在.ToolKits层中,之前说过这里主要是公共的工具类、扩展方法。

新建一个Base文件夹,添加响应实体类ServiceResult.cs,在Enum文件夹下单独定义一个ServiceResultCode响应码枚举,0/1。分别代表 成功和失败。

//ServiceResultCode.cs
namespace Meowv.Blog.ToolKits.Base.Enum
{
    ////// 服务层响应码枚举
    ///public enum ServiceResultCode
    {
        ////// 成功
        ///Succeed = 0,

        ////// 失败
        ///Failed = 1,
    }
}
//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; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

        ////// 响应成功
        ////////////public void IsSuccess(string message = "")
        {
            Message = message;
            Code = ServiceResultCode.Succeed;
        }

        ////// 响应失败
        ////////////public void IsFailed(string message = "")
        {
            Message = message;
            Code = ServiceResultCode.Failed;
        }

        ////// 响应失败
        //////<param name="exexception>//////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(...)方法,代码如下:

//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

//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{
        ////// 返回结果
        ///IReadOnlyListItem { get; set; }
    }
}

IListResult接受一个参数,并将其指定为IReadOnlyList返回。

现在来实现IListResult接口,新建ListResult实现类,继承IListResult,在构造函数中为其赋值,代码如下:

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

namespace Meowv.Blog.ToolKits.Base.Paged
{
    public class ListResult: IListResult{
        IReadOnlyListitem;

        public IReadOnlyListItem
        {
            get => item ?? (item = new List());
            set => item = value;
        }

        public ListResult()
        {
        }

        public ListResult(IReadOnlyListitem)
        {
            Item = item;
        }
    }
}

最后新建我们的分页响应实体接口:IPagedList和分页响应实体实现类:PagedList,它同时也要接受一个泛型参数 T。

接口继承了IListResult和IHasTotalCount,实现类继承ListResult和IPagedList,在构造函数中为其赋值。代码如下:

//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, IReadOnlyListresult) : base(result)
        {
            Total = total;
        }
    }
}

到这里我们的返回模型就圆满了,看一下此时下我们的项目层级目录。

基于 abp vNext 和 .NET Core 开发博客项目 - 统一规范API,包装返回模型_.NET Core

接下来去实践一下,修改我们之前创建的增删改查接口的返回参数。

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

        //TaskDeletePostAsync(int id);
        TaskDeletePostAsync(int id);

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

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

接口全部为异步方式,用ServiceResult包裹作为返回模型,添加和更新T参数为string类型,删除就直接不返回结果,然后查询为:ServiceResult,再看一下实现类:

//BlogService.cs
...
        public async Task<ServiceResult> 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 TaskDeletePostAsync(int id)
        {
            var result = new ServiceResult();

            await _postRepository.DeleteAsync(id);

            return result;
        }

        public async Task<ServiceResult> 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<ServiceResult> 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中也需要修改一下,如下:

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

        ...
        public async TaskDeletePostAsync([Required] int id)
        ...

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

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

此时再去我们的Swagger文档发起请求,这里我们调用一下查询接口看看返回的样子,看看效果吧。

基于 abp vNext 和 .NET Core 开发博客项目 - 统一规范API,包装返回模型_.NET Core_02

本篇内容比较简单,主要是包装我们的api,让返回结果显得比较正式一点。那么,你学会了吗?