ASP.NET Web API 2.0 統一響應格式


傳統實現

在搭建 Web API 服務的時候,針對客戶端請求,我們一般都會自定義響應的 JSON 格式,比如:

{
	"Data" : {
		"Id" : 100,
		"Name" : "Robin"
	},
	"ErrorMessage" : "錯誤消息"
}

在基於 ASP.NET Web API 的應用程序,我們一般會創建一個相應結構的 C# 類,如下:

public class ApiResult
{  
   public string ErrorMessage { get; set; }
   public object Data { get; set; }
}

這里約定, ErrorMessage 為空或null,即表示沒有異常,這時 Data 就是需要的數據;反之如果 ErrorMessage 不為空或null, 則代表錯誤消息,這時 Data 為null。

接下來在 Action 中返回該類的一個實例, Web API 會在內部調用格式化器將對象序列化為 JSON 或 XML 等格式,如下:

public class UserController : ApiController
{
	public IHttpActionResult GetUser()
	{
		return new ApiResult()
		{
			Data = new User{ Id = 100, Name = "Robin" },
			ErrorMessage = string.Empty
		};
	}
}

public class User
{
	public int Id {get; set;}
	public string Name {get; set;}
}

好了,傳統做法就是這樣,也可以實現。但是再進一步考慮,如果有非常多的 Action 方法,每次都要寫 reutrn new ApiResult(){......} 是不是特別繁瑣呢?

問題

有沒有辦法在 Action 方法中只返回真正需要的數據,但是返回給客戶端時又統一成約定的 JSON 結構呢?

解決方案

當然有辦法,借助 Web API 提供的 ActionFilter 就可以實現。

首先我們新建一個 CustomActionFilter :

public class CustomActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuted(HttpActionExecutedContext context)
    {
        var content = context.Response?.Content as ObjectContent;
        if (content != null)
        {
            content.Value = new ApiResult 
				            { 
					            Data = content.Value, 
							};
        }
    }
}

然后 Action 方法這樣寫:

public User GetAll()
{
	return new new User{Id = 100, Name = "Robin"};
}

這樣實現的另一個好處是,由於返回值是強類型,可以據此生成 API 文檔,方法的可讀性也更好。

異常處理

前面提到的需求實現了,然后再進一步考慮,如何處理異常情況?
如果由於代碼 BUG 拋出未處理的異常,Web API仍然會調用 CustomActionFilter 中的代碼,但是這時 Response = null,也就無法給 content.Value 重新賦值。
這時 Web API 會將框架約定的 JSON 消息返回給客戶端,而不是我們業務上需要的,如下是 Web API 拋出的未處理異常消息:

{
  "Message": "An error has occurred.",
  "ExceptionMessage": "No MessageException parameter",
  "ExceptionType": "Framework.Common.MessageException",
  "StackTrace": "   在 Controllers.FooController.GetAll() 位置......
}

這時如果還希望異常消息遵循業務約定的 JSON 格式,該如何做呢?
這里要分幾種情況:

Action 內的異常

可以直接在 CustomActionFilter 的 OnActionExecuted 方法中處理,改造后的代碼如下:

public override void OnActionExecuted(HttpActionExecutedContext context)
{
    var content = context.Response?.Content as ObjectContent;
    if (content != null)
    {
        content.Value = new ApiResult { Data = content.Value };
    }
    
    // 設置發生異常時的消息
    if (context.Exception != null)
    {
        context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
        {
            Content = new StringContent(JsonConvert.SerializeObject(
                new ApiResult
                {
                    ErrorMessage = context.Exception.Message
                }), Encoding.UTF8, "application/json")
        };
    }
}

同樣也可以用自定義 ExceptionFilter 來達到同樣的目的,這里為了簡化不再貼代碼。

其他異常

ActionFilterAttribute 和 ExceptionFilterAttribute 都只能處理部分異常,比如 Action 內的異常,但是譬如 以下的幾種未處理異常,過濾器就愛莫能助了:

  1. 來自 Controller 構造器的異常。
  2. 來自 Message Handlers 的異常。
  3. 匹配路由過程中的異常
  4. 在序列化響應內容期間產生的異常

為了處理全局范圍內的未處理異常,Web API 提供了 ExceptionHandler 和 ExceptionLogger。
詳情可以參考我翻譯的文檔:ASP.NET Web API 2 中的全局錯誤處理
其中只有 ExceptionHandler 可以在捕捉到未處理異常並處理后,對響應消息進行重新設置,而 ExceptionLogger 則不能。

代碼如下:

public class CollectServiceExceptionHandler : ExceptionHandler
{
    public override Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
    {
        context.Result = new ApiResult { ErrorMessage = context.Exception.Message };

        return base.HandleAsync(context, cancellationToken);
    }
}

注意:這里 ExceptionHandlerContextResult 屬性的類型是 IHttpActionResult,所以**ApiResult ** 類要實現 IHttpActionResult 接口。

ExceptionHandler 的用途就是:接收全局范圍內未處理的異常,然后返回一個自定義的錯誤消息。

總結

實現開篇的需求,有三種實現方式:

  1. 自定義 ActionFilterAttribute
  2. 自定義 ExceptionFilterAttribute
  3. 自定義 ExceptionHandler

補充:經 @ichengzi 指出,『web api 2.0 之前的版本不支持這種處理方法』。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM