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

1|0系列文章

  1. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目
  2. 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来
  3. 基于 abp vNext 和 .NET Core 开发博客项目 - 完善与美化,Swagger登场
  4. 基于 abp vNext 和 .NET Core 开发博客项目 - 数据访问和代码优先
  5. 基于 abp vNext 和 .NET Core 开发博客项目 - 自定义仓储之增删改查
  6. 基于 abp vNext 和 .NET Core 开发博客项目 - 统一规范API,包装返回模型
  7. 基于 abp vNext 和 .NET Core 开发博客项目 - 再说Swagger,分组、描述、小绿锁
  8. 基于 abp vNext 和 .NET Core 开发博客项目 - 接入GitHub,用JWT保护你的API
  9. 基于 abp vNext 和 .NET Core 开发博客项目 - 异常处理和日志记录
  10. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用Redis缓存数据
  11. 基于 abp vNext 和 .NET Core 开发博客项目 - 集成Hangfire实现定时任务处理
  12. 基于 abp vNext 和 .NET Core 开发博客项目 - 用AutoMapper搞定对象映射
  13. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(一)
  14. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(二)
  15. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(三)
  16. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)
  17. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)
  18. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(三)
  19. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(四)
  20. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)
  21. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(一)
  22. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)
  23. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)
  24. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(四)
  25. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)
  26. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(六)
  27. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七)
  28. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(八)
  29. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(九)
  30. 基于 abp vNext 和 .NET Core 开发博客项目 - 终结篇之发布项目

上一篇文章(https://www.cnblogs.com/meowv/p/12916613.html)使用自定义仓储完成了简单的增删改查案例,有心的同学可以看出,我们的返回参数一塌糊涂,显得很不友好。

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

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

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

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

其中还有IsSuccess(...)IsFailed(...)方法,当我们成功返回数据或者当系统出错或者参数异常的时候执行,这一点也不难理解吧。

这个返回模型暂时只支持无需返回参数的api使用,还需要扩展一下,当我们需要返回其它各种复杂类型的数据就行不通了。所以还需要添加一个支持泛型的返回模型,新建模型类:ServiceResultOfT.cs,这里的T就是我们的返回结果,然后继承我们的ServiceResult,指定T为class。并重新编写一个IsSuccess(...)方法,代码如下:

//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、返回结果列表接口IListResult

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


__EOF__

本文作者阿星Plus
本文链接https://www.cnblogs.com/meowv/p/12924409.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   阿星Plus  阅读(4607)  评论(18编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示