Global Exception Handling — middleware, ProblemDetails, RFC 7807
Muammodan boshlaylik
Har bir action try/catch bilan o'ralgan:
[HttpGet("{id:int}")]
public async Task<IActionResult> Ol(int id)
{
try
{
var m = await service.OlAsync(id);
return Ok(m);
}
catch (TopilmadiException) { return NotFound(); }
catch (Exception ex)
{
return StatusCode(500, ex.ToString()); // stack trace mijozga!
}
}
- Bir xil kod 40 ta action da takrorlangan
- Biror joyda catch unutilsa — mijoz tushunarsiz javob oladi
ex.ToString()— stack trace, fayl yo'llari va SQL so'rovlari hujumchiga ko'rinadi- Har bir endpoint xatoni o'z formatida qaytaradi
Hayotiy o'xshatma
Kasalxonaning qabul bo'limi. Har bir shifokor o'z kabinetida favqulodda holatlar uchun alohida reanimatsiya qurmaydi — muammo bo'lsa, bemor yagona qabul bo'limiga yuboriladi. U yerda standart tartib bor: holat baholanadi, hujjatlashtiriladi (log), qarindoshlarga tushunarli va bir xil shakldagi ma'lumot beriladi (ProblemDetails) — tibbiy tarix tafsilotlarisiz.
ProblemDetails — standart xato formati
RFC 9457 (avval RFC 7807) — HTTP API xatolari uchun standart JSON:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Not Found",
"status": 404,
"detail": "42 raqamli mahsulot topilmadi",
"instance": "/api/mahsulotlar/42",
"traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}
Mijoz har doim bir xil tuzilmani oladi; Content-Type: application/problem+json.
builder.Services.AddProblemDetails();
Shundan keyin NotFound(), BadRequest() va validatsiya xatolari ham ProblemDetails ko'rinishida qaytadi.
Domen exception lari
public abstract class IlovaException(string xabar) : Exception(xabar);
public class TopilmadiException(string resurs, object id)
: IlovaException(quot;{id} raqamli {resurs} topilmadi");
public class BiznesQoidaException(string xabar) : IlovaException(xabar);
public class ZiddiyatException(string xabar) : IlovaException(xabar);
Servis — HTTP haqida bilmaydi, faqat ma'noli exception tashlaydi:
public async Task<MahsulotDto> OlAsync(int id, CancellationToken ct) =>
(await db.Mahsulotlar.FindAsync([id], ct))?.ToDto()
?? throw new TopilmadiException("mahsulot", id);
public async Task SotAsync(int id, int soni, CancellationToken ct)
{
var m = await db.Mahsulotlar.FindAsync([id], ct) ?? throw new TopilmadiException("mahsulot", id);
if (m.Qoldiq < soni) throw new BiznesQoidaException(quot;Omborda faqat {m.Qoldiq} dona bor");
// ...
}
Controller — try/catch siz:
[HttpGet("{id:int}")]
public async Task<MahsulotDto> Ol(int id, CancellationToken ct) => await service.OlAsync(id, ct);
IExceptionHandler (.NET 8+)
using Microsoft.AspNetCore.Diagnostics;
public class IlovaExceptionHandler(
IProblemDetailsService pds,
ILogger<IlovaExceptionHandler> log) : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext ctx, Exception ex, CancellationToken ct)
{
var (status, title) = ex switch
{
TopilmadiException => (StatusCodes.Status404NotFound, "Topilmadi"),
BiznesQoidaException => (StatusCodes.Status422UnprocessableEntity, "Biznes qoidasi buzildi"),
ZiddiyatException => (StatusCodes.Status409Conflict, "Ziddiyat"),
_ => (StatusCodes.Status500InternalServerError, "Server xatosi")
};
if (status == 500)
log.LogError(ex, "Kutilmagan xato: {Path}", ctx.Request.Path);
else
log.LogInformation("Domen xatosi {Tur}: {Xabar}", ex.GetType().Name, ex.Message);
ctx.Response.StatusCode = status;
return await pds.TryWriteAsync(new ProblemDetailsContext
{
HttpContext = ctx,
Exception = ex,
ProblemDetails =
{
Status = status,
Title = title,
Detail = ex is IlovaException ? ex.Message : "Kutilmagan xato yuz berdi. Keyinroq urinib ko'ring."
}
});
}
}
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<IlovaExceptionHandler>();
var app = builder.Build();
app.UseExceptionHandler(); // pipeline boshida
app.UseStatusCodePages(); // body siz 404/405 larga ham ProblemDetails
Bir nechta handler ro'yxatdan o'tkazish mumkin — ular tartib bilan chaqiriladi, birinchi true qaytargani xatoni "qabul qiladi".
[!WARNING] 500 xatolarda
ex.Messageni mijozga qaytarmang — unda ulanish satri, jadval nomlari yoki ichki tafsilotlar bo'lishi mumkin. Mijozga — umumiy xabar vatraceId; to'liq ma'lumot — faqat log da.traceIdorqali qo'llab-quvvatlash xizmati log dan aniq xatoni topadi.
ProblemDetails ni boyitish
builder.Services.AddProblemDetails(o =>
{
o.CustomizeProblemDetails = ctx =>
{
ctx.ProblemDetails.Instance = quot;{ctx.HttpContext.Request.Method} {ctx.HttpContext.Request.Path}";
ctx.ProblemDetails.Extensions["traceId"] = Activity.Current?.Id ?? ctx.HttpContext.TraceIdentifier;
};
});
Development va Production
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage(); // to'liq stack trace — faqat lokal
else
app.UseExceptionHandler();
[!TIP] .NET 8+ da Development muhitida
UseDeveloperExceptionPagesukut bo'yicha yoqilgan. Production da hech qachon — stack trace hujumchi uchun xarita.
Exception — oqimni boshqarish uchun emas
Kutiladigan holatlar uchun exception qimmat (stack trace yig'iladi). Juda tez-tez bo'ladigan holatlarda (masalan, validatsiya) — natija turi:
public record Natija<T>(T? Qiymat, string? Xato)
{
public bool Muvaffaqiyatli => Xato is null;
public static Natija<T> Ok(T q) => new(q, null);
public static Natija<T> Xatolik(string x) => new(default, x);
}
Amaliy kelishuv: "topilmadi" va biznes qoidalar — exception bilan global handler ga, validatsiya — FluentValidation/ModelState bilan (46–47-darslar).
Tez-tez uchraydigan xatolar
UseExceptionHandler pipeline oxirida — undan oldingi middleware xatolari ushlanmaydi; eng boshiga qo'ying.
Exception ni yutib yuborish — catch { } — xato yo'qoladi, log yo'q.
Har bir controller da try/catch + global handler — ikki xil format; bittasini tanlang.
Xulosa
Global exception handling — try/catch ni har bir action dan olib, bitta joyga yig'ish. ProblemDetails (RFC 9457) — standart xato JSON: type, title, status, detail, instance; AddProblemDetails().
IExceptionHandler (.NET 8+): exception turiga qarab status kodi (404, 409, 422, 500), log va IProblemDetailsService bilan javob; AddExceptionHandler<T>() + UseExceptionHandler(). Servislar — domen exception lari, controller lar — try/catch siz. 500 da ichki tafsilot emas — umumiy xabar va traceId.
Keyingi dars
66-dars: Logging — ILogger, log darajalari, Serilog