循序渐进学.Net Core Web Api开发系列【14】:异常处理
系列目录
本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi
一、概述
本篇介绍异常处理的知识。由于异常处理的技术应用并不复杂,本篇更多讨论异常处理的一些理论知识,包括一些原则、约定和建议。
二、异常处理的基本原则
在Win32API编程中是没有异常处理机制的,函数一般都是通过返回一个BOOL型的状态码来表达处理是否成功,比如需要通过ID取得一个实体信息,需要这样定义:
BOOL GetArticleByID(string ID,out Article article);
当调用失败时(函数返回false),其实调用者是不知道失败的原因的,如果需要知道原因,那就要返回一个int类型来表达状态,-1表示成功,其他都是错误码,这种函数对调用者而言简直是噩梦。
.NET Framework中采用异常处理机制后,情况就好多了,上面的方法定义如下:
Article GetArticleByID(string ID);
看到这样的定义,基本上不要看文档也能明白这个方法的含义,另外所有可能失败的情况都通过异常来进行报告。
所以,对于调用者而言,所有与期望不符的结果都可以认为是“异常”。
对于异常的处理,有几个基本原则:
1、只处理(catch)预计可能会发生的异常
在代码中,我们只处理我们预计可能会发生的异常,比如要把一个字符串转换为数字,我们预计可能会发生FormatException异常,那么我们就Catch该异常,并提供处理办法。
这里的异常应该是我们有能力处理的,其实每一行代码我们都预计可能发生OutMemoryException的异常 ,但这个异常发生时,应用是没有能力处理的,请不要catch它。
2、绝对不要catch根异常Exception
这个原则和上面的原则其实是很类似的,catch了根异常表示你有能力处理所有未知异常,而且以同一种方式来处理,显然是不合适的。
由于考虑不周,我没有考虑到某个异常,又不允许我catch根异常,实际运行时应该果然报了一个之前没有预料的异常怎么办?很简单,把这个异常加上就可以了。发生这种事情是因为编程者的经验不足造成的,不能因为这个原因破坏异常处理的原则。
3、如果方法还有调用者,应该对异常进行封装
如果我们是写类库相关的代码,主要是提供服务给消费者调用的,最好对捕捉到的异常进行封装,给出和调用者重新约定的异常类型。比如我们在DAO层把所有捕捉到的异常处理完成后重新抛出一个DBOperateException,并提供相关信息。Control层在调用DAO时相对就简单了,只需处理DBOperateException并把信息(或处理过的信息)报告给View就可以了。
下面我们会以一些实例描述我们是如何遵守和打破这些原则的。
三、在WebApi开发中的异常处理
我们要设计一个Controller,实现通过ID来获取实例对象的功能,由于异常无法通过Http协议进行传送,所以我们定义了一个ResultObject的返回类型,用于向客户端传送调用结果。
public class ResultObject
{
public ResultObject()
{
state = ResultState.Success;
ExceptionString = "";
result = null;
} public ResultState state { get; set; }
public String ExceptionString { get; set; } public Object result { get; set; }
} public enum ResultState
{
Success,
Exception
}
具体的Controller设计如下:
public ResultObject GetArticleByID(string id)
{
try
{
int idn = int.Parse(id); Article article = _context.Articles
.AsNoTracking()
.Where(a => a.ID == id)
.Single(); return new ResultObject
{
result = article
};
}
catch (System.FormatException ex)
{
_logger.LogError(ex.Message + "\n" + ex.StackTrace); return new ResultObject
{
state = ResultState.Exception,
ExceptionString = "id必须为数字"
};
}
catch (System.InvalidOperationException ex)
{
_logger.LogError(ex.Message + "\n" + ex.StackTrace); return new ResultObject
{
state = ResultState.Exception,
ExceptionString = "未查询到预料的数据"
};
}
catch(MySql.Data.MySqlClient.MySqlException ex)
{
_logger.LogError(ex.Message + "\n" + ex.StackTrace); return new ResultObject
{
state = ResultState.Exception,
ExceptionString = "数据库异常"
};
}
}
对于上述代码,我们预料到可能用户会输入字符串而不是数字,也能预料到可能查询不到结果,所以就截获了这两个异常。对于ToList这样的操作,没有查询到数据会返回NULL,不会报异常,所以就不应该catch InvalidOperationException。另外,我们可能预料到会发生无法连接数据库的异常,在此也处理了,由于数据库连接异常可能在每个方法调用时都可能发生。建议提供为统一异常处理。
四、全局未处理异常
设计一个全局异常处理的中间件:
public class UnifyExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger; public UnifyExceptionMiddleware(RequestDelegate next, ILogger<UnifyExceptionMiddleware> logger)
{
_next = next;
_logger=logger;
} public async Task Invoke(HttpContext context)
{
ResultObject result =null; try
{
await _next(context);
}
catch(MySql.Data.MySqlClient.MySqlException ex)
{
_logger.LogError(ex.Message + "\n" + ex.StackTrace); result = new ResultObject
{
state = ResultState.Exception,
ExceptionString = "数据库异常"
};
}
catch(Exception ex)
{
_logger.LogError($"系统发生未处理异常:{ex.StackTrace}"); result = new ResultObject
{
state = ResultState.Exception,
ExceptionString = "系统发生未处理异常"
};
} context.Response.StatusCode = ;
context.Response.ContentType = "application/json; charset=utf-8";
context.Response.WriteAsync(JsonConvert.SerializeObject(result));
}
} public static class VisitLogMiddlewareExtensions
{
public static IApplicationBuilder UseUnifyException(this IApplicationBuilder builder)
{
return builder.UseMiddleware<UnifyExceptionMiddleware>();
}
}
使用该中间件
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddNLog();
app.UseUnifyException();
app.UseMvcWithDefaultRoute();
}
}
异常处理的中间件要放在MVC中间件之前,这样就可以截获Contriller内的未处理异常。
五、两点思考
1、为什么我们处理了根异常Exception
前面提到不要处理根异常,但这里却处理了,这是什么情况?我们说不要处理根异常,是因为不希望某个方法掩盖了问题,向上级报告一个虚假的状态,但对于所有处理流程的最上级,可以适当违反该原则。
就应用程序而言,当发生未处理异常时,操作系统会接管该异常的处理,这是微软推荐的做法,但我们还是常常会进行全局未处理异常的处理,弹出一个用户看得懂的提示框,并登记一个异常报告。
对于WebApi而言,接口并不直接面对用户,但由于异常机制无法通过Http协议进行传输,接口的调用者就是WebApi的最终用户了,所有可以对根异常进行捕获。
这里有两种选择:
1)不捕获根异常,出现未处理异常时,向调用者报500;
2)捕获根异常,出现未处理异常时,向调用者报200,同时报告异常内容。
具体如何选择,就不是一个技术问题了,主要看团队的管理规定与约定。某些公司规定接口是不允许报500的,否则是要扣绩效的,那只能捕获根异常了,毕竟绩效最重要对吧。
2、异常发生时,应该报告给客户端什么样的状态码?
我们和前端约定使用ResultObject来返回调用状态和结果,对于发生“异常”时应该返回什么样的状态码比较合适呢,这大致也有两种选择:
1)一律返回200,通过ResultObject报告接口,字段不够可以增加信息字段;
2)通过状态码返回一些特殊的异常,比如:找不到资源返回404,认证失败返回401等,未知异常报500等等。
对于WebApi而言推荐使用第一种模式。
附:Http Response 返回码
HTTP协议状态码表示的意思主要分为五类,大体是:
|
1×× |
保留 |
|
2×× |
表示请求成功地接收 |
|
3×× |
为完成请求客户需进一步细化请求 |
|
4×× |
客户错误 |
|
5×× |
服务器错误 |
列举一些常见的状态码:
200 OK 指示客服端的请求已经成功收到,解析,接受。
401 Unauthorized 如果请求需要用户验证。回送应该包含一个WWW-Authenticate头字段用来指明请求资源的权限。
403 Forbidden 服务器接受请求,但是被拒绝处理。
404 Not Found 服务器已经找到任何匹配Request-URI的资源。
500 Internal Server Error 服务器遭遇异常阻止了当前请求的执行。
502 Bad Gateway 无效网关。
503 Service Unavailable 因为临时文件超载导致服务器不能处理当前请求。
循序渐进学.Net Core Web Api开发系列【14】:异常处理的更多相关文章
- 循序渐进学.Net Core Web Api开发系列【0】:序言与目录
一.序言 我大约在2003年时候开始接触到.NET,最初在.NET framework 1.1版本下写过代码,曾经做过WinForm和ASP.NET开发.大约在2010年的时候转型JAVA环境,这么多 ...
- 循序渐进学.Net Core Web Api开发系列【16】:应用安全续-加密与解密
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 应用安全除 ...
- 循序渐进学.Net Core Web Api开发系列【15】:应用安全
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇介绍W ...
- 循序渐进学.Net Core Web Api开发系列【13】:中间件(Middleware)
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇介绍如 ...
- 循序渐进学.Net Core Web Api开发系列【12】:缓存
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇介绍如 ...
- 循序渐进学.Net Core Web Api开发系列【11】:依赖注入
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇介绍如 ...
- 循序渐进学.Net Core Web Api开发系列【10】:使用日志
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.本篇概述 本篇介 ...
- 循序渐进学.Net Core Web Api开发系列【9】:常用的数据库操作
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇描述一 ...
- 循序渐进学.Net Core Web Api开发系列【8】:访问数据库(基本功能)
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇讨论如 ...
随机推荐
- linux driver error ------ 编译驱动出现 ERROR: Kernel configuration is invalid
ERROR: Kernel configuration is invalid. include/generated/autoconf.h or include/config/au ...
- python爬虫 scrapy3_ 安装指南
安装指南 安装Scrapy 注解 请先阅读 平台安装指南. 下列的安装步骤假定您已经安装好下列程序: Python 2.7 Python Package: pip and setuptools. ...
- JVM调优命令-jstat
JVM Statistics Monitoring Tool,是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载.内存.垃圾收集.JIT编译等运行数据.[性能分析] 命令格式 1 ...
- 51Nod 1684 子集价值 (平方和去括号技巧)
http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1684 题意: 新建一个位运算,求所有子集通过这个位运算后的答案的平方和是 ...
- [iOS]Xcode+GitHub远程代码托管(GIT, SVN)
先来看看什么是代码远程托管: 其实就是将我们的代码上传到GitHub的服务器上, 供别人下载, 当然了也可以在团队开发的时候, 使用GitHub进行代码合并工作, 下面我们进入正题 (已经有远程仓库的 ...
- css框架,一把锋利的剑
CSS 框架是一系列 CSS 文件的集合体,包含了基本的元素重置,页面排版.网格布局.表单样式.通用规则等代码块,用于简化web前端开发的工作,提高工作效率. 产生原因 互联网行业已经发展了多年,浏览 ...
- Django Book 学习笔记(上)
拜读了网上的Django Book,现在来总结一下吧...... 一.Django的配置 非常的蛋疼,由于Django的块组之间耦合度低,这既是它的优点,也是它的缺点.我在Ubuntu所配置的Djan ...
- js基础知识:闭包,事件处理,原型
闭包:其实就是js代码在执行的时候会创建变量对象的一个作用域链,标识符解析的时候会沿着作用域链一级一级的网上搜索,最后到达全局变量停止.所以某个函数可以访问外层的局部变量和全局变量,但是访问不了里层的 ...
- c语言.函数指针数组
函数指针: 一个指向函数的指针.一般用函数名表示. 函数指针数组:元素为函数指针的数组.转移表.c语言中函数不可以定义为数组,只能通过定义函数指针来操作. #include<stdio.h> ...
- 20155303 2016-2017-2 《Java程序设计》课程总结
20155303 2016-2017-2 <Java程序设计>课程总结 目录 一.每周作业及实验报告链接汇总 二.关于博客 自认为写得最好一篇博客是?为什么? 作业中阅读量最高的一篇博客是 ...