我的Asp.net站点的异常处理机制
这两天在对以前的系统进行重构。以前是在数据层发现sql异常后,就地进行处理,跳转到出错页面。这样处理的问题是数据层的代码只能被用户请求的页面所涉及到的功能进行调用;而整个系统中中还包括一个windows服务项目,即使在网站项目内部也包括了一个后台工作线程来异步处理一些复杂请求,它们也需要使用数据层。接下来,给出自己在异常处理这个部分的重构步骤:
1.建立一个自己的异常类。有一点是我比较困惑的,如果我通过SqlCommand来执行一个查询或者更新,由于种种原因操作没有成功,抛出一个SqlException,那么在SqlException里面应当有一个属性来打印查询或更新的sql命令文本。就我个人而言,我觉得这是对开发者最有帮助的信息。虽说异常里提供了调用栈,可以帮我定位到出错的数据层、逻辑层或显示层方法,但查看这些方法的源代码、找出里面执行数据库操作的相关语句效率还是太低了。可我找了半天也没找到,如果哪位同志清楚的话能不能跟我说一下?如果确实没有这个属性,请问又是为什么呢?我自己的异常类非常简单,除了一个SqlException类型的对象,就是一个表示sql命令的文本,代码如下:
2.接下来,在数据层的相关位置截获sql异常,并封装成SqlTextException类型继续抛出,以数据层的一个方法举例如下:
1.建立一个自己的异常类。有一点是我比较困惑的,如果我通过SqlCommand来执行一个查询或者更新,由于种种原因操作没有成功,抛出一个SqlException,那么在SqlException里面应当有一个属性来打印查询或更新的sql命令文本。就我个人而言,我觉得这是对开发者最有帮助的信息。虽说异常里提供了调用栈,可以帮我定位到出错的数据层、逻辑层或显示层方法,但查看这些方法的源代码、找出里面执行数据库操作的相关语句效率还是太低了。可我找了半天也没找到,如果哪位同志清楚的话能不能跟我说一下?如果确实没有这个属性,请问又是为什么呢?我自己的异常类非常简单,除了一个SqlException类型的对象,就是一个表示sql命令的文本,代码如下:
1public class SqlTextException : Exception
2 {
3 private SqlException baseException = null;
4 private string sqlText = null;
5
6 public SqlTextException(SqlException e, string sql)
7 {
8 baseException = e;
9 sqlText = sql;
10 }
11
12 public SqlException BaseException
13 {
14 get
15 {
16 return baseException;
17 }
18 }
19
20 public String SqlText
21 {
22 get
23 {
24 return sqlText;
25 }
26 }
27 }
2 {
3 private SqlException baseException = null;
4 private string sqlText = null;
5
6 public SqlTextException(SqlException e, string sql)
7 {
8 baseException = e;
9 sqlText = sql;
10 }
11
12 public SqlException BaseException
13 {
14 get
15 {
16 return baseException;
17 }
18 }
19
20 public String SqlText
21 {
22 get
23 {
24 return sqlText;
25 }
26 }
27 }
2.接下来,在数据层的相关位置截获sql异常,并封装成SqlTextException类型继续抛出,以数据层的一个方法举例如下:
1public Object ExecuteScalar(string cmdText, SqlParameter[] sqlParams)
2{
3 if (cmdText == null)
4 return null;
5
6 Object result = null;
7 SqlCommand command = this.PrepareCommand(cmdText, sqlParams);
8
9 try {
10 connection.Open();
11 result = command.ExecuteScalar();
12 }
13 catch (SqlException e) {
14 SqlTextException myE = new SqlTextException(e, cmdText);
15 throw myE;
16 }
17 finally {
18 connection.Close();
19 }
20 return result;
21}
2{
3 if (cmdText == null)
4 return null;
5
6 Object result = null;
7 SqlCommand command = this.PrepareCommand(cmdText, sqlParams);
8
9 try {
10 connection.Open();
11 result = command.ExecuteScalar();
12 }
13 catch (SqlException e) {
14 SqlTextException myE = new SqlTextException(e, cmdText);
15 throw myE;
16 }
17 finally {
18 connection.Close();
19 }
20 return result;
21}
3.异常被创建了,也从数据层被抛出了,接下来就是对异常的处理了。正如随笔开头所说的那样,数据层会在两种运行环境中被用到,一种我们姑且称之为同步Web环境,这种环境的特点有3:(1)程序的运行是由客户端对服务器端页面的请求直接引发的;(2)当前线程位于w3wp工作进程管理的线程池中;(3)用户正在客户端等待页面刷新。而另一种运行环境,要么代码是在服务器端的后台运行(windows服务),要么代码是另起一个工作线程在后台处理(任务队列),我们姑且统称为非Web环境。下面就对这两种环境分别叙述。
同步Web环境在整个系统中占主要地位,如果在所有可能抛出异常的方法里都进行异常的捕获和处理,工作量将十分巨大。还好,ASP.NET已经为我们定义了一个很好的异常处理结构(具体可参见msdn),我们可以在某个时刻(具体来说,就是Page_Error事件和Application_Error事件中)对页面或者整个应用程序的未处理异常(异常抛出时,CLR会向上遍历整个调用栈来查看与被抛出异常对象类型相匹配的catch块;若未找到这样的catch块,就会出现一个unhandled exception)统一进行处理。最后我的实现是使用了Application_Error事件,即在Global.ascx中加入如下代码段:
1void Application_Error(object sender, EventArgs e)
2{
3 Exception objErr = Server.GetLastError().GetBaseException();
4 GEA52.Rule.SqlBase.SqlTextException myExp = objErr as GEA52.Rule.SqlBase.SqlTextException;
5 if (myExp != null)
6 {
7 string err = "引发错误的sql语句为:\n" + myExp.SqlText +"\n; 系统返回的出错信息:"+myExp.BaseException.Message;
8 Server.ClearError();
9 GEA52.Rule.CommonRule.ShowError(err);
10 }
11 else
12 {
13 string err = "在Application_Error事件中捕获异常:\n" +
14 "Error in:" + Request.Url.ToString() +
15 "\nError Message:" + objErr.Message.ToString() +
16 "\nStack Trace1:" + objErr.StackTrace.ToString();
17
18 Server.ClearError();
19 GEA52.Rule.CommonRule.ShowError(err);
20 }
21}
2{
3 Exception objErr = Server.GetLastError().GetBaseException();
4 GEA52.Rule.SqlBase.SqlTextException myExp = objErr as GEA52.Rule.SqlBase.SqlTextException;
5 if (myExp != null)
6 {
7 string err = "引发错误的sql语句为:\n" + myExp.SqlText +"\n; 系统返回的出错信息:"+myExp.BaseException.Message;
8 Server.ClearError();
9 GEA52.Rule.CommonRule.ShowError(err);
10 }
11 else
12 {
13 string err = "在Application_Error事件中捕获异常:\n" +
14 "Error in:" + Request.Url.ToString() +
15 "\nError Message:" + objErr.Message.ToString() +
16 "\nStack Trace1:" + objErr.StackTrace.ToString();
17
18 Server.ClearError();
19 GEA52.Rule.CommonRule.ShowError(err);
20 }
21}
非Web环境可就没那么幸运了,我还没能找到一个统一的未处理异常处理位置(哪位兄弟知道,望不吝赐教^_^)。好在这部分代码不多,我在一些主要的方法调用处进行了异常捕获,并把捕获后的异常存储数据库(在数据库里建了ErrorLog表,表包含Message,Source,TargetSite,StackTrace等列来记录异常的一些主要属性),这一部分就不赘述了。