dotNET Core WebAPI 统一处理(返回值、参数验证、异常)

现在 Web 开发比较流行前后端分离

640?wx_fmt=jpeg

现在 Web 开发比较流行前后端分离,我们的产品也是一样,前端使用Vue,后端使用 dotNet Core WebAPI ,在写 API 的过程中有很多地方需要统一处理

  • 文档

  • 参数验证

  • 返回值

  • 异常处理

本文就说说 API 的统一处理这些事。

环境

dotNet Core:2.1

文档

Swagger 是一个 API 文档生成框架,在非 Core 时代就一直在使用,现在前后端分离的模式下,API 文档更是非常重要,让前端开发人员和后端开发人员能更好的沟通和合作,前端开发人员在 Swagger 可以了解到接口的地址、入参、出参,还能模拟调用,非常方便。

安装

在 VS For Mac 中创建 API 项目 DotNetCoreApiSample ,在依赖项中的 NuGet 上点击右键,选择添加包,如下图:

640?wx_fmt=png

搜索 Swashbuckle.AspNetCore,选中搜索结果的第一条,点击「添加包」按钮进行添加。

配置

Startup 类的 ConfigureServices 方法中添加

services.AddSwaggerGen(options =>
{options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info{Version = "v1",Title = "DotNet Core WebAPI文档"});});

Startup 类的 Configure 方法中添加

app.UseSwagger();
app.UseSwaggerUI(c =>
{c.SwaggerEndpoint("/swagger/v1/swagger.json", "DotNet Core WebAPI文档");
});

运行效果

运行 WepAPI 项目,在浏览器中输入 http://localhost:5000/swagger ,效果如下

640?wx_fmt=png

参数验证

此处所说的参数验证指的是实体类型的参数验证,通过在实体的属性上添加特性的方式来实现。

简单实现

创建名为 ValidationDemoController 的 API 类,代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;namespace DotNetCoreApiSample.Controllers
{[Route("api/[controller]")]public class ValidationDemoController : Controller{[HttpPost]public IActionResult AddUser([FromBody]User user){string errorMessage = string.Empty;if (!ModelState.IsValid){foreach (var item in ModelState.Values){foreach (var error in item.Errors){errorMessage += error.ErrorMessage + "|";}}}if(!string.IsNullOrEmpty(errorMessage)){return BadRequest(errorMessage);}return Ok();}}public class User{[Required(ErrorMessage = "用户Code不能为空")]public string Code { get; set; }[Required(ErrorMessage = "用户名称不能为空")]public string Name { get; set; }[Required(ErrorMessage = "用户年龄不能为空")][Range(1, 100, ErrorMessage = "年龄必须介于1~100之间")]public int Age { get; set; }public string Address { get; set; }}
}

  • 实体类属性使用 Required 等特性需要引用命名空间System.ComponentModel.DataAnnotations

  • 除了上面的 Required 和 Range 标记,还有很多实用的标记,详细参考:https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations(v=vs.110).aspx

  • 上面的示例代码将错误信息的收集写在了接口方法中,这是一个很不好的做法,仅仅实现了功能,下面将通过过滤器的方式来进行重构,统一处理错误信息

重构

添加名为 ValidateModelAttribute 的过滤器类,继承 ActionFilterAttribute ,代码如下

namespace DotNetCoreApiSample.Filters
{public class ValidateModelAttribute : ActionFilterAttribute{public override void OnActionExecuting(ActionExecutingContext context){if (!context.ModelState.IsValid){var result = context.ModelState.Keys.SelectMany(key => context.ModelState[key].Errors.Select(x => new ValidationError(key, x.ErrorMessage))).ToList();context.Result = new ObjectResult(result);}}}public class ValidationError{[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]public string Field { get; }public string Message { get; }public ValidationError(string field, string message){Field = field != string.Empty ? field : null;Message = message;}}
}

Startup 类的 ConfigureServices 方法中添加下面代码:

services.AddMvc(options =>
{options.Filters.Add<ValidateModelAttribute>();
});

使用 Postman 调用结果如下

640?wx_fmt=png

返回值

返回值的统一处理需要下面几个步骤:

  • 创建统一返回结果的实体类,所有的接口方法都返回固定格式,方便前端统一处理

  • 创建过滤器,过滤器用来拦截请求,包装结果,统一输出

  • Startup 类中进行配置注册

结果实体类

接口的返回值需要统一的格式,下面的属性字段是我认为必须要有的

  • Result:返回的结果

  • Message:出现错误或需要提示时的提示文本内容

  • Code:调用成功、失败或出错时的编码

  • ReturnStatus:用来判断接口调用状态的

创建返回结果的实体类 BaseResultModel

