dotNET:怎样处理程序中的异常(实战篇)?

在上篇 《dotNET:怎样处理程序中的异常(理论篇)》 中讲了一些程序中出现异常怎样处理的理论知识,本文将以代码的方式来进行实践。

环境

  • dotNET Core:3.1

  • 工具:Rider 2019.3.2

  • 系统:macOS 10.15.4

创建项目

在 Rider 中创建示例项目 ExceptionDemo ,该项目为 dotNET Core 3.1 的 WebAPI 项目,为了演示方便,不同层级以目录的方式放在了一个项目中,创建好的项目目录结构如下:

  • Controllers

    • UserController:操作用户的控制器

  • CustomExceptions

    • UserNotFoundException:用户不存在的自定义异常类

  • Filters

    • CustomerExceptionAttribute:异常结果处理过滤器

    • ResultFilterAttribute:普通结果处理过滤器

  • Models

    • CustomExceptionResult:异常返回的处理类

    • CustomExceptionResultModel:异常内容的模型类

    • DataResult:普通结果的返回处理类

    • DataResultModel:普通结果的内容模型类

    • MessageResult:消息结果的返回处理类

    • MessageResultModel:消息结果的内容模型类

    • ResultModelBase:返回结果内容模型的基类

    • User:示例中用户的实体类

  • Repositories

    • IUserRepository:用户操作数据库的接口

    • UserRepository:用户操作数据库的实现类

  • Services

    • IUserService:用户业务层的接口

    • UserService:用户业务层的实现类

结果的返回

接口的返回可以归纳为三种情况:

  • 正常的请求数据的返回

  • 通过判断需要返回一些消息给前端进行提示

  • 异常的返回

所以上面定义了 DataResult、MessageResult 和 CustomExceptionResult 相关类来进行这三种情况的封装。

这三个类都继承 ResultModelBase 类,ResultModelBase 类中只定义了 Code

public class ResultModelBase
{public int? Code { get; set; }
}

DataResultModel 类用属性 Data 来包装返回结果

public class DataResultModel:ResultModelBase
{public DataResultModel(object data,int? code = 200){Code = code;Data = data;}public object Data { get; set; }
}

MessageResultModel 类使用属性 Message 类返回消息文本

public class MessageResultModel:ResultModelBase
{public MessageResultModel(string massage,int? code = 200){Code = code;Message = massage;}public string Message { get; set; }
}

CustomExceptionResultModel 类中可以传入 Exception 类型和定义一些其他的相关属性

public class CustomExceptionResultModel:ResultModelBase
{public CustomExceptionResultModel(Exception exception,int? code = 500){Code = code;Reason = exception.InnerException != null ?exception.InnerException.Message :exception.Message;}public string Reason { get; set; }
}

DataResult、MessageResult 和 CustomExceptionResult 类都是继承自ObjectResult,将相对应的 Model 类包装后通过构造函数赋值给 ObjectResult 的 Value 属性,用于最后的结果返回。

public class DataResult: ObjectResult
{public DataResult(object data , int? code=200 ): base(new DataResultModel(data,code)){StatusCode = 200;}
}
public class MessageResult:ObjectResult
{public MessageResult(string message, int? code=200 ): base(new MessageResultModel(message,code)){StatusCode = 200;}
}
public class CustomExceptionResult:ObjectResult
{public CustomExceptionResult(Exception exception,HttpStatusCode statusCode,  int? code=500 ): base(new CustomExceptionResultModel(exception,code)){StatusCode = (int)statusCode;}
}

使用两个过滤器对返回结果进行处理

