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

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

  • 文档

  • 参数验证

  • 返回值

  • 异常处理

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

环境

dotNet Core:2.1

文档

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

安装

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

搜索 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 ,效果如下

参数验证

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

简单实现

创建名为 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 调用结果如下

返回值

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

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

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

  • 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 调用该接口方法,返回结果如下

继续重构参数验证

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

{"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、调用参数验证接口结果如下

异常处理

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

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 项目,这些基础处理是必不可少的,有了这些做保障才能专注于业务代码的编写。

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

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

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

  • 怎样做接口的验证?

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

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

  1. asp.net core webapi 统一处理返回值、异常和请求参数验证

    现在的开发模式很少用asp.net mvc一个项目直接操作界面和数据库了.大部分都使用前后端分离,更多的是为了让API支持移动端. 后端写webapi的时候必然需要和前端约定请求值和返回值的格式,如果 ...

  2. 使用 @ControllerAdvice 和 实现ResponseBodyAdvice接口, 拦截Controller方法默认返回参数,统一处理返回值/响应体

    使用 @ControllerAdvice 和 实现ResponseBodyAdvice接口, 拦截Controller方法默认返回参数,统一处理返回值/响应体 1.Controller代码 以下是Co ...

  3. ASP.NET Core搭建多层网站架构【11-WebApi统一处理返回值、异常】

    ASP.NET Core搭建多层网站架构[11-WebApi统一处理返回值.异常] 参考文章: (1)ASP.NET Core搭建多层网站架构[11-WebApi统一处理返回值.异常] (2)http ...

  4. dotnet core webapi +vue 搭建前后端完全分离web架构(一)

    架构 服务端采用 dotnet core  webapi 前端采用: Vue + router +elementUI+axios 问题 使用前后端完全分离的架构,首先遇到的问题肯定是跨域访问.前后端可 ...

  5. 函数概念 返回值 参数

    一.函数概念 1.什么是函数函数就是具有某个具体功能的工具 2.为什么要用函数提供开发效率减少代码冗余提高程序的扩展性 3.定义一个函数def是定义函数的关键字,函数在定义的时候只检测函数体语法 不执 ...

  6. C语言函数无返回值 参数

    void的两种说法: 1.void代表无返回值,不需要return 2.void代表返回值的类型是无类型,return要写但后面不加变量 形式参数,意思有点类似变量定义,写法类似变量的定义 如果调用的 ...

  7. 检索方法应该返回#39;null#39;还是无法产生返回值时引发异常? [关闭]

    已关闭 . 这个问题是 基于观点的 . 它当前不接受答案. 想改善这个问题吗? 更新问题,以便通过编辑此帖子以事实和引用的形式回答. 3年前关闭. 我有一种方法,应该在找到对象后返回它. 如果找不到, ...

  8. dotNET Core 3.X 请求处理管道和中间件的理解

    理解 dotNET Core 中的管道模型,对我们学习 dotNET Core 有很大的好处,能让我们知其然,也知其所以然,这样在使用第三方组件或者自己写一些扩展时,可以避免入坑,或者说避免同样的问题 ...

  9. (转)C#进阶系列——WebApi 接口返回值不困惑:返回值类型详解

    原文链接:https://www.cnblogs.com/landeanfen/p/5501487.html 阅读目录 一.void无返回值 二.IHttpActionResult 1.Json(T ...

最新文章

  1. Java项目:疫情人员流动管理系统(java+JSP+SSM+Springboot+maven+Mysql)
  2. java IO(输入输出) 字节流
  3. 【转载】静态时序分析
  4. 6 个步骤,教你在Ubuntu虚拟机环境下,用Docker自带的DNS配置Hadoop | 附代码
  5. html扇形调节角度,CSS如何实现任意角度的扇形(代码示例)
  6. Cisco IOS Unicast NAT 工作原理 [一]
  7. Redis__WindowsServer主从服务部署及调用实例
  8. 200行Python实现简单的区块链系统
  9. python爬虫十二种方法_Python爬虫的N种姿势
  10. 名为 cursor_jinserted 的游标不存在_一个工程师必须了解的测量常识,你不知道怎么行...
  11. QObject类 moc处理后代码
  12. Python 身份证校验代码
  13. 浙江旅行新地标!图卷9号与法国著名建筑大师安东尼·贝叙共同打造
  14. 在excel筛选出某一列多个重复值
  15. 湖北武汉劳务员证书劳务人员实名制管理的现状建筑七大员培训
  16. android中怎么播放本地视频播放器,安卓之播放本地视频讲解
  17. 当易方达张坤遇招商白酒侯昊
  18. matlab曲面拟合的算法,用Matlab 实现移动曲面拟合法生成DEM
  19. PHP使用ffmpeg压缩视频
  20. 杭电1856——并差集

热门文章

  1. 基于Visual C++2010与windows SDK fo windows7开发windows7平台的tabletpc应用(1)-手写数学公式输入...
  2. 智能家居设备_您的智能家居设备正在监视您吗?
  3. CSS text-indent 属性
  4. VMware虚拟机VMDK 快照 数据恢复成功
  5. 4项技巧使你不再为PHP中文编码苦恼
  6. php中使用exec,system等函数调用系统命令
  7. 我对CTO的理解 CTO要有技术魅力[转载]
  8. 【组图】地震前线归来--心中的震撼
  9. 祝贺 在线文件管理系统 访问量 超过500
  10. Dapr + .NET Core实战(三)状态管理