public class BaseResultModel
{public BaseResultModel(int? code = null, string message = null,object result = null, ReturnStatus returnStatus = ReturnStatus.Success){this.Code = code;this.Result = result;this.Message = message;this.ReturnStatus = returnStatus;}public int? Code { get; set; }public string Message { get; set; }public object Result { get; set; }public ReturnStatus ReturnStatus { get; set; }
}
public enum ReturnStatus
{Success = 1,Fail = 0,ConfirmIsContinue = 2,Error = 3
}

过滤器类

创建名称为 ApiResultFilterAttribute 的过滤器类,该类继承 ActionFilterAttribute ,具体代码如下

public class ApiResultFilterAttribute : ActionFilterAttribute
{public override void OnActionExecuting(ActionExecutingContext context){base.OnActionExecuting(context);}public override void OnResultExecuting(ResultExecutingContext context){var objectResult = context.Result as ObjectResult;context.Result = new OkObjectResult(new BaseResultModel(code:200, result: objectResult.Value));}
}

在过滤器中将接口的返回值获取后重新包装到 BaseResultModel 模型类中进行返回。

Startup 配置

在 Startup 类的 ConfigureServices 方法中添加如下代码

services.AddMvc(options =>
{options.Filters.Add<ValidateModelAttribute>();options.Filters.Add<ApiResultFilterAttribute>();
});

添加示例接口方法

[HttpGet]
public IActionResult GetUserCode()
{return Ok("oec2003");
}

运行效果

使用 Postman 调用该接口方法,返回结果如下

640?wx_fmt=png

继续重构参数验证

添加了返回值的过滤器类后,调用之前的参数验证的接口,会发现返回结果如下

{"code": 200,"message": null,"result": [{"field": "Age","message": "年龄必须介于1~100之间"}],"returnStatus": 1
}

接口会调用两次过滤器,先调用参数验证的过滤器,再调用返回值的过滤器,导致验证失败的接口返回值状态也是成功的,所以需要做进一步重构。

1、添加 ValidationFailedResultModel 类

public class ValidationFailedResultModel : BaseResultModel
{public ValidationFailedResultModel(ModelStateDictionary modelState){Code = 422;Message = "参数不合法";Result = modelState.Keys.SelectMany(key => modelState[key].Errors.Select(x => new ValidationError(key, x.ErrorMessage))).ToList();ReturnStatus = ReturnStatus.Fail;}
}public class ValidationError
{[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]public string Field { get; }public string Message { get; }public ValidationError(string field, string message){Field = field != string.Empty ? field : null;Message = message;}
}

将错误信息的收集移到了 ValidationFailedResultModel 类中,所以

2、修改 ValidateModelAttribute 过滤器,在修改代码之前,先要添加名为 ValidationFailedResult 的类,该类继承 ObjectResult ,用做参数验证的结果收集。

public class ValidationFailedResult: ObjectResult
{public ValidationFailedResult(ModelStateDictionary modelState): base(new ValidationFailedResultModel(modelState)){StatusCode = StatusCodes.Status422UnprocessableEntity;}
}

修改 ValidateModelAttribute 类

public override void OnActionExecuting(ActionExecutingContext context)
{if (!context.ModelState.IsValid){context.Result = new ValidationFailedResult(context.ModelState);}
}

3、修改 ApiResultFilterAttribute 过滤器,添加对 ValidationFailedResult 类型的判断

public override void OnResultExecuting(ResultExecutingContext context)
{if (context.Result is ValidationFailedResult){var objectResult = context.Result as ObjectResult;context.Result = objectResult;}else{var objectResult = context.Result as ObjectResult;context.Result = new OkObjectResult(new BaseResultModel(code: 200, result: objectResult.Value));}
}

4、调用参数验证接口结果如下

640?wx_fmt=png

异常处理

异常处理和参数验证的方式基本相同,有以下几个步骤

1、创建名为 CustomExceptionResultModel 的模型类

public class CustomExceptionResultModel:BaseResultModel
{public CustomExceptionResultModel(int? code, Exception exception){Code = code;Message = exception.InnerException != null ?exception.InnerException.Message :exception.Message;Result = exception.Message;ReturnStatus = ReturnStatus.Error;}
}

2、创建名为 CustomExceptionResult 的异常结果类

public class CustomExceptionResult:ObjectResult
{public CustomExceptionResult(int? code, Exception exception): base(new CustomExceptionResultModel(code, exception)){StatusCode = code;}
}

3、创建名为 CustomExceptionAttribute 的异常过滤器类,继承自 IExceptionFilter

public class CustomExceptionAttribute : IExceptionFilter
{public void OnException(ExceptionContext context){HttpStatusCode status = HttpStatusCode.InternalServerError;//处理各种异常context.ExceptionHandled = true;context.Result = new CustomExceptionResult((int)status, context.Exception);}
}

4、Startup 配置

在 Startup 类的 ConfigureServices 方法中添加如下代码

services.AddMvc(options =>
{options.Filters.Add<ValidateModelAttribute>();options.Filters.Add<ApiResultFilterAttribute>();options.Filters.Add<CustomExceptionAttribute>();
});

感兴趣的朋友可以在 Github 上下载示例代码进行调试。

总结

如果是从零开始搭建一个 WebAPI 项目,这些基础处理是必不可少的,有了这些做保障才能专注于业务代码的编写。

本文只是抛砖引玉,同样的思路我们还可以实现更多的功能,例如

  • 如果某些特殊接口需要直接返回值怎么办?

  • 怎样记录耗时较长的接口?

  • 怎样做接口的验证?

点击「阅读原文」可访问示例代码。

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/314277.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HDU 4059 The Boss on Mars (容斥)(2011 Asia Dalian Regional Contest)

The Boss on Mars 思路 显然我们可以求得∑i1ni46n515n410n3−n30\sum_{i 1} ^{n} i ^ 4 \frac{6n^5 15n^4 10n ^3 - n}{30}∑i1n​i4306n515n410n3−n​,接下来就是考虑把其中不与nnn互质的数给踢出去了,显然我们可以考虑容斥。 假设np1a1p2a2p3…

P2764 最小路径覆盖问题(网络流)

P2764 最小路径覆盖问题 最小链覆盖问题,关键在于怎么转化为网络流问题,我们可以发现网络流的常见套路就是将一个点拆成出点和入点来处理,对于一条链恰好满足出点和入点匹配的性质,所以可以拆点然后对应连边,这样跑最…

.net测试篇之单元测试/集成测试神器Autofixture

autofixture简介有了单元测试框架加上Moq(后面我们会用单独章节来介绍moq),可以说测试问题基上都能搞定了.然而有了AutoFixture对单元测试来说可以说是如虎添翼,AutoFixture并且它能与moq,rhinomock等框架结合,对单元测试带来的便捷性,可维护性和扩展性更是难以言表,只有用用了…

杜教筛入门详解

杜教筛入门 前置知识 迪利克雷卷积(*): 先介绍三个重要的函数: 元函数ϵ(n)[n1]\epsilon(n) [n 1]ϵ(n)[n1] 原函数可以看成是迪利克雷卷积里的单位元,即(ϵ∗f)(n)f(n)(\epsilon * f)(n) f(n)(ϵ∗f)(n)f(n)&am…

P2765 魔术球问题(网络流)

P2765 魔术球问题 给出n根柱子&#xff0c;求解可以将最多多少编号的球放在上面&#xff0c;并且满足相邻的编号和为完全平方数。 n<50 这个数据范围我们可以选择网络流&#xff0c;然后将对应点连边&#xff0c;然后每次枚举编号&#xff0c;将其加入残量网络&#xff0c…

DotNetCore 3.0 助力 WPF本地化

概览随着我们的应用程序越来越受欢迎&#xff0c;我们的下一步将要开发多语言功能。方便越来越多的国家使用我们中国的应用程序&#xff0c;基于 WPF 本地化&#xff0c;我们很多时候使用的是系统资源文件&#xff0c;可是动态切换本地化&#xff0c;就比较麻烦了。实现思路现在…

#6229. 这是一道简单的数学题(反演 + 杜教筛)

#6229. 这是一道简单的数学题 推式子 ∑i1n∑j1ilcm(i,j)gcd(i,j)(∑i1n∑j1nlcm(i,j)gcd(i,j)n)∗inv2所以重点求∑i1n∑j1nlcm(i,j)gcd(i,j)∑i1n∑j1nijgcd(i,j)2∑d1n∑i1nd∑j1ndij(gcd(i,j)1)∑d1n∑k1ndμ(k)k2(∑i1nkdi)2我们另tkd&#xff0c;得到∑t1n(∑i1nti)2∑k…

P2766 最长不下降子序列问题(网络流)

P2766 最长不下降子序列问题 求解LIS长度k求解长度为k的不下降子序列个数&#xff0c;并且一个数只能使用一次求解长度为k的不下降子序列个数&#xff0c;第一个数和第n个数可以使用任意次 首先利用dp可以求解出以每个点开始的最长不下降子序列&#xff0c;然后可以类似于最短…

开源题材征集 + MVCEF Core 完整教程小结

到目前为止&#xff0c;我们的MVCEF Core 完整教程的理论部分就全部结束了&#xff0c;共20篇&#xff0c;覆盖了核心的主要知识点。下一阶段是实战部分&#xff0c;我们将会把这些知识点串联起来&#xff0c;用10篇(天)来完成一个开源项目。现向园友征集题材&#xff0c;你提需…

P2770 航空路线问题(网络流)

P2770 航空路线问题 似乎是一个经典的双调路径问题&#xff0c;然后这里使用网络流解决了&#xff0c;本质上要求两条路径没有经过同一个点&#xff0c;并且总长度最大&#xff0c;所以我们实际上可以跑网络流&#xff0c;然后拆点限制路径没有交点。

欧拉心算(反演 + 积性函数筛)

欧拉心算 推式子 ∑i1n∑j1nϕ(gcd(i,j))∑d1nϕ(d)∑i1nd∑j1nd[gcd(i,j)1]∑d1nϕ(d)∑k1ndμ(k)(⌊nkd⌋)2另tkd∑t1n(⌊nt⌋)2∑d∣tϕ(d)μ(td)另f(n)∑d∣nϕ(d)μ(nd)我们考虑如何得到这个函数的前缀和&#xff0c;显然这是一个积性函数有如下性质f(1)1f(p)ϕ(1)μ(p)ϕ…

对微软的敌视何时休? 从一篇语言评论文章对C#的评价说起

看到一篇公众号文章《2020年什么编程语言最受欢迎&#xff0c;待遇最高&#xff1f;》&#xff0c;其中对C#的描述如下&#xff1a;点击阅读原文&#xff0c;看到这是一篇翻译文章&#xff1a;https://codinginfinite.com/top-programming-languages-2020-stats-surveys/这篇文…

1 ~ n的k次方求和模板

∑i1nik\sum\limits_{i 1} ^{n} i ^ ki1∑n​ik /*Author : lifehappy */ #pragma GCC optimize(2) #pragma GCC optimize(3) #include <bits/stdc.h>using namespace std;typedef long long ll;const int inf 0x3f3f3f3f; const double eps 1e-7;const int N 1e6 …

ASP.NET Core on K8S深入学习(6)Health Check

本篇已加入《.NET Core on K8S学习实践系列文章索引》&#xff0c;可以点击查看更多容器化技术相关系列文章。预计阅读时间为10分钟。01—关于K8S中的健康监测所谓Health Check&#xff0c;就是健康检查&#xff0c;即防微杜渐。K8S是一个编排引擎可以帮助我们快捷地部署容器集…

Polynomial(2019南昌邀请赛)(拉格朗日插值)

Polynomial 思路 题目给的是一个nnn次多项式&#xff0c;要我们求∑ilr\sum\limits_{i l} ^{r}il∑r​&#xff0c;也就是一个累加的形式&#xff0c;容易想到转换成求前缀和。 所以我们考虑求前缀和&#xff0c;容易得到这个多项式的前缀和一定是≥n&≤n1\geq n \&…

P3356 火星探险问题(网络流)

P3356 火星探险问题 对于一个第一次经过会有价值&#xff0c;但是之后经过没有价值的点&#xff0c;我们的处理方法就是只连一条流量为1并且有费用的边&#xff0c;再连接流量为INF但是没有费用的边&#xff0c;这样我们要使得价值最大就会优先流有费用的边。

ASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口

目录① 存储角色/用户所能访问的 API② 实现 IAuthorizationRequirement 接口③ 实现 TokenValidationParameters④ 生成 Token⑤ 实现服务注入和身份认证配置⑥ 实现登陆⑦ 添加 API 授权策略⑧ 实现自定义授权校验⑨ 一些有用的代码① 存储角色/用户所能访问的 API例如 使用 …

EOJ Monthly 2019.11 E. 数学题(反演 + 杜教筛 + 拉格朗日插值)

EOJ Monthly 2019.11 ∑i1n∑a11i∑a21i∑a31i⋯∑ak−1i∑aki[gcd(a1,a2,a3,…,ak−1,ak,i)1]∑i1n∑d∣iμ(d)⌊id⌋k∑d1nμ(d)∑d∣i⌊id⌋k∑d1nμ(d)∑t1ndtk\sum_{i 1} ^{n} \sum_{a_1 1} ^{i} \sum_{a_2 1} ^{i} \sum_{a_3 1} ^{i} \dots \sum_{a_{k - 1}} ^{i} \s…

打表

打表 众所周知&#xff0c;打表是一项非常非常重要的技能QAQ 寻找通项&#xff1a;直接观察一元函数或者一些dp值或者sg值的规律注意二进制&#xff1a;有些规律不在10进制下&#xff0c;而是存在于二进制下观察递推关系&#xff1a;有时候无法得到有用的通项&#xff0c;但是…

关于 .Net Core runtimeconfig 文件说明

项目的bin\debug\netcoreapp${Version}下面能够找到这个${AppName}.runtimeconfig.json文件&#xff0c;简单来说&#xff0c;它就是用来定义应用程序所用的共享框架&#xff08;.Net Core App&#xff09;以及运行时选项 的一个文件。一个简单的例子{ "runtimeOptions&q…