public class CustomerExceptionAttribute: IExceptionFilter
{public void OnException(ExceptionContext context){HttpStatusCode status = HttpStatusCode.InternalServerError;int code = (int) status;//处理各种异常if (context.Exception is UserNotFoundException){code = 500001;}context.Result = new CustomExceptionResult(context.Exception,status ,code);context.ExceptionHandled = true;}
}public class ResultFilterAttribute:ActionFilterAttribute
{public override void OnResultExecuting(ResultExecutingContext context){var objectResult = context.Result as ObjectResult;if (objectResult?.Value == null){context.Result=new NotFoundObjectResult(new MessageResult("未找到资源"));}if (context.Result is MessageResult){context.Result = new MessageResult(objectResult.Value.ToString());}else if (context.Result is OkObjectResult || context.Result is ObjectResult){context.Result = new DataResult(objectResult.Value);}}
}

用户添加接口

在 UserRepository 中添加 AddUser 方法

public User AddUser(User user)
{int id=_users.OrderByDescending(x => x.Id).First().Id + 1;user.Id = id;_users.Add(user);return user;
}

示例中没有实际操作数据库,_users 是一个 List对象,当 _users 为 Null 或内容为空时,_users.OrderByDescending(x => x.Id).First() 的执行就会报错,空对象的问题在实际程序中无处不在,修改后的代码如下:

public User AddUser(User user)
{int id = 1;if (_users.Any()){id=_users.OrderByDescending(x => x.Id).First().Id + 1;}user.Id = id;_users.Add(user);return user;
}

在 Controller 层的 AddUser 方法也需要对入参实体进行检查

[HttpPost]
public User AddUser(User user)
{return _userService.AddUser(user);
}public class User
{public int Id { get; set; }[Required(ErrorMessage = "用户名不能为空")]public string Name { get; set; }[Required(ErrorMessage = "用户编码不能为空")]public string Code { get; set; }
}

实际情况下接口层的入参实体和底层的数据实体需要分开,然后使用 AutoMapper 之类的映射工具进行转换,本示例中使用了同一个 User 。

使用 Postman 进行调用,当 Name 或 Code 为空时,结果如下:

默认的返回结果格式和上面定义的统一的格式有些区别,大家可以思考下,怎样使用过滤器的方式将参数验证的返回信息进行统一输出。

根据 Id 获取用户的名称

在 UserRepository 中有根据 Id 获取 User 对象的方法

public User GetUserById(int id)
{return _users.Find(x => x.Id == id);
}

在 UserService 中添加 GetUserName 方法获取名称

public string GetUserName(int id)
{User user=_userRepository.GetUserById(id);if (user == null){throw new UserNotFoundException($"用户id:{id} 在数据库不存在" );}return user.Name;
}

当通过 id 找不到 User 对象时,可以抛出 UserNotFoundException 异常,如果只是对 user 对象进行 Null 判断然后返回一个空字符,就弄不清楚是 user 对象不存在还是用户名为空。

获取用户全名

下面用一个获取用户全名(包含部门)的业务来模拟异常的重新包装,部门操作的相关类就不在赘述了,可以在文章最下方的链接中查看源码。

UserController 中添加了接口方法

[HttpGet("{id}")]
public string GetFullName(int id)
{return _userService.GetFullName(id);
}

UserService 中添加 GetFullName 方法

public string GetFullName(int id)
{try{User user = GetUserById(id);string deptName = _deptService.GetDeptName(user.ParentId);//处理其他逻辑return $"{user.Name}[{deptName}]";}catch (Exception e){throw new UserFullNameGenException($"用户 Id 为 {id} 的 FullName 生产失败",e);}
}
  • GetUserById 方法和 _deptService.GetDeptName 方法中都可能抛异常,在上次可以捕获异常然后抛出符合当前业务的 UserFullNameGenException 异常;

  • 捕获的异常 e 作为 UserFullNameGenException 异常的 InnerException 传入,这样如果层级比较多,通过 InnerException 就可以追溯到最底层的原因。

当输入参数为用户不存在的时候调用结果如下:

当输入参数为用户的部门不存在时调用结果如下:

  • 通过二次捕获提示的错误信息是跟当前业务有关的,可以更容易定位问题,更底一层的原因可以在 InnerException 中获取;

  • 两次异常是不同原因造成的,但对于这个业务来说就是获取 FullName 失败,返回的错误码也是一致的 500100 ;

  • 因为有了二次捕获,异常堆栈信息中只能定位到最上层捕获异常的地方,如果需要知道更底层的异常堆栈,可以将 InnerException 的堆栈信息进行合并。

最后

本文以一个简单的示例演示了代码中异常的处理,但重要的不是编码而是处理问题的思路。具体应该怎么做还是需要结合当前的上下文。希望本文对您有所帮助。

示例源码:https://github.com/oec2003/DotNetCoreThreeAPIDemo/tree/master/ExceptionDemo

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

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

相关文章

在 MySQL 中使用码农很忙 IP 地址数据库

在下载到码农很忙 IP 地址数据库后,我们可以将其存储在 MySQL 数据库中,并在需要查询某个 IP 对应的位置数据时,通过 SQL 语句获取正确的结果。这是一种很便捷的使用方式,并且在增加了恰当的索引后,可以取得不错的搜索…

java numberformat异常_Java NumberFormat格式化float类型的bug

首先,这个NumberFormat这个类,可以格式化各种数字。你只要稍微设置一下,结果还是很理性的。但是,他有那么一丢丢的bug,不知道你知道不?/*** 2.3F经过格式化,竟然变成2.99啦。what the fuck .* f…

7-24 树种统计 (25 分)(详解)map做法 map真香啊!

一:题目 7-24 树种统计 (25 分)随着卫星成像技术的应用,自然资源研究机构可以识别每一棵树的种类。请编写程序帮助研究人员统计每种树的数量,计算每种树占总数的百分比。 输入格式: 输入首先给出正整数N(≤10 ​5 ​​ &#xf…

死磕 Redis,我有这么几招

如果你是一位后端工程师,面试时八成会被问到 Redis,特别是那些大型互联网公司,不仅要求面试者能简单使用 Redis,还要深入理解其底层实现原理,具备解决常见问题的能力。可以说,熟练使用 Redis 就是后端工程师…

回归统计在DMP中的实战应用

源宝导读:本文将讲解在大数据分析领域的线性回归统计计算方法,以及如何将非线性转化为线性回归的原理,同时介绍了两种的回归统计库的使用和对比,最后介绍线性回归在DMP产品的应用实践。一、背景回归统计,是数据分析常用…

龙芯3A5000初样顺利交付流片

此前,龙芯完成3A5000设计初样的流片交付。在3A4000架构的基础上,3A5000采用12纳米工艺,设计频率提高近40%,同频模式下功耗降低近60%,同时保持与3A4000芯片管脚兼容。龙芯3A5000和3A4000在微结构上变化不大,…

redhat java 多个版本_Linux下安装JDK(多个版本) 切换

1、检查系统是否自带了OpenJDK以及相关安装包,如果有的话则应先将其卸载。检查命令:java -versionrpm -qa | grep javarpm -e --nodeps tzdata-java-2013g-1.el6.noarchrpm -e --nodeps java-1.6.0-openjdk-1.6.0.0-1.66.1.13.0.el6.i686rpm -e --nodeps…

十分钟搭建自己的私有NuGet服务器-BaGet

点击上方蓝字"小黑在哪里"关注我吧搭建BaGet上传程序包在vs中使用其他前言NuGet是用于微软.NET(包括 .NET Core)开发平台的软件包管理器。NuGet能够令你在项目中添加、移除和更新引用的工作变得更加快捷方便。通常使用NuGet都是官方的服务&…

mysql or 创建索引_Mysql索引优化

1、单表索引优化单表索引优化分析创建表建表 SQLCREATE TABLE IF NOT EXISTS article(id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,author_id INT(10) UNSIGNED NOT NULL,category_id INT(10) UNSIGNED NOT NULL,views INT(10) UNSIGNED NOT NULL,comments INT(1…

.Net Core HttpClient处理响应压缩

前言在上篇文章[ASP.NET Core中的响应压缩]中我们谈到了在ASP.NET Core服务端处理关于响应压缩的请求,服务端的主要工作就是根据Content-Encoding头信息判断采用哪种方式压缩并返回。之前在群里有人问道过,现在的网络带宽这么高了还有必要在服务端针对请…

mysql脚本的制作_制作脚本实现mysql自动备份

首先执行vi dbbackup.sh命令,在打开的编辑器输入:#!/bin/bash/usr/local/mysql/bin/mysqldump -uuser -ppasswd databasename > /home/wwwroot/backup/date_$(date%Y%m%d).sql这段命令的意思是:用mysqldump导出名为databasename的数据库到…

在 PostgreSQL 中使用码农很忙 IP 地址数据库

在下载到码农很忙 IP 地址数据库后,我们可以将其存储在 PostgreSQL 数据库中,并在需要查询某个 IP 对应的位置数据时,通过 SQL 语句获取正确的结果。这是一种很便捷的使用方式,并且在增加了恰当的索引后,可以取得不错的…

Java当中用 javabean和其他容器存入表格数据 或 利用 容器进行存储表格

一:javabean 和list容器或map容器 package cn.wyj.two;import java.util.*;/*** javabean :必须有一个无参构造函数;变量属性私有化;* 本篇还是打印一张表* author 86155**/ public class Demo13_Javabean和其他容器 {public static void ma…

7-25 朋友圈 (25 分)(详解+并查集的了解和应用)

一:题目 某学校有N个学生,形成M个俱乐部。每个俱乐部里的学生有着一定相似的兴趣爱好,形成一个朋友圈。一个学生可以同时属于若干个不同的俱乐部。根据“我的朋友的朋友也是我的朋友”这个推论可以得出,如果A和B是朋友&#xff0…

使用Azure人脸API对图片进行人脸识别

人脸识别是人工智能机器学习比较成熟的一个领域。人脸识别已经应用到了很多生产场景。比如生物认证,人脸考勤,人流监控等场景。对于很多中小功能由于技术门槛问题很难自己实现人脸识别的算法。Azure人脸API对人脸识别机器学习算法进行封装提供REST API跟…

java while do循环_c语言中,while 和 do while 循环的主要区别是( )

1、循环构造的表达式不同:while循环构造的表达式为:while(表达式){循环体}。do-while循环构造表达式为:do{循环体;}while(条件表达);。2、执行末尾循环体的次…

[NewLife.Net]单机400万长连接压力测试

目标对网络库NewLife.Net进行单机百万级长连接测试,并持续收发数据,检测网络库稳定性。【2020年8月1日晚上22点】先上源码:https://github.com/NewLifeX/NewLife.Net结论,8月1日晚达到200万,8月2日下午达到404万。上一…

ABP快速开发一个.NET Core电商平台

总听.NETer羡慕Java有SSM框架,其实.NET也有ABP,极度优秀的开源应用程序框架,支持.NET Framework和.NET Core。羡慕Java有SpringCloud,其实.NET也有ABP.vNext,由ABP团队全新打造的.NET Core微服务架构开源框架&#xff…

java swing 控件拖动_java swing中实现拖拽功能示例

java实现拖拽示例Swing中实现拖拽功能,代码很简单,都有注释,自己看,运行效果如下图:package com;import java.awt.*;import java.awt.datatransfer.DataFlavor;import java.awt.dnd.DnDConstants;import java.awt.dnd.…

7-26 Windows消息队列 (25 分)(详解+思路+超时解决)

一:题目 消息队列是Windows系统的基础。对于每个进程,系统维护一个消息队列。如果在进程中有特定事件发生,如点击鼠标、文字改变等,系统将把这个消息加到队列当中。同时,如果队列不是空的,这一进程循环地从…