ABP提供了用於處理Web應用程序異常的標準模型.
- 自動 處理所有異常 .如果是API/AJAX請求,會向客戶端返回一個標準格式化後的錯誤消息 .
- 自動隱藏 內部詳細錯誤 並返回標準錯誤消息.
- 爲異常消息的 本地化 提供一種可配置的方式.
- 自動爲標準異常設置 HTTP狀態代碼 ,並提供可配置選項,以映射自定義異常.
自動處理異常
當滿足下面任意一個條件時,AbpExceptionFilter
會處理此異常:
- 當controller action方法返回類型是object result(而不是view result)並有異常拋出時.
- 當一個請求爲AJAX(Http請求頭中
X-Requested-With
爲XMLHttpRequest
)時. - 當客戶端接受的返回類型爲
application/json
(Http請求頭中accept
爲application/json
)時.
如果異常被處理過,則會自動記錄日誌並將格式化的JSON消息返回給客戶端.
錯誤消息格式
每個錯誤消息都是RemoteServiceErrorResponse
類的實例.最簡單的錯誤JSON只有一個 Message 屬性,如下所示:
{
"error": {
"message": "This topic is locked and can not add a new message"
}
}
其它可選字段可以根據已發生的異常來填充.
錯誤代碼
錯誤 代碼(code) 是異常信息中一個有唯一值並可選的字符串值.拋出的異常應實現IHasErrorCode
接口來填充該字段.示例JSON如下:
{
"error": {
"code": "App:010042",
"message": "This topic is locked and can not add a new message"
}
}
錯誤代碼同樣可用於異常信息的本地化及自定義HTTP狀態代碼(請參閱下面的相關部分).
錯誤詳細信息
錯誤的 詳細信息(Details) 是可選屬性.拋出的異常應實現IHasErrorDetails
接口來填充該字段.示例JSON如下:
{
"error": {
"code": "App:010042",
"message": "This topic is locked and can not add a new message",
"details": "A more detailed info about the error..."
}
}
驗證錯誤
當拋出的異常實現IHasValidationErrors
接口時,validationErrors是一個可被填充的標準字段.示例JSON如下:
{
"error": {
"code": "App:010046",
"message": "Your request is not valid, please correct and try again!",
"validationErrors": [{
"message": "Username should be minimum length of 3.",
"members": ["userName"]
},
{
"message": "Password is required",
"members": ["password"]
}]
}
}
AbpValidationException
已經實現了IHasValidationErrors
接口,當請求輸入無效時,框架會自動拋出此錯誤. 因此,除非你有自定義的驗證邏輯,否則不需要處理驗證錯誤.
日誌
被捕獲的異常會被自動記錄到日誌中.
日誌級別
默認情況下,記錄異常級別爲Error
.可以通過實現IHasLogLevel
接口來指定日誌的級別,例如:
public class MyException : Exception, IHasLogLevel
{
public LogLevel LogLevel { get; set; } = LogLevel.Warning;
//...
}
異常自定義日誌
某些異常類型可能需要記錄額外日誌信息.可以通過實現IExceptionWithSelfLogging
接口來記錄指定日誌,例如:
public class MyException : Exception, IExceptionWithSelfLogging
{
public void Log(ILogger logger)
{
//...log additional info
}
}
擴展方法
ILogger.LogException
用來記錄異常日誌. 在需要時可以使用相同的擴展方法.
業務異常
大多數異常都是業務異常.可以通過使用IBusinessException
接口來標記異常爲業務異常.
BusinessException
除了實現IHasErrorCode
,IHasErrorDetails
,IHasLogLevel
接口外,還實現了IBusinessException
接口.其默認日誌級別爲Warning
.
通常你會將一個錯誤代碼關聯至特定的業務異常.例如:
throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer);
QaErrorCodes.CanNotVoteYourOwnAnswer
是一個字符串常量. 建議使用下面的錯誤代碼格式:
<code-namespace>:<error-code>
code-namespace,應在指定的模塊/應用層中保證其唯一.例如:
Volo.Qa:010002
Volo.Qa
在這是作爲code-namespace
. code-namespace
同樣可以在 本地化 異常信息時使用.
- 你可以直接拋出一個
BusinessException
異常,或者需要時可以從該類派生你自己的Exception類型. - 對於
BusinessException
類型,其所有屬性都是可選的.但是通常會設置ErrorCode
或Message
屬性.
異常本地化
這裏有個問題,就是如何在發送錯誤消息到客戶端時,對錯誤消息進行本地化.ABP提供了2個模型.
用戶友好異常
如果異常實現了 IUserFriendlyException
接口,那麼ABP不會修改 Message
和Details
屬性,而直接將它發送給客戶端.
UserFriendlyException
類是內建的 IUserFriendlyException
接口的實現,示例如下:
throw new UserFriendlyException(
"Username should be unique!"
);
採用這種方式是不需要本地化的.如果需要本地化消息,則可以注入string localizer( 請參閱本地化文檔 )來實現. 例:
throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage"]);
再在本地化資源中爲每種語言添加對應的定義.例如:
{
"culture": "en",
"texts": {
"UserNameShouldBeUniqueMessage": "Username should be unique!"
}
}
string localizer 支持參數化信息,例如:
throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage", "john"]);
其本地化文本如下:
"UserNameShouldBeUniqueMessage": "Username should be unique! '{0}' is already taken!"
IUserFriendlyException
接口派生自IBusinessException
,而 UserFriendlyException
類派生自BusinessException
類。
使用錯誤代碼
UserFriendlyException
很好用,但是在一些高級用法裏面,它存在以下問題:
- 在拋出異常的地方必須注入string localizer 來實現本地化 .
- 但是,在某些情況下,可能注入不了string localizer(比如,在靜態上下文或實體方法中)
那麼這時就可以通過使用 錯誤代碼 的方式來處理本地化,而不是在拋出異常的時候.
首先,在模塊配置代碼中將 code-namespace 映射至 本地化資源:
services.Configure<ExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("Volo.Qa", typeof(QaResource));
});
然後Volo.Qa
命名空間下的所有異常都將被對應的本地化資源進行本地化處理. 本地化資源中應包含對應錯誤代碼的文本. 例如:
{
"culture": "en",
"texts": {
"Volo.Qa:010002": "You can not vote your own answer!"
}
}
最後就可以拋出一個包含錯誤代碼的業務異常了:
throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer);
- 拋出所有實現
IHasErrorCode
接口的異常都具有相同的行爲.因此,對錯誤代碼的本地化,並不是BusinessException
類所特有的. - 爲錯誤消息定義本地化文本並不是必須的. 如果未定義,ABP會將默認的錯誤消息發送給客戶端. 而不使用異常的
Message
屬性. 如果你想要發送異常的Message
,使用UserFriendlyException
(或使用實現IUserFriendlyException
接口的異常類型)
使用消息的格式化參數
如果有參數化的錯誤消息,則可以使用異常的Data
屬性進行設置.例如:
throw new BusinessException("App:010046")
{
Data =
{
{"UserName", "john"}
}
};
另外有一種更爲快捷的方式:
throw new BusinessException("App:010046")
.WithData("UserName", "john");
下面就是一個包含UserName
參數的錯誤消息:
{
"culture": "en",
"texts": {
"App:010046": "Username should be unique. '{UserName}' is already taken!"
}
}
WithData
支持有多個參數的鏈式調用 (如.WithData(...).WithData(...)
).
HTTP狀態代碼映射
ABP嘗試按照以下規則,自動映射常見的異常類型的HTTP狀態代碼:
- 對於
AbpAuthorizationException
:- 用戶沒有登錄,返回
401
(未認證). - 用戶已登錄,但是當前訪問未授權,返回
403
(未授權).
- 用戶沒有登錄,返回
- 對於
AbpValidationException
返回400
(錯誤的請求) . - 對於
EntityNotFoundException
返回404
(未找到). - 對於
IBusinessException
和IUserFriendlyException
(它是IBusinessException
的擴展) 返回403
(未授權) . - 對於
NotImplementedException
返回501
(未實現) . - 對於其他異常 (基礎架構中未定義的) 返回
500
(服務器內部錯誤) .
IHttpExceptionStatusCodeFinder
是用來自動判斷HTTP狀態代碼.默認的實現是DefaultHttpExceptionStatusCodeFinder
.可以根據需要對其進行更換或擴展.
自定義映射
可以重寫HTTP狀態代碼的自動映射,示例如下:
services.Configure<ExceptionHttpStatusCodeOptions>(options =>
{
options.Map("Volo.Qa:010002", HttpStatusCode.Conflict);
});
內置的異常
框架會自動拋出以下異常類型:
- 當用戶沒有權限執行操作時,會拋出
AbpAuthorizationException
異常. 有關更多信息,請參閱授權文檔(TODO:link). - 如果當前請求的輸入無效,則拋出
AbpValidationException 異常
. 有關更多信息,請參閱授權文檔(TODO:link). - 如果請求的實體不存在,則拋出
EntityNotFoundException
異常. 此異常大多數由 repositories 拋出.
你同樣可以在代碼中拋出這些類型的異常(雖然很少需要這樣做)