Middleware — nima, pipeline, tartib, custom middleware yaratish

~25 daq · .NET Backend · ASP.NET Core — Kirish

Muammodan boshlaylik

Har bir API so'rovi uchun kerak:

  • So'rov qancha vaqt olganini log qilish
  • Har bir so'rovga noyob ID berish va uni javob sarlavhasiga qo'shish
  • Xizmat ko'rsatish rejimida barcha so'rovlarga 503 qaytarish

Har bir controller va har bir action ga bu kodni yozish — yuzlab takrorlanish va bittasi albatta unutiladi. Kerak: barcha so'rovlar o'tadigan bitta joy.


Middleware nima?

Middleware — HTTP so'rovi va javobi o'tadigan zanjirdagi komponent. Har biri:

  1. So'rov bilan biror narsa qiladi
  2. Keyingi middleware ni chaqiradi (yoki zanjirni to'xtatib, o'zi javob beradi)
  3. Javob qaytayotganda yana biror narsa qilishi mumkin
So'rov ──▶ [ Log ] ──▶ [ Auth ] ──▶ [ Controller ]
              │           │               │
Javob  ◀──────┴───────────┴───────────────┘

37-darsdagi app.UseHttpsRedirection(), app.UseAuthentication() — o'rnatilgan middleware lar.


Hayotiy o'xshatma

Aeroportdagi yo'lovchi yo'li: chipta tekshiruvi → bagaj topshirish → xavfsizlik nazorati → pasport nazorati → samolyot. Har bir punkt o'z ishini qiladi va keyingisiga o'tkazadi. Xavfsizlik taqiqlangan narsa topsa — yo'lovchi samolyotga yetib bormaydi (zanjir to'xtatildi). Qaytishda esa yo'l teskari tartibda.


Inline middleware — app.Use

app.Use(async (context, next) =>
{
    var vaqt = Stopwatch.StartNew();

    await next(context);          // keyingi middleware va controller

    vaqt.Stop();
    Console.WriteLine(
quot;{context.Request.Method} {context.Request.Path} → {context.Response.StatusCode} ({vaqt.ElapsedMilliseconds} ms)"); });
  • next(context) dan oldingi kod — so'rov ketayotganda
  • next(context) dan keyingi kod — javob qaytayotganda
  • context — HttpContext: Request, Response, User, Items

Zanjirni to'xtatish

app.Use(async (context, next) =>
{
    if (builder.Configuration.GetValue<bool>("XizmatRejimi"))
    {
        context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
        await context.Response.WriteAsync("Texnik ishlar olib borilmoqda");
        return;                    // next chaqirilmaydi — controller ga yetib bormaydi
    }
    await next(context);
});

app.Run — oxirgi middleware

app.Run(async context => await context.Response.WriteAsync("Hech narsa topilmadi"));

Run — next siz, zanjirning oxiri.


Klass sifatida middleware

Kattaroq va qayta ishlatiladigan mantiq uchun:

public class SorovIdMiddleware(RequestDelegate next, ILogger<SorovIdMiddleware> logger)
{
    private const string Sarlavha = "X-Request-Id";

    public async Task InvokeAsync(HttpContext context)
    {
        var id = context.Request.Headers[Sarlavha].FirstOrDefault() ?? Guid.NewGuid().ToString("N");

        context.TraceIdentifier = id;
        context.Response.OnStarting(() =>
        {
            context.Response.Headers[Sarlavha] = id;
            return Task.CompletedTask;
        });

        using (logger.BeginScope(new Dictionary<string, object> { ["RequestId"] = id }))
        {
            await next(context);
        }
    }
}
// Program.cs
app.UseMiddleware<SorovIdMiddleware>();
  • Konstruktor: RequestDelegate next + singleton bog'liqliklar (middleware bir marta yaratiladi)
  • InvokeAsync(HttpContext context, ...) — har so'rovda; scoped servislar (masalan, DbContext) shu metod parametrlari orqali
public async Task InvokeAsync(HttpContext context, DokonDbContext db)   // scoped — shu yerda

Extension metod bilan

public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseSorovId(this IApplicationBuilder app) =>
        app.UseMiddleware<SorovIdMiddleware>();
}

app.UseSorovId();

Tartib — eng muhim qoida

app.UseExceptionHandler("/error");    // 1 — keyingi hammasining xatolarini ushlaydi
app.UseSorovId();                     // 2 — log larda ID bo'lishi uchun erta
app.UseHttpsRedirection();
app.UseCors();
app.UseAuthentication();              // Authorization dan OLDIN
app.UseAuthorization();
app.MapControllers();

[!WARNING] Middleware lar yozilgan tartibda bajariladi. Keng tarqalgan xatolar: UseAuthorization UseAuthentication dan oldin (hamma 401), UseCors controller lardan keyin (CORS sarlavhalari qo'shilmaydi), xato ushlovchi pipeline oxirida (undan oldingi xatolar ushlanmaydi). Shubha bo'lsa — rasmiy hujjatdagi tartib jadvaliga qarang.

[!INFO] await next(context) dan keyin javob allaqachon yuborilgan bo'lishi mumkin. Shu paytda context.Response.Headers ni o'zgartirish — InvalidOperationException: Headers are read-only, response has already started. Sarlavha qo'shish uchun — Response.OnStarting(...) (yuqoridagi misolda).


Middleware yoki filtr?

Middleware Filtr (action filter)
Qayerda Barcha so'rovlar (statik fayllar ham) Faqat controller/action lar
Nimani biladi HttpContext Qaysi controller, action, model, argumentlar
Qachon Log, so'rov ID, xatolar, CORS, rate limit Action ga xos: validatsiya, audit, natijani o'zgartirish

Tez-tez uchraydigan xatolar

next ni chaqirishni unutish:

app.Use(async (context, next) =>
{
    Console.WriteLine("Log");
    // ❌ await next(context) yo'q — hamma so'rovlar shu yerda bo'sh javob bilan tugaydi
});

Javob boshlangandan keyin sarlavha qo'shish — OnStarting.

Middleware konstruktorida scoped servis — captive dependency (38-dars); InvokeAsync parametriga o'tkazing.


Xulosa

Middleware — barcha so'rovlar o'tadigan zanjir bo'g'ini: so'rovda ishlaydi, next ni chaqiradi yoki zanjirni to'xtatib o'zi javob beradi, javob qaytayotganda yana ishlaydi. Inline — app.Use(async (ctx, next) => ...), oxirgisi — app.Run.

Klass middleware — RequestDelegate next + InvokeAsync(HttpContext); konstruktorda singleton, InvokeAsync parametrlarida scoped bog'liqliklar; app.UseMiddleware<T>(). Tartib hal qiluvchi: xatolar → ... → Authentication → Authorization → endpoint lar. Sarlavhalar — Response.OnStarting.


Keyingi dars

40-dars: Configuration — appsettings.json, environment, User Secrets, IOptions