目錄
前言:
本文使用 .NET Core SDK 3.1 的版本,介紹了處理 ASP.NET Core Web 應用中常見異常的一些方法。
本文 Demo 中貫穿全文的代碼如下:
#region Enums
public enum ResultState
{
請求成功 = 200, 未登錄 = 300, 業務錯誤 = 500, 未知錯誤 = 999
}
#endregion
#region Exceptions
public interface IKnownException
{
public ResultState Code { get; set; }
public string Message { get; }
public object Result { get; }
}
public class KnownException : Exception, IKnownException
{
public KnownException(string message) : base(message) { }
public KnownException(string message, object result) : base(message) { Result = result; }
public ResultState Code { get; set; }
public object Result { get; }
}
public class KnownExceptionMessage : IKnownException
{
public ResultState Code { get; set; }
public string Message { get; private set; }
public object Result { get; private set; }
public readonly static IKnownException UnKnown = new KnownExceptionMessage { Message = "未知錯誤", Code = ResultState.未知錯誤 };
public IKnownException FromKnownException(IKnownException knownException)
{
return new KnownExceptionMessage { Code = knownException.Code, Message = knownException.Message, Result = knownException.Result };
}
}
#endregion
一、開發人員異常頁
開發人員異常頁 顯示請求異常的詳細信息。
向 Startup.Configure
方法添加代碼,以當應用在開發環境中運行時啓用此頁:
if (env.IsDevelopment())
{
// 開發人員異常頁
app.UseDeveloperExceptionPage();
}
根據中間件管道的順序,將 UseDeveloperExceptionPage` 調用置於要捕獲其異常的任何中間件前面。
僅當應用程序在開發環境中運行時才啓用開發人員異常頁 。
二、異常處理程序頁
爲生產環境配置自定義錯誤處理頁,使用異常處理中間件。
使用 UseExceptionHandler
在非開發環境中添加異常處理中間件:
if (env.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler("/Error");
異常的 Controller
如下:
[ApiController]
public class ErrorController : ControllerBase
{
private readonly JsonSerializerOptions jsonSerializerOptions;
public ErrorController(IOptionsMonitor<JsonOptions> jsonOptins)
{
jsonSerializerOptions = jsonOptins.CurrentValue.JsonSerializerOptions;
}
[Route("Error")]
[AllowAnonymous]
public async Task Error()
{
var exceptionHandlerPathFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
// var originalRequestPath = exceptionHandlerPathFeature?.Path; // 原始路徑
var knownException = exceptionHandlerPathFeature.Error as IKnownException; //
if (knownException == null)
{
// 未知異常 Http 響應碼 500
HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
else
{
knownException.Code = ResultState.業務錯誤;
// 業務邏輯異常 Http 響應碼 200
HttpContext.Response.StatusCode = StatusCodes.Status200OK;
}
HttpContext.Response.ContentType = "application/json;";
await HttpContext.Response.WriteAsync(
JsonSerializer.Serialize(knownException, jsonSerializerOptions), Encoding.UTF8);
}
}
使用 [AllowAnonymous]
允許匿名訪問,未身份驗證也能夠訪問。
上面代碼使用 IExceptionHandlerPathFeature
訪問上下文報出的異常信息和原始路徑。
如果我們使用以下代碼:
[HttpGet]
public int[] Index()
{
throw new KnownException("this is message", "this is result");
return new int[] { 1, 2, 3 };
}
前端將得到如下JSON:
{"code":500,"message":"this is message","result":"this is result"}
如果返回值中有中文可能會被轉換爲 Unicode,請修改 JsonSerializerOptions
的 Encoder
屬性爲 JavaScriptEncoder.UnsafeRelaxedJsonEscaping
。
#region Startup.ConfigureServices
services.AddControllers() // services.AddMvc()
.AddJsonOptions(jsonOptions =>
{
jsonOptions.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
});
#endregion
or
#region
var jsonSerializerOptions = context.RequestServices.GetService<IOptionsMonitor<JsonOptions>>()
.CurrentValue.JsonSerializerOptions;
jsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
#endregion
三、異常處理程序 lambda
異常處理程序頁 的替代方法可以向 UseExceptionHandler
提供 lambda,也可以在相應前訪問上下文報出的異常。
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
// var originalRequestPath = exceptionHandlerPathFeature?.Path;
var knownException = exceptionHandlerPathFeature.Error as IKnownException;
if (knownException == null)
{
// 未知異常 Http 響應碼 500
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
else
{
knownException.Code = ResultState.業務錯誤;
// 業務邏輯異常 Http 響應碼 200
context.Response.StatusCode = StatusCodes.Status200OK;
}
var jsonSerializerOptions = context.RequestServices.GetService<IOptionsMonitor<JsonOptions>>()
.CurrentValue.JsonSerializerOptions;
context.Response.ContentType = "application/json;";
await context.Response.WriteAsync(
JsonSerializer.Serialize(knownException, jsonSerializerOptions), Encoding.UTF8);
});
});
四、異常過濾器 IExceptionFilter
異常過濾器只作用於 MVC 的生命週期,
如果我們需要對 Controller
進行特殊異常處理,對整體來講又需要用整體的異常出來,可以用 IExceptionFilter
。
1) 直接實現 IExceptionFilter 的方式
#region Startup.ConfigureServices
services.AddControllers(mvcOptions =>
{
mvcOptions.Filters.Add<MyExceptionFilter>();
}).AddJsonOptions(jsonOptions =>
{
// 如果返回值中有中文可能會被轉換爲 Unicode 。
jsonOptions.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
});
#endregion
#region MyExceptionFilter
public class MyExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
var knownException = context.Exception as IKnownException;
if (knownException == null)
{
knownException = KnownExceptionMessage.UnKnown;
context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
else
{
knownException.Code = ResultState.業務錯誤;
knownException = new KnownExceptionMessage().FromKnownException(knownException);
context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
}
context.Result = new JsonResult(knownException)
{
ContentType = "application/json; charset=utf-8"
};
}
}
#endregion
2) 繼承 ExceptionFilterAttribute 的方式
ExceptionFilterAttribute
也實現了 IExceptionFilter
接口。
寫法和 直接實現 IExceptionFilter 的方式 一樣的
public class MyExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var knownException = context.Exception as IKnownException;
if (knownException == null)
{
knownException = KnownExceptionMessage.UnKnown;
context.HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
else
{
knownException.Code = ResultState.業務錯誤;
knownException = new KnownExceptionMessage().FromKnownException(knownException);
context.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
}
context.Result = new JsonResult(knownException)
{
ContentType = "application/json; charset=utf-8"
};
}
}
在使用時,我們可以直接攔截所有的 Controller
的錯誤:
services.AddControllers(mvcOptions =>
{
mvcOptions.Filters.Add<MyExceptionFilterAttribute>();
});
也可以使用註解的方式單獨攔截某個 Controller
:
[ApiController]
[MyExceptionFilter]
public class HomeController : ControllerBase { /* Do something... */}