三层架构中的异常处理






4.76/5 (59投票s)
使用 Enterprise Library 在三层架构中处理异常
引言
我们很早以前就想研究如何使用企业库(Enterprise Library)在三层架构中进行异常处理,但一直没有时间,也没有遇到必须这样做的场景。最终,我们得到了一个机会来实践它。
我们的主要目标是在三层架构中无缝地处理异常。我们查阅了互联网,但没有找到能满足我们需求的合适代码示例。因此,我们决定编写一个可重用的代码,以方便自己和他人。以下是我们认为异常处理应该实现的功能:
场景 | 描述 | 预期结果 |
1 | 应用程序调用了一个存储过程。 | 正常情况;一切都应正常工作。 |
2 | 数据库管理系统引发异常,例如主键冲突或对象不存在等。 | 系统应将抛出的异常记录到一个接收器(文本文件)中。用用户友好的消息替换该异常,并通过业务逻辑层将其传播到UI。 |
3 | 基于某些验证检查/业务规则,从存储过程中引发一个自定义错误。 | 抛出的自定义错误消息应传播到UI,而无需记录日志,因为我们假设这些错误消息是某种替代流程,而非真正的异常。 |
4 | 在处理请求时,业务逻辑层发生错误,例如除零、对象未设置、空引用异常等。 | 系统应将抛出的异常记录到一个接收器(文本文件)中。用用户友好的消息替换该异常,并将其传播到UI。 |
5 | 基于某些业务验证检查,从业务逻辑层抛出一个自定义异常。 | 抛出的自定义错误消息应传播到UI,而无需记录日志,因为我们假设这些错误消息是某种替代流程,而非真正的异常。 |
6 | 在处理请求时,UI层发生错误。例如,Null 引用异常等。 |
系统应将抛出的异常记录到一个接收器(文本文件)中。用用户友好的消息替换该异常,并将其传播到UI。 |
Using the Code
使用/运行此代码需要准备什么?
- Microsoft.NET Framework 3.5
- Microsoft Visual Studio 2008
- Enterprise Library 5.0
- SQL Server(服务器版、桌面版或SQL Express版 - 请相应地调整连接字符串。)
让我们来解释一下这个应用程序是如何分解的:
- 用户界面:使用ASP.NET和C#作为后端代码的Web应用程序。
- 业务逻辑:我们在Web应用程序内部为业务逻辑创建了一个单独的文件夹。但是,它可以很容易地移到一个单独的类库项目中。
- 帮助程序:使用C#的Windows类库。我们创建这个项目是为了整合以下功能:
- 数据访问帮助类,它使用企业库与数据库进行交互。这样我们就可以利用企业库的强大功能来处理任何类型的数据库管理系统,如SQL和Oracle。
- 异常处理,它也使用企业库,通过不同的策略来记录、替换和传播异常。
为了处理上述场景,我们创建了以下异常类型类:
BaseException
:它是一个继承自 System.Exception
类的基类。本项目中的所有其他类都将继承自该类。
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Common.Helpers.ExceptionHandling
{
public class BaseException : System.Exception, ISerializable
{
public BaseException()
: base()
{
// Add implementation (if required)
}
public BaseException(string message)
: base(message)
{
// Add implementation (if required)
}
public BaseException(string message, System.Exception inner)
: base(message, inner)
{
// Add implementation (if required)
}
protected BaseException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
// Add implementation (if required)
}
}
}
DataAccessException
:异常处理块使用此类来将原始异常替换为用户友好的消息,例如“处理您的请求时数据库层发生未知错误。请联系技术支持并提供此标识符 XXXXXXXXXXXXXX”。
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Common.Helpers.ExceptionHandling
{
public class DataAccessException : BaseException, ISerializable
{
public DataAccessException()
: base()
{
// Add implementation (if required)
}
public DataAccessException(string message)
: base(message)
{
// Add implemenation (if required)
}
public DataAccessException(string message, System.Exception inner)
: base(message, inner)
{
// Add implementation
}
protected DataAccessException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
//Add implemenation
}
}
}
DataAccessCustomException
:异常处理块使用此类将引发的自定义错误消息发送到UI。
PassThroughException
:此类用于在业务逻辑层将 DataAccessException
和 DataAccessCustomException
类型替换为此类型。否则,业务逻辑层也会处理该异常,因为数据访问层是从业务逻辑层调用的。
BusinessLogicException
:异常处理块使用此类来将原始异常替换为用户友好的消息,例如“处理您的请求时业务逻辑层发生未知错误。请联系技术支持并提供此标识符 XXXXXXXXXXXXXX”。
BusinessLogicCustomException
:异常处理块使用此类将引发的自定义错误消息发送到UI。
UserInterfaceException
:异常处理块使用此类来将原始异常替换为用户友好的消息,例如“处理您的请求时用户界面发生未知错误。请联系技术支持并提供此标识符 XXXXXXXXXXXXXX”。
为了支持该设计,我们创建了以下策略:
DataAccessPolicy
:此策略会将原始异常记录在定义的接收器中,并将原始异常替换为 DataAccessException
类型。
<add name="DataAccessPolicy">
<exceptionTypes>
<add name="All Exceptions" type="System.Exception, mscorlib,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="ThrowNewException">
<exceptionHandlers>
<add name="DataAccessLoggingHandler"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
Logging.LoggingExceptionHandler,
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging,
Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
logCategory="General" eventId="100" severity="Error"
title="Enterprise Library Exception Handling"
formatterType="Microsoft.Practices.EnterpriseLibrary.
ExceptionHandling.TextExceptionFormatter,
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"
priority="0" />
<add name="DataAccessReplaceHandler"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling,
Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
exceptionMessage="An unknown error has occurred in Data Access Layer
while processing your request. Please contract Help Desk Support at
X-XXX-XXX-XXXX with Error Token ID {handlingInstanceID}."
replaceExceptionType=
"Common.Helpers.ExceptionHandling.DataAccessException, Helpers,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</exceptionHandlers>
</add>
</exceptionTypes>
</add>
DataAccessCustomPolicy
:此策略将仅把原始异常替换为 DataAccessCustomException
类型。
<add name="DataAccessCustomPolicy"> <exceptionTypes> <add name="All Exceptions" type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" postHandlingAction="NotifyRethrow"> <exceptionHandlers> <add name="Replace Handler" type="Microsoft.Practices.EnterpriseLibrary. ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" replaceExceptionType="Common.Helpers.ExceptionHandling. DataAccessCustomException, Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </exceptionHandlers> </add> </exceptionTypes> </add>
PassThroughPolicy
:此策略会将类型为 DataAccessException
或 DataAccessCustomException
的原始异常替换为 PassThroughException
类型。
<add name="PassThroughPolicy">
<exceptionTypes>
<add name="All Exceptions" type="System.Exception, mscorlib,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="NotifyRethrow">
<exceptionHandlers>
<add name="PassThroughReplaceHandler"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling,
Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
replaceExceptionType="Common.Helpers.ExceptionHandling.PassThroughException,
Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</exceptionHandlers>
</add>
</exceptionTypes>
</add>
BusinessLogicPolicy
:此策略会将原始异常记录在定义的接收器中,并将原始异常替换为 BusinessLogicException
类型。
<add name="BusinessLogicPolicy">
<exceptionTypes>
<add name="All Exceptions" type="System.Exception,
mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="ThrowNewException">
<exceptionHandlers>
<add name="BusinessLogicLoggingHandler"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
Logging.LoggingExceptionHandler,
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging,
Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
logCategory="General" eventId="100" severity="Error"
title="Enterprise Library Exception Handling"
formatterType="Microsoft.Practices.EnterpriseLibrary.
ExceptionHandling.TextExceptionFormatter,
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"
priority="0" />
<add name="BusinessLogicReplaceHandler"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling,
Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
exceptionMessage="An unknown error has occurred in Business Logic
Layer while processing your request. Please contract Help Desk Support
at X-XXX-XXX-XXXX with Error Token ID {handlingInstanceID}."
replaceExceptionType="Common.Helpers.ExceptionHandling.
BusinessLogicException, Helpers, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null" />
</exceptionHandlers>
</add>
</exceptionTypes>
</add>
BusinessLogicCustomPolicy
:此策略将仅把原始异常替换为 BusinessLogicCustomException
类型。
<add name="BusinessLogicCustomPolicy">
<exceptionTypes>
<add name="All Exceptions" type="System.Exception,
mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="NotifyRethrow">
<exceptionHandlers>
<add name="Replace Handler"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling,
Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
replaceExceptionType="Common.Helpers.ExceptionHandling.
BusinessLogicCustomException, Helpers, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null" />
</exceptionHandlers>
</add>
</exceptionTypes>
</add>
UserInterfacePolicy
:此策略会将原始异常记录在定义的接收器中,并将原始异常替换为 UserInterfaceException
类型。
<add name="UserInterfacePolicy">
<exceptionTypes>
<add name="All Exceptions" type="System.Exception, mscorlib,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="ThrowNewException">
<exceptionHandlers>
<add name="UserInterfaceReplaceHandler"
type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.
Logging.LoggingExceptionHandler,
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging,
Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
logCategory="General" eventId="100" severity="Error"
title="Enterprise Library Exception Handling"
formatterType="Microsoft.Practices.EnterpriseLibrary.
ExceptionHandling.TextExceptionFormatter,
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"
priority="0" />
<add name="Replace Handler" type="Microsoft.Practices.
EnterpriseLibrary.ExceptionHandling.ReplaceHandler,
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling,
Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
exceptionMessage="An error occord at front end. please check."
replaceExceptionType="Common.Helpers.ExceptionHandling.
UserInterfaceException, Helpers, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null" />
</exceptionHandlers>
</add>
</exceptionTypes>
</add>
我们创建了以下异常处理程序来管理异常的记录、替换和传播:
DataAccessExceptionHandler
:此处理程序将处理 "DataAccessException
" 和 "DataAccessCustomException
" 异常类型。根据异常的类型,此处理程序将决定应使用哪个策略。以下是调用不同策略的代码:
using System;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Collections.Generic;
using System.Data.SqlClient;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
namespace Common.Helpers.ExceptionHandling
{
public static class DataAccessExceptionHandler
{
public static bool HandleException(ref System.Exception ex)
{
bool rethrow = false;
if ((ex is SqlException))
{
SqlException dbExp = (SqlException)ex;
if (dbExp.Number >= 50000)
{
rethrow = ExceptionPolicy.HandleException(ex, "DataAccessCustomPolicy");
ex = new DataAccessCustomException(ex.Message);
}
else
{
rethrow = ExceptionPolicy.HandleException(ex, "DataAccessPolicy");
if (rethrow)
{
throw ex;
}
}
}
else if (ex is System.Exception)
{
rethrow = ExceptionPolicy.HandleException(ex, "DataAccessPolicy");
if (rethrow)
{
throw ex;
}
}
return rethrow;
}
}
}
BusinessLogicExceptionHandler
:此处理程序将处理 "PassThroughException
"、"BusinessLogicException
" 和 "BusinessLogicCustomException
" 异常类型。根据异常的类型,此处理程序将决定应使用哪个策略。以下是调用不同策略的代码:
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using Common.Helpers.ExceptionHandling;
namespace Common.Helpers.ExceptionHandling
{
public static class BusinessLogicExceptionHandler
{
public static bool HandleExcetion(ref System.Exception ex)
{
bool rethrow = false;
if ((ex is DataAccessException) || (ex is DataAccessCustomException))
{
rethrow = ExceptionPolicy.HandleException(ex, "PassThroughPolicy");
ex = new PassThroughException(ex.Message);
}
else if (ex is BusinessLogicCustomException)
{
rethrow = ExceptionPolicy.HandleException(ex, "BusinessLogicCustomPolicy");
}
else
{
rethrow = ExceptionPolicy.HandleException(ex, "BusinessLogicPolicy");
}
if (rethrow)
{
throw ex;
}
return rethrow;
}
}
}
如果你检查上面的代码,你会发现在 DataAccessException
或 DataAccessCustomException
的情况下,该处理程序使用了 PassThroughExceptionPolicy
。这意味着异常已经被记录,需要传播给调用者。然后,系统会检查异常是由业务逻辑层有意引发的,还是一个不受控制的异常。根据异常类型,处理程序会调用适当的异常处理策略。
UserInterfaceExceptionHandler
:此处理程序将处理 "UserInterfaceException
" 异常类型。根据异常的类型,此处理程序将决定应使用哪个策略。以下是调用不同策略的代码:
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
namespace Common.Helpers.ExceptionHandling
{
public static class UserInterfaceExceptionHandler
{
public static bool HandleExcetion(ref System.Exception ex)
{
bool rethrow = false;
try
{
if (ex is BaseException)
{
// do nothing as Data Access or Business Logic exception
// has already been logged / handled
}
else
{
rethrow = ExceptionPolicy.HandleException(ex, "UserInterfacePolicy");
}
}
catch (Exception exp)
{
ex = exp;
}
return rethrow;
}
}
}
就是这样了。下载代码,尽情享用,并请留下您的反馈。
谢谢!
Sohail Maroof Naushahi, Farrukh Hashmi