مدیریت خطای سراسری در ASP.NET Core: از Middleware تا Handlerهای مدرن 🚨
بیایید در مورد چیزی صحبت کنیم که همه ما با آن سر و کار داریم اما اغلب تا آخرین لحظه به تعویق میاندازیم - مدیریت خطا در اپلیکیشنهای ASP.NET Core ما.
وقتی چیزی در پروداکشن خراب میشود، آخرین چیزی که میخواهید یک خطای مبهم 500 بدون هیچ زمینهای است. مدیریت خطای مناسب فقط مربوط به لاگ کردن استثناها نیست. بلکه در مورد این است که مطمئن شوید اپلیکیشن شما به آرامی شکست میخورد و اطلاعات مفیدی به فراخواننده (و شما) میدهد.
در این مقاله، گزینههای اصلی برای مدیریت خطای سراسری در ASP.NET Core را بررسی خواهیم کرد.
مدیریت خطای مبتنی بر Middleware 📜
روش کلاسیک برای گرفتن استثناهای کنترلنشده، استفاده از Middleware سفارشی است. اینجاست که اکثر ما شروع میکنیم، و صادقانه بگویم، هنوز هم برای اکثر سناریوها عالی کار میکند.
internal sealed class GlobalExceptionHandlerMiddleware(
RequestDelegate next,
ILogger<GlobalExceptionHandlerMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
logger.LogError(ex, "Unhandled exception occurred");
// حتماً قبل از نوشتن در بدنه پاسخ، کد وضعیت را تنظیم کنید
context.Response.StatusCode = ex switch
{
ApplicationException => StatusCodes.Status400BadRequest,
_ => StatusCodes.Status500InternalServerError
};
await context.Response.WriteAsJsonAsync(
new ProblemDetails
{
Type = ex.GetType().Name,
Title = "An error occured",
Detail = ex.Message
});
}
}
}
فراموش نکنید که middleware را به پایپلاین درخواست اضافه کنید:
app.UseMiddleware<GlobalExceptionHandlerMiddleware>();
این رویکرد محکم است و در همه جای پایپلاین شما کار میکند. زیبایی آن در سادگیاش است: همه چیز را در یک try-catch بپیچید، خطا را لاگ کنید و یک پاسخ یکپارچه برگردانید.
اما وقتی شروع به افزودن قوانین خاص برای انواع مختلف استثناها میکنید (مانند ValidationException, NotFoundException)، این به یک آشفتگی تبدیل میشود.
معرفی IProblemDetailsService 📄
مایکروسافت این نقطه ضعف را تشخیص داد و IProblemDetailsService را برای استانداردسازی پاسخهای خطا به ما داد. به جای سریالسازی دستی آبجکتهای خطای خودمان، میتوانیم از فرمت داخلی Problem Details استفاده کنیم.
// ...
catch (Exception ex)
{
logger.LogError(ex, "Unhandled exception occurred");
context.Response.StatusCode = ex switch
{
ApplicationException => StatusCodes.Status400BadRequest,
_ => StatusCodes.Status500InternalServerError
};
await problemDetailsService.TryWriteAsync(new ProblemDetailsContext
{
HttpContext = context,
Exception = ex,
ProblemDetails = new ProblemDetails
{
Type = ex.GetType().Name,
Title = "An error occured",
Detail = ex.Message
}
});
}
// ...
این خیلی تمیزتر است. ما اکنون از یک فرمت استاندارد استفاده میکنیم که مصرفکنندگان API انتظار دارند. اما هنوز با مشکل آن switch statement در حال رشد گیر کردهایم.