.NET 6開發(fā)TodoList應(yīng)用之實現(xiàn)全局異常處理
需求
因為在項目中,會有各種各樣的領(lǐng)域異?;蛳到y(tǒng)異常被拋出來,那么在Controller里就需要進(jìn)行完整的try-catch捕獲,并根據(jù)是否有異常拋出重新包裝返回值。這是一項機械且繁瑣的工作。有沒有辦法讓框架自己去做這件事呢?
有的,解決方案的名稱叫做全局異常處理,或者叫做如何讓接口優(yōu)雅地失敗。
目標(biāo)
我們希望將異常處理和消息返回放到框架中進(jìn)行統(tǒng)一處理,擺脫Controller層的try-catch塊。
原理和思路
一般而言用來實現(xiàn)全局異常處理的思路有兩種,但是出發(fā)點都是通過.NET Web API的管道中間件Middleware Pipeline實現(xiàn)的。第一種方式是通過.NET內(nèi)建的中間件來實現(xiàn);第二種是完全自定義中間件實現(xiàn)。
我們會簡單地介紹一下如何通過內(nèi)建中間件實現(xiàn),然后實際使用第二種方式來實現(xiàn)我們的代碼,大家可以比較一下異同。
在Api項目中創(chuàng)建Models文件夾并創(chuàng)建ErrorResponse類。
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文件夾并新建一個靜態(tài)類ExceptionMiddlewareExtensions實現(xiàn)一個靜態(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());
}
});
});
}
}
在中間件配置的最開始配置好,注意中間件管道是有順序的,把全局異常處理放到第一步(同時也是請求返回的最后一步)能確保它能攔截到所有可能發(fā)生的異常。即這個位置:
var app = builder.Build(); app.UseGlobalExceptionHandler();
就可以實現(xiàn)全局異常處理了。接下來我們看如何完全自定義一個全局異常處理的中間件,其實原理是完全一樣的,只不過我更偏向自定義中間件的代碼組織方式,更加簡潔和一目了然。
與此同時,我們希望對返回值進(jìn)行格式上的統(tǒng)一包裝,于是定義了這樣的返回類型:
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);
}
實現(xiàn)
在Api項目中新建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就可以寫成下面這樣了:
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;
}
}
驗證
首先我們需要在Controller中包裝我們的返回值,舉一個CreateTodoList的例子,其他的類似修改:
TodoListController.cs
[HttpPost]
public async Task<ApiResponse<Domain.Entities.TodoList>> Create([FromBody] CreateTodoListCommand command)
{
return ApiResponse<Domain.Entities.TodoList>.Success(await _mediator.Send(command));
}
還記得我們在TodoList的領(lǐng)域?qū)嶓w上有一個Colour的屬性嗎,它是一個值對象,并且在賦值的過程中我們讓它有機會拋出一個UnsupportedColourException,我們就用這個領(lǐng)域異常來驗證全局異常處理。
為了驗證需要,我們可以對CreateTodoListCommand做一些修改,讓它接受一個Colour的字符串,相應(yīng)修改如下:
CreateTodoListCommand.cs
public class CreateTodoListCommand : IRequest<Domain.Entities.TodoList>
{
public string? Title { get; set; }
public string? Colour { get; set; }
}
// 以下代碼位于對應(yīng)的Handler中,省略其他...
var entity = new Domain.Entities.TodoList
{
Title = request.Title,
Colour = Colour.From(request.Colour ?? string.Empty)
};
啟動Api項目,我們試圖以一個不支持的顏色來創(chuàng)建TodoList:
請求

響應(yīng)

順便去看下正常返回的格式是否按我們預(yù)期的返回,下面是請求所有TodoList集合的接口返回:

可以看到正常和異常的返回類型已經(jīng)統(tǒng)一了。
總結(jié)
其實實現(xiàn)全局異常處理還有一種方法是通過Filter來做,具體方法可以參考這篇文章:Filters in ASP.NET Core,我們之所以不選擇Filter而使用Middleware主要是基于簡單、易懂,并且作為中間件管道的第一個個中間件加入,有效地覆蓋包括中間件在內(nèi)的所有組件處理過程。Filter的位置是在路由中間件作用之后才被調(diào)用到。實際使用中,兩種方式都有應(yīng)用。
下一篇我們來實現(xiàn)PUT請求。
參考資料
1.Write custom ASP.NET Core middleware
到此這篇關(guān)于.NET 6開發(fā)TodoList應(yīng)用之實現(xiàn)全局異常處理的文章就介紹到這了,更多相關(guān).NET 6 全局異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
DataGridView - DataGridViewCheckBoxCell的使用介紹
Datagridview是.net中最復(fù)雜的控件,Datagridview中,用戶可以對行、列、單元格進(jìn)行編程,下面與大家分享下DataGridViewCheckBoxCell的使用,感興趣的朋友可以參考下哈2013-06-06
使用vs2019加.net core 對WeiApi的創(chuàng)建過程詳解
這篇文章主要介紹了使用vs2019加.net core 對WeiApi的創(chuàng)建,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
ASP.Net MVC 布局頁、模板頁使用方法詳細(xì)介紹
這篇文章主要介紹了ASP.Net MVC 布局頁、模板頁使用方法詳細(xì)介紹,需要的朋友可以參考下2017-08-08
在asp.net中使用加密數(shù)據(jù)庫聯(lián)接字符串保證數(shù)據(jù)安全
加密web.config可以有效保證數(shù)據(jù)庫用戶和密碼安全,下面是示例代碼,大家可以看看2014-09-09
DataTable類Clone方法與Copy方法的區(qū)別分析
初學(xué)者可能不清楚DataTable類的Clone及Copy方法的區(qū)別,查msdn,可得到如下結(jié)論2013-03-03

