.NET 6開(kāi)發(fā)TodoList應(yīng)用之實(shí)現(xiàn)全局異常處理
需求
因?yàn)樵陧?xiàng)目中,會(huì)有各種各樣的領(lǐng)域異?;蛳到y(tǒng)異常被拋出來(lái),那么在Controller
里就需要進(jìn)行完整的try-catch
捕獲,并根據(jù)是否有異常拋出重新包裝返回值。這是一項(xiàng)機(jī)械且繁瑣的工作。有沒(méi)有辦法讓框架自己去做這件事呢?
有的,解決方案的名稱(chēng)叫做全局異常處理,或者叫做如何讓接口優(yōu)雅地失敗。
目標(biāo)
我們希望將異常處理和消息返回放到框架中進(jìn)行統(tǒng)一處理,擺脫Controller
層的try-catch
塊。
原理和思路
一般而言用來(lái)實(shí)現(xiàn)全局異常處理的思路有兩種,但是出發(fā)點(diǎn)都是通過(guò).NET Web API
的管道中間件Middleware Pipeline
實(shí)現(xiàn)的。第一種方式是通過(guò).NET
內(nèi)建的中間件來(lái)實(shí)現(xiàn);第二種是完全自定義中間件實(shí)現(xiàn)。
我們會(huì)簡(jiǎn)單地介紹一下如何通過(guò)內(nèi)建中間件實(shí)現(xiàn),然后實(shí)際使用第二種方式來(lái)實(shí)現(xiàn)我們的代碼,大家可以比較一下異同。
在Api
項(xiàng)目中創(chuàng)建Models
文件夾并創(chuàng)建ErrorResponse
類(lèi)。
ErrorResponse.cs
using System.Net; using System.Text.Json; namespace TodoList.Api.Models; public class ErrorResponse { public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.InternalServerError; public string Message { get; set; } = "An unexpected error occurred."; public string ToJsonString() => JsonSerializer.Serialize(this); }
創(chuàng)建Extensions文件夾并新建一個(gè)靜態(tài)類(lèi)ExceptionMiddlewareExtensions實(shí)現(xiàn)一個(gè)靜態(tài)擴(kuò)展方法:
ExceptionMiddlewareExtensions.cs
using System.Net; using Microsoft.AspNetCore.Diagnostics; using TodoList.Api.Models; namespace TodoList.Api.Extensions; public static class ExceptionMiddlewareExtensions { public static void UseGlobalExceptionHandler(this WebApplication app) { app.UseExceptionHandler(appError => { appError.Run(async context => { context.Response.ContentType = "application/json"; var errorFeature = context.Features.Get<IExceptionHandlerFeature>(); if (errorFeature != null) { await context.Response.WriteAsync(new ErrorResponse { StatusCode = (HttpStatusCode)context.Response.StatusCode, Message = errorFeature.Error.Message }.ToJsonString()); } }); }); } }
在中間件配置的最開(kāi)始配置好,注意中間件管道是有順序的,把全局異常處理放到第一步(同時(shí)也是請(qǐng)求返回的最后一步)能確保它能攔截到所有可能發(fā)生的異常。即這個(gè)位置:
var app = builder.Build(); app.UseGlobalExceptionHandler();
就可以實(shí)現(xiàn)全局異常處理了。接下來(lái)我們看如何完全自定義一個(gè)全局異常處理的中間件,其實(shí)原理是完全一樣的,只不過(guò)我更偏向自定義中間件的代碼組織方式,更加簡(jiǎn)潔和一目了然。
與此同時(shí),我們希望對(duì)返回值進(jìn)行格式上的統(tǒng)一包裝,于是定義了這樣的返回類(lèi)型:
ApiResponse.cs
using System.Text.Json; namespace TodoList.Api.Models; public class ApiResponse<T> { public T Data { get; set; } public bool Succeeded { get; set; } public string Message { get; set; } public static ApiResponse<T> Fail(string errorMessage) => new() { Succeeded = false, Message = errorMessage }; public static ApiResponse<T> Success(T data) => new() { Succeeded = true, Data = data }; public string ToJsonString() => JsonSerializer.Serialize(this); }
實(shí)現(xiàn)
在Api項(xiàng)目中新建Middlewares文件夾并新建中間件GlobalExceptionMiddleware
GlobalExceptionMiddleware.cs
using System.Net; using TodoList.Api.Models; namespace TodoList.Api.Middlewares; public class GlobalExceptionMiddleware { private readonly RequestDelegate _next; public GlobalExceptionMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception exception) { // 你可以在這里進(jìn)行相關(guān)的日志記錄 await HandleExceptionAsync(context, exception); } } private async Task HandleExceptionAsync(HttpContext context, Exception exception) { context.Response.ContentType = "application/json"; context.Response.StatusCode = exception switch { ApplicationException => (int)HttpStatusCode.BadRequest, KeyNotFoundException => (int)HttpStatusCode.NotFound, _ => (int)HttpStatusCode.InternalServerError }; var responseModel = ApiResponse<string>.Fail(exception.Message); await context.Response.WriteAsync(responseModel.ToJsonString()); } }
這樣我們的ExceptionMiddlewareExtensions就可以寫(xiě)成下面這樣了:
ExceptionMiddlewareExtensions.cs
using TodoList.Api.Middlewares; namespace TodoList.Api.Extensions; public static class ExceptionMiddlewareExtensions { public static WebApplication UseGlobalExceptionHandler(this WebApplication app) { app.UseMiddleware<GlobalExceptionMiddleware>(); return app; } }
驗(yàn)證
首先我們需要在Controller中包裝我們的返回值,舉一個(gè)CreateTodoList的例子,其他的類(lèi)似修改:
TodoListController.cs
[HttpPost] public async Task<ApiResponse<Domain.Entities.TodoList>> Create([FromBody] CreateTodoListCommand command) { return ApiResponse<Domain.Entities.TodoList>.Success(await _mediator.Send(command)); }
還記得我們?cè)赥odoList的領(lǐng)域?qū)嶓w上有一個(gè)Colour的屬性嗎,它是一個(gè)值對(duì)象,并且在賦值的過(guò)程中我們讓它有機(jī)會(huì)拋出一個(gè)UnsupportedColourException,我們就用這個(gè)領(lǐng)域異常來(lái)驗(yàn)證全局異常處理。
為了驗(yàn)證需要,我們可以對(duì)CreateTodoListCommand做一些修改,讓它接受一個(gè)Colour的字符串,相應(yīng)修改如下:
CreateTodoListCommand.cs
public class CreateTodoListCommand : IRequest<Domain.Entities.TodoList> { public string? Title { get; set; } public string? Colour { get; set; } } // 以下代碼位于對(duì)應(yīng)的Handler中,省略其他... var entity = new Domain.Entities.TodoList { Title = request.Title, Colour = Colour.From(request.Colour ?? string.Empty) };
啟動(dòng)Api項(xiàng)目,我們?cè)噲D以一個(gè)不支持的顏色來(lái)創(chuàng)建TodoList:
請(qǐng)求
響應(yīng)
順便去看下正常返回的格式是否按我們預(yù)期的返回,下面是請(qǐng)求所有TodoList集合的接口返回:
可以看到正常和異常的返回類(lèi)型已經(jīng)統(tǒng)一了。
總結(jié)
其實(shí)實(shí)現(xiàn)全局異常處理還有一種方法是通過(guò)Filter來(lái)做,具體方法可以參考這篇文章:Filters in ASP.NET Core,我們之所以不選擇Filter而使用Middleware主要是基于簡(jiǎn)單、易懂,并且作為中間件管道的第一個(gè)個(gè)中間件加入,有效地覆蓋包括中間件在內(nèi)的所有組件處理過(guò)程。Filter的位置是在路由中間件作用之后才被調(diào)用到。實(shí)際使用中,兩種方式都有應(yīng)用。
下一篇我們來(lái)實(shí)現(xiàn)PUT請(qǐng)求。
參考資料
1.Write custom ASP.NET Core middleware
到此這篇關(guān)于.NET 6開(kāi)發(fā)TodoList應(yīng)用之實(shí)現(xiàn)全局異常處理的文章就介紹到這了,更多相關(guān).NET 6 全局異常處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
給Asp.Net初學(xué)者的關(guān)于繼承和多態(tài)性的例子
給Asp.Net初學(xué)者的關(guān)于繼承和多態(tài)性的例子...2006-09-09asp.net下數(shù)據(jù)庫(kù)操作優(yōu)化一例
數(shù)據(jù)庫(kù)升級(jí),需要對(duì)幾個(gè)表進(jìn)行一些數(shù)據(jù)轉(zhuǎn)換,具體是這樣:針對(duì)每一個(gè) Item,從 orders 表里查出 Shop_Id,并把此 Id 賦值給 items 和 skus 中的 Shop_Id。2010-11-11ASP.NET Core中實(shí)現(xiàn)全局異常攔截的完整步驟
這篇文章主要給大家介紹了關(guān)于ASP.NET Core中如何實(shí)現(xiàn)全局異常攔截的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Visual Studio 2017無(wú)法加載Visual Studio 2015創(chuàng)建的SharePoint解決方法
這篇文章主要為大家詳細(xì)介紹了Visual Studio 2017無(wú)法加載Visual Studio 2015創(chuàng)建的SharePoint的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03ASP.NET MVC視圖頁(yè)使用jQuery傳遞異步數(shù)據(jù)的幾種方式詳解
本文詳細(xì)講解了ASP.NET MVC視圖頁(yè)使用jQuery傳遞異步數(shù)據(jù)的幾種方式,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09詳解.NET中的加密算法總結(jié)(自定義加密Helper類(lèi)續(xù))
這篇文章主要介紹了詳解.NET中的加密算法總結(jié)(自定義加密Helper類(lèi)續(xù)) ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-12-12asp.net 無(wú)刷新翻頁(yè)就是這么簡(jiǎn)單
前兩天看了一個(gè)自定義分頁(yè)控件,和AspNetPager一樣是實(shí)現(xiàn)IPostBackEventHandler接口,不過(guò)簡(jiǎn)潔許多,就想能不能實(shí)現(xiàn)ICallbackEventHandler接口做到無(wú)刷新分頁(yè)呢?想到了就馬上去做,終于,設(shè)想變成了現(xiàn)實(shí)??!2010-03-03