在我們提供的 HttpClient 用戶端程式碼中,有將 HttpClient 的相關存取程式碼,都加入到 try 區塊內,這樣,當用戶端程式碼發生例外異常的時候,我們就可以將其例外異常捕捉起來;不過,對於 Web API 伺服器那端,若發生了例外異常之後,我們在用戶端處理上,就會有些狀況發生。
在我們的設計模式中,希望不論後端系統是否有例外異常發生的時候,都能夠回傳
APIResult
JSON 編碼內容,這樣,我們就可以憑藉著這個些屬性值,做出適當的判斷與處理。由於我們後端的 Web API 程式碼採用的是 ASP.NET Core 的方式來進行開發,在後端那哩,我們可以設計一個繼承 ExceptionFilterAttribute
類別的物件,並且套用到整個控制器或者相關動作中,這樣,在用戶端那哩,我們就可以統一接收到 APIResult
JSON 編碼內容。
了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
呼叫 Web API 之後端與用戶端例外異常處理用法,避免應用程式異常中斷
在這個
GetExceptionFilter
方法中,我們會接收一個字串參數,這個參數會用來產生不同的 URL,當傳入的是 GetExceptionFilter 字串,我們將會呼叫有套用客製化的 CustomExceptionFilterAttribute
屬性,而傳入的是 GetException 字串,則沒有套用任何 ExceptionFilterAttribute
。
當取得
HttpResponseMessage
物件之後,我們會使用 response.IsSuccessStatusCode
屬性來判斷這次的呼叫,是否有成功,若沒有成功的話,我們會先嘗試解碼回傳內容,若成功的話,就可以取得後端伺服器回覆的內容,但是解碼失敗的話,則會在用戶端自行產生一個新的 APIResult
物件,並且設定回傳錯誤資訊。private static async Task<APIResult> GetExceptionFilter(string action)
{
APIResult fooAPIResult;
using (HttpClientHandler handler = new HttpClientHandler())
{
using (HttpClient client = new HttpClient(handler))
{
try
{
#region 呼叫遠端 Web API
string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/values/{action}";
HttpResponseMessage response = null;
#region 設定相關網址內容
var fooFullUrl = $"{FooUrl}";
// Accept 用於宣告客戶端要求服務端回應的文件型態 (底下兩種方法皆可任選其一來使用)
//client.DefaultRequestHeaders.Accept.TryParseAdd("application/json");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Content-Type 用於宣告遞送給對方的文件型態
//client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
response = await client.GetAsync(fooFullUrl);
#endregion
#endregion
#region 處理呼叫完成 Web API 之後的回報結果
if (response != null)
{
if (response.IsSuccessStatusCode == true)
{
// 取得呼叫完成 API 後的回報內容
String strResult = await response.Content.ReadAsStringAsync();
fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
}
else
{
// 這裡將會取得這次例外異常的錯誤資訊
String strResult = await response.Content.ReadAsStringAsync();
fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
if (fooAPIResult == null)
{
fooAPIResult = new APIResult
{
Success = false,
Message = string.Format("Error Code:{0}, Error Message:{1}", response.StatusCode, response.RequestMessage),
Payload = null,
};
}
}
}
else
{
fooAPIResult = new APIResult
{
Success = false,
Message = "應用程式呼叫 API 發生異常",
Payload = null,
};
}
#endregion
}
catch (Exception ex)
{
fooAPIResult = new APIResult
{
Success = false,
Message = ex.Message,
Payload = ex,
};
}
}
}
return fooAPIResult;
}
觸發的 Web API 動作
當 URL 指向的是
http://vulcanwebapi.azurewebsites.net/api/values/GetExceptionFilter
,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public APIResult GetExceptionFilter()
動作(Action),其有套用我們自訂的 CustomExceptionFilter
C# 屬性 (Attribute) 在這個方法上,其該動作的原始碼如下所示。
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpGet("GetExceptionFilter")]
[CustomExceptionFilter]
public APIResult GetExceptionFilter()
{
APIResult foo = new APIResult();
throw new Exception("喔喔,我發生錯誤了");
return foo;
}
而這個
CustomExceptionFilterAttribute
類別,則是當後端 Web API 有例外異常發生的時候,就會執行 OnException
這裡覆寫方法,在這個方法內,我們取得當時後端伺服器中發生的例外異常呼叫堆疊與訊息,並且把些資料,設定到 APIResul
類別物件內,這樣,用戶端就會得到這個回傳訊息(雖然,伺服器端已經發生了錯誤)。
這裡,我們也使用了
context.HttpContext.Response.StatusCode
設定了此次 Http 呼叫的處理狀態碼為 500。public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
APIResult foo = new APIResult();
// Unhandled errors
var msg = context.Exception.GetBaseException().Message;
string stack = context.Exception.StackTrace;
foo.Success = false;
foo.Message = msg;
foo.Payload = stack;
context.HttpContext.Response.StatusCode = 500;
context.Result = new JsonResult(foo);
base.OnException(context);
}
}
當 URL 指向的是
http://vulcanwebapi.azurewebsites.net/api/values/GetException
,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public APIResult GetException()
動作(Action),這裡沒有套用任何自訂的 ExceptionFilterAttribute
C# 屬性 (Attribute) 在這個方法上,其該動作的原始碼如下所示。
這個 Web API 動作,將不會回傳一個 APIData 的 JSON 資料。
[HttpGet("GetException")]
public APIResult GetException()
{
APIResult foo = new APIResult();
throw new Exception("喔喔,我發生錯誤了");
return foo;
}
進行測試
在程式進入點函式,我們呼叫了兩次
GetExceptionFilter
方法,不過,由於傳送的引數字串不同,會導致執行不同 URL 的 Http GET 要求。static async Task Main(string[] args)
{
var foo = await GetExceptionFilter("GetExceptionFilter");
Console.WriteLine($"使用 Get 方法呼叫,並有套用 ExceptionFilter");
Console.WriteLine($"結果狀態 : {foo.Success}");
Console.WriteLine($"結果訊息 : {foo.Message}");
Console.WriteLine($"其他訊息 : {foo.Payload}");
Console.WriteLine($"");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
foo = await GetExceptionFilter("GetException");
Console.WriteLine($"使用 Get 方法呼叫,沒有套用 ExceptionFilter");
Console.WriteLine($"結果狀態 : {foo.Success}");
Console.WriteLine($"結果訊息 : {foo.Message}");
Console.WriteLine($"其他訊息 : {foo.Payload}");
Console.WriteLine($"");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
}
執行結果
這個測試將會輸出底下內容
使用 Get 方法呼叫,並有套用 ExceptionFilter
結果狀態 : False
結果訊息 : 喔喔,我發生錯誤了
其他訊息 : at VulcanWebAPI.Controllers.ValuesController.GetExceptionFilter() in D:\Vulcan\GitHub\CSharpNotes\WebAPI\VulcanWebAPI\Controllers\ValuesController.cs:line 86
at lambda_method(Closure , Object , Object[] )
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextExceptionFilterAsync>d__23.MoveNext()
Press any key to Exist...
使用 Get 方法呼叫,沒有套用 ExceptionFilter
結果狀態 : False
結果訊息 : Error Code:InternalServerError, Error Message:Method: GET, RequestUri: 'http://vulcanwebapi.azurewebsites.net/api/values/GetException', Version: 1.1, Content: <null>, Headers:
{
Accept: application/json
}
其他訊息 :
Press any key to Exist...
HTTP 傳送與接收原始封包
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
- 請求 (Request)這裡將會呼叫 URL 有套用
CustomExceptionFilterAttribute
的屬性之控制器動作。
GET http://vulcanwebapi.azurewebsites.net/api/values/GetExceptionFilter HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
Connection: Keep-Alive
- 反應 (Response)由於 ASP.NET Core 系統會自動捕捉到任何在呼叫 Web API 過程中所產生的例外異常,因此,在用戶端中,我們可以透過回傳的 JSON 字串,經過反序列化得到
APIResult
類別物件;我們可以從這個物件,得到更多關於此次伺服器上發生的錯誤資訊。
HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Mon, 23 Oct 2017 02:24:12 GMT
7f0
{"success":false,"message":"喔喔,我發生錯誤了","payload":" at VulcanWebAPI.Controllers.ValuesController.GetExceptionFilter() in D:\\Vulcan\\GitHub\\CSharpNotes\\WebAPI\\VulcanWebAPI\\Controllers\\ValuesController.cs:line 86\r\n at lambda_method(Closure , Object , Object[] )\r\n at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__14.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.GetResult()\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextExceptionFilterAsync>d__23.MoveNext()"}
0
- 請求 (Request)這裡將會呼叫 URL 將沒有套用
CustomExceptionFilterAttribute
的屬性之控制器動作。
GET http://vulcanwebapi.azurewebsites.net/api/values/GetException HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
- 反應 (Response)您可以看到,由於沒有套用任何
ExceptionFilterAttribute
的客製類別屬性,所以,所得到的 Http 回應封包卻沒有辦法看到任何此次異常發生了甚麼問題,只知道狀態碼是 500 Internal Server Error
HTTP/1.1 500 Internal Server Error
Content-Length: 0
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Mon, 23 Oct 2017 02:24:16 GMT
相關文章索引
C# HttpClient WebAPI 系列文章索引
了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
關於 Xamarin 在台灣的學習技術資源
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程