入門系列-異常處理

ABP提供了用於處理Web應用程序異常的標準模型.

  • 自動 處理所有異常 .如果是API/AJAX請求,會向客戶端返回一個標準格式化後的錯誤消息 .
  • 自動隱藏 內部詳細錯誤 並返回標準錯誤消息.
  • 爲異常消息的 本地化 提供一種可配置的方式.
  • 自動爲標準異常設置 HTTP狀態代碼 ,並提供可配置選項,以映射自定義異常.

自動處理異常

當滿足下面任意一個條件時,AbpExceptionFilter 會處理此異常:

  • controller action方法返回類型是object result(而不是view result)並有異常拋出時.
  • 當一個請求爲AJAX(Http請求頭中X-Requested-WithXMLHttpRequest)時.
  • 當客戶端接受的返回類型爲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-namespacecode-namespace 同樣可以在 本地化 異常信息時使用.

  • 你可以直接拋出一個 BusinessException 異常,或者需要時可以從該類派生你自己的Exception類型.
  • 對於BusinessException 類型,其所有屬性都是可選的.但是通常會設置ErrorCodeMessage屬性.

異常本地化

這裏有個問題,就是如何在發送錯誤消息到客戶端時,對錯誤消息進行本地化.ABP提供了2個模型.

用戶友好異常

如果異常實現了 IUserFriendlyException 接口,那麼ABP不會修改 MessageDetails屬性,而直接將它發送給客戶端.

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 拋出.

你同樣可以在代碼中拋出這些類型的異常(雖然很少需要這樣做)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章