石家庄定制网站建设,十大不收费看盘软件网站,深圳网站建设网站制作公司,做seo推广手机网站1前言开发接口#xff0c;是给客户端#xff08;Web前端、App#xff09;用的#xff0c;前面说的RESTFul#xff0c;是接口的规范#xff0c;有了统一的接口风格#xff0c;客户端开发人员在访问后端功能的时候能更快找到需要的接口#xff0c;能写出可维护性更高的代…1前言开发接口是给客户端Web前端、App用的前面说的RESTFul是接口的规范有了统一的接口风格客户端开发人员在访问后端功能的时候能更快找到需要的接口能写出可维护性更高的代码。而接口的数据返回格式也是接口规范的重要一环不然一个接口返回JSON一个返回纯字符串客户端对接到数据时一脸懵逼没法处理啊。合格的接口返回值应该包括状态码、提示信息和数据。就像这样{statusCode: 200,successful: true,message: null,data: {}
}默认AspNetCore的WebAPI模板是没有特定的返回格式因为这些业务性质的东西需要开发者自己来定义和完成。在前面的文章中可以看到本项目的接口返回值都是 ApiResponse 及其派生类型这就是在StarBlog里定制的统一返回格式。事实上我的其他项目也在用这套接口返回值这已经算是一个 Utilities 性质的组件了。PS今天写这篇文章时我顺手把这个返回值发布了一个nuget包以后在其他项目里使用就不用复制粘贴了~2分析一下在 AspNetCore 里写 WebApi 我们的 Controller 需要继承 ControllerBase 这个类接口 Action 可以设置返回值为 IActionResult 或 ActionResultT 类型然后返回数据的时候可以使用 ControllerBase 封装好的 Ok(), NotFound() 等方法这些方法在返回数据的同时会自动设置响应的HTTP状态码。PS关于 IActionResult 或 ActionResultT 这俩的区别请参考官方文档。本文只提关键的一点ActionResultT返回类型可以让接口在swagger文档中直观看出返回的数据类型。所以我们不仅要封装统一的返回值还要实现类似 Ok(), NotFound(), BadRequest() 的快捷方法。显然当接口返回类型全都是 ApiResponseT 时这样返回的状态码都是200不符合需求。而且有些接口之前已经写好了返回类型是 ListT 这类的我们也要把这些接口的返回值包装起来统一返回格式。要解决这些问题我们得了解一下 AspNetCore 的管道模型。AspNetCore 管道模型最外层是中间件一个请求进来经过一个个中间件到最后一个中间件生成响应再依次经过一个个中间件走出来得到最终响应。image常用的 AspNetCore 项目中间件有这些如下图所示image最后的 Endpoint 就是最终生成响应的中间件。在本项目中Program.cs 配置里的最后一个中间件就是添加了一个处理 MVC 的 Endpointapp.MapControllerRoute(name: default,pattern: {controllerHome}/{actionIndex}/{id?});这个 Endpoint 的结构又是这样的image可以看到有很多 Filter 包围在用户代码的前后。所以得出结论要修改请求的响应我们可以选择写一个中间件处理使用过滤器(Filter)那么来开始写代码吧~3定义ApiResponse首先是这个出现频率很高的 ApiResponse终于要揭晓了~在 StarBlog.Web/ViewModels/Response 命名空间下我创建了三个文件分别是ApiResponse.csApiResponsePaged.cs: 分页响应IApiResponse.cs: 几个相关的接口ApiResponse.cs 中其实是两个类一个 ApiResponseT 另一个 ApiResponse带泛型和不带泛型。PSC#的泛型有点复杂当时搞这东西搞得晕晕的又复习了一些逆变和协变不过最终没有用上。接口代码上代码先是几个接口的代码public interface IApiResponse {public int StatusCode { get; set; }public bool Successful { get; set; }public string? Message { get; set; }
}public interface IApiResponseT : IApiResponse {public T? Data { get; set; }
}public interface IApiErrorResponse {public Dictionarystring,object ErrorData { get; set; }
}保证了所有相关对象都来自 IApiResponse 接口。ApiResponseT接着看 ApiResponseT 的代码。public class ApiResponseT : IApiResponseT {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 crefApiResponse/ 隐式转换为 see crefApiResponse{T}//// /summary/// param nameapiResponsesee crefApiResponse//parampublic static implicit operator ApiResponseT(ApiResponse apiResponse) {return new ApiResponseT {StatusCode apiResponse.StatusCode,Successful apiResponse.Successful,Message apiResponse.Message};}
}这里使用运算符重载实现了 ApiResponse 到 ApiResponseT 的隐式转换。等下就能看出有啥用了~ApiResponse继续看 ApiResponse 代码比较长封装了几个常用的方法在里面会有一些重复代码。这个类实现了俩接口IApiResponse, IApiErrorResponsepublic 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/// /summarypublic Dictionarystring,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};}
}ApiResponsePagedT这个分页是最简单的只是多了个 Pagination 属性而已public class ApiResponsePagedT : ApiResponseListT where T : class {public ApiResponsePaged() {}public ApiResponsePaged(IPagedListT pagedList) {Data pagedList.ToList();Pagination pagedList.ToPaginationMetadata();}public PaginationMetadata? Pagination { get; set; }
}4类型隐式转换来看这个接口public ApiResponsePost Get(string id) {var post _postService.GetById(id);return post null ? ApiResponse.NotFound() : new ApiResponsePost(post);
}根据上面的代码可以发现 ApiResponse.NotFound() 返回的是一个 ApiResponse 对象但这接口的返回值明明是 ApiResponsePost 类型呀这不是类型不一致吗不过在 ApiResponseT 中我们定义了一个运算符重载实现了 ApiResponse 类型到 ApiResponseT 的隐式转换所以就完美解决这个问题大大减少了代码量。不然原本是要写成这样的return post null ? new ApiResponsePost {StatusCode StatusCodes.Status404NotFound,Successful false, Message 未找到} : new ApiResponsePost(post);现在只需简简单单的 ApiResponse.NotFound()就跟 AspNetCore 自带的一样妙~5包装返回值除了这些以 ApiResponse 或 ApiResponseT 作为返回类型的接口还有很多其他返回类型的接口比如public ListConfigItem GetAll() {return _service.GetAll();
}还有public async Taskstring Poem() {return await _crawlService.GetPoem();
}这些接口在 AspNetCore 生成响应的时候会把这些返回值归类为 ObjectResult 如果不做处理就会直接序列化成不符合我们返回值规范的格式。这个不行必须对这部分接口的返回格式也统一起来。因为种种原因最终我选择使用过滤器来实现这个功能。关于过滤器的详细用法可以参考官方文档本文就不展开了直接上代码。创建文件 StarBlog.Web/Filters/ResponseWrapperFilter.cspublic 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 ApiResponseobject {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.AddResponseWrapperFilter(); }
);6搞定这样就完事儿啦~最后所有接口可序列化的返回格式就都变成了这样{statusCode: 200,successful: true,message: null,data: {}
}强迫症表示舒服了~PS对了返回文件的那类接口除外。7在其他项目中使用这个 ApiRepsonse 我已经发布了nuget包需要在其他项目使用的话可以直接安装 CodeLab.Share 这个包引入 CodeLab.Share.ViewModels.Response 命名空间就完事了~不用每次都复制粘贴这几个类还得改命名空间。PS这个包里不包括过滤器8参考资料https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?viewaspnetcore-7.09系列文章基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目基于.NetCore开发博客项目 StarBlog - (3) 模型设计基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入基于.NetCore开发博客项目 StarBlog - (5) 开始搭建Web项目基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表基于.NetCore开发博客项目 StarBlog - (7) 页面开发之文章详情页面基于.NetCore开发博客项目 StarBlog - (8) 分类层级结构展示基于.NetCore开发博客项目 StarBlog - (9) 图片批量导入基于.NetCore开发博客项目 StarBlog - (10) 图片瀑布流基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计基于.NetCore开发博客项目 StarBlog - (12) Razor页面动态编译基于.NetCore开发博客项目 StarBlog - (13) 加入友情链接功能基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能基于.NetCore开发博客项目 StarBlog - (15) 生成随机尺寸图片基于.NetCore开发博客项目 StarBlog - (16) 一些新功能 (监控/统计/配置/初始化)基于.NetCore开发博客项目 StarBlog - (17) 自动下载文章里的外部图片基于.NetCore开发博客项目 StarBlog - (18) 实现本地Typora文章打包上传基于.NetCore开发博客项目 StarBlog - (19) Markdown渲染方案探索基于.NetCore开发博客项目 StarBlog - (20) 图片显示优化基于.NetCore开发博客项目 StarBlog - (21) 开始开发RESTFul接口基于.NetCore开发博客项目 StarBlog - (22) 开发博客文章相关接口基于.NetCore开发博客项目 StarBlog - (23) 文章列表接口分页、过滤、搜索、排序基于.NetCore开发博客项目 StarBlog - (24) 统一接口数据返回格式