Lesson 0005 · Backend · ±15 menit

Middleware pipeline, urutannya, dan Options Pattern

Jalur yang dilewati setiap request, kenapa satu baris yang tertukar bisa membuka celah keamanan, dan cara Service membaca konfigurasi tanpa menyentuh secret

Target lesson ini: Anda bisa menjawab pertanyaan interview "Jelaskan apa yang terjadi pada sebuah request di ASP.NET Core dari masuk sampai response keluar, dan kenapa urutan middleware penting" dalam 60–90 detik, lalu menyambungnya ke "di mana secret disimpan".

1. Pipeline: rangkaian middleware yang membungkus endpoint

Di lesson 0001 request "masuk" ke Controller. Sebenarnya sebelum sampai ke sana, request melewati Middleware. Microsoft: "Middleware is software that's assembled into an app pipeline to handle requests and responses. Each middleware: Chooses whether to pass the request to the next middleware in the pipeline. Can perform work before and after the next middleware in the pipeline." [1]

request→ A (sebelum next)→ B (sebelum next)→ endpoint / Controller→ B (sesudah next)→ A (sesudah next)→ response

"The order that middleware appears in the app's Program file defines the order in which middleware are invoked on a request with the reverse order for the response." [1] Bayangkan bawang: masuk lapis demi lapis, keluar lewat lapis yang sama dengan urutan terbalik.

app.Use(async (context, next) =>
{
    var sw = Stopwatch.StartNew();          // sebelum: request masuk
    await next(context);                    // teruskan ke middleware berikutnya
    logger.LogInformation("{Path} {Ms}ms",  // sesudah: response kembali
        context.Request.Path, sw.ElapsedMilliseconds);
});
ASP.NET CoreArtinyaPadanan Express
app.Use((context, next) => …)middleware biasa, boleh memanggil nextapp.use((req, res, next) => …)
short-circuit: tidak memanggil nextpipeline berhenti di sini; middleware ini menjadi terminal middleware [1]res.status(401).send() tanpa next()
app.Run(context => …)terminal: tidak menerima next; Use sesudahnya "never executes" [1]handler terakhir yang selalu mengirim response

Express juga mendefinisikan middleware sebagai fungsi dengan akses ke req, res, dan next, dan "If a middleware function does not end the request-response cycle, it must call next()". [7] Bedanya di ASP.NET Core: await next(context) menunggu seluruh sisa pipeline selesai, jadi kode sesudahnya bisa membaca status response.

Latihan 1 — Cara kerja pipeline

Jawab tanpa scroll ke atas. Soal yang sama muncul lagi, diacak, di review campuran 0007.

Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil di review campuran 0007.

2. Urutan yang dipakai API Product Catalog

"The order of middleware can be critical for security, performance, and functionality." [1] Untuk Web API yang dipanggil Angular, urutan dari dokumentasi menjadi:

var app = builder.Build();

app.UseExceptionHandler("/error");   // 1. paling luar: membungkus semuanya
app.UseHttpsRedirection();           // 2. http → https
app.UseRouting();                    // 3. pilih endpoint
app.UseCors("angular");              // 4. header CORS + preflight
app.UseAuthentication();             // 5. SIAPA user ini? (isi HttpContext.User)
app.UseAuthorization();              // 6. BOLEH atau tidak?
app.MapControllers();                // 7. endpoint: Controller → Service → Repository
app.Run();

Detail .NET 8 yang jarang diketahui: WebApplication menambahkan UseRouting, UseAuthentication, dan UseAuthorization otomatis bila Anda tidak memanggilnya. Tapi kalau Anda memakai CORS, "The app needs to call UseAuthentication and UseAuthorization if UseCors is called", karena yang otomatis itu diletakkan tepat sesudah UseRouting, yaitu sebelum UseCors Anda. [1]

Latihan 2 — Susun ulang pipeline

Baris Program.cs di bawah sudah diacak. Pindahkan dengan ↑/↓ sampai urutannya benar, lalu tekan Periksa. Coba dari ingatan dulu.

  1. app.UseExceptionHandler("/error")
  2. app.UseHttpsRedirection()
  3. app.UseRouting()
  4. app.UseCors("angular")
  5. app.UseAuthentication()
  6. app.UseAuthorization()
  7. app.MapControllers()

Exception handler paling luar supaya menangkap exception dari semua yang sesudahnya. Routing memilih endpoint, CORS harus sesudah routing dan sebelum authorization, authentication mengisi HttpContext.User sebelum authorization memutuskan, dan endpoint (Controller) paling dalam.

3. Service Scoped di dalam middleware

Di lesson 0004 Anda belajar captive dependency. Middleware adalah contoh nyatanya: "Middleware is constructed at app startup and therefore has application life time." Jadi service Scoped tidak boleh diterima lewat constructor. "To share a scoped service between middleware and other types, add these services to the InvokeAsync method's signature." [2]

public class RequestAuditMiddleware(RequestDelegate next, ILogger<RequestAuditMiddleware> logger)
{   // constructor: hanya dependensi berumur panjang (Singleton)

    public async Task InvokeAsync(HttpContext context, AppDbContext db)  // Scoped: per request
    {
        await next(context);
        db.AuditLogs.Add(new AuditLog(context.Request.Path, context.Response.StatusCode));
        await db.SaveChangesAsync();
    }
}

// Program.cs
app.UseMiddleware<RequestAuditMiddleware>();

Syarat middleware class: "A public constructor with a parameter of type RequestDelegate" dan "A public method named Invoke or InvokeAsync" yang mengembalikan Task dengan parameter pertama HttpContext. [2] Padanan Express: fungsi middleware dibuat sekali saat app.use(...), sedangkan objek per request Anda ambil dari req.

Latihan 3 — Scoped di middleware

4. Konfigurasi berlapis: siapa menimpa siapa

WebApplication.CreateBuilder menggabungkan beberapa sumber. Dari prioritas tertinggi ke terendah: [3]

  1. argumen command-line
  2. environment variable (yang tidak berawalan ASPNETCORE_ / DOTNET_)
  3. User Secrets, "when the app runs in the Development environment"
  4. appsettings.{Environment}.json (mis. appsettings.Production.json)
  5. appsettings.json

Sumber yang dibaca belakangan menimpa yang sebelumnya: "key values read from the environment override values read from appsettings.json, appsettings.{ENVIRONMENT}.json, and user secrets." [3] Di environment variable, hierarki ditulis dengan double underscore: Jwt__Key menjadi Jwt:Key, karena "The colon (:) separator doesn't work with environment variable hierarchical keys on all platforms." [3]

Di mana secret disimpan?

Padanan Node: appsettings.json ≈ default di modul config/; User Secrets ≈ file .env yang masuk .gitignore, bedanya ia disimpan di luar folder project; environment variable sama-sama process.env.

Latihan 4 — Susun prioritas konfigurasi

Urutkan dari prioritas terendah (paling mudah ditimpa) ke tertinggi, untuk app yang jalan di Development.

  1. appsettings.json
  2. appsettings.Development.json
  3. User Secrets
  4. Environment variable
  5. Argumen command-line

Setiap sumber menimpa sumber di atasnya. User Secrets terdaftar sesudah file appsettings, sehingga "user secrets keys take precedence over keys in appsettings.json and appsettings.{ENVIRONMENT}.json." Command-line paling akhir, jadi paling kuat.

5. Options Pattern: Service membaca class, bukan string

"The options pattern uses classes to provide strongly typed access to groups of related settings." Dua prinsip yang terpenuhi: Encapsulation ("Classes that depend on configuration settings depend only on the configuration settings that they use") dan Separation of Concerns. [4]

// appsettings.json: { "Jwt": { "Issuer": "catalog-api", "Key": "" } }   ← Key diisi dari User Secrets / env
public class JwtOptions
{
    public const string Section = "Jwt";        // field: tidak di-bind
    [Required] public string Issuer { get; set; } = "";
    [Required] public string Key { get; set; } = "";
}

// Program.cs (composition root)
builder.Services.AddOptions<JwtOptions>()
    .Bind(builder.Configuration.GetSection(JwtOptions.Section))
    .ValidateDataAnnotations()
    .ValidateOnStart();                         // gagal saat start, bukan saat request pertama

// Service
public class TokenService(IOptions<JwtOptions> options)
{
    private readonly JwtOptions _jwt = options.Value;
}

Bentuk singkatnya, tanpa validasi: builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection("Jwt")); [4] Tanpa ValidateOnStart, "Options validation runs the first time a TOption instance is created". [4]

InterfaceLifetimeMembaca perubahan setelah start?Pakai untuk
IOptions<T>Singletontidak: "Does not support: Reading of configuration data after the app has started"default; setting yang tetap
IOptionsSnapshot<T>Scoped: "so it can't be injected into a singleton service"ya, dihitung ulang per requestService Scoped yang butuh nilai terbaru per request
IOptionsMonitor<T>Singletonya: "Reloadable configuration", change notificationSingleton / BackgroundService yang butuh nilai terbaru

Kutipan tabel: [4] Options interfaces. Perhatikan IOptionsSnapshot + Singleton = captive dependency lagi.

Latihan 5 — Konfigurasi dan Options

Latihan 6 — Ucapkan dengan suara keras

Pasang timer 90 detik dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.

"Jelaskan apa yang terjadi pada sebuah request di ASP.NET Core dari masuk sampai response keluar, dan kenapa urutan middleware penting."
Bandingkan dengan jawaban contoh dan checklist-nya

"Request masuk ke pipeline middleware yang disusun di Program.cs. Setiap middleware bisa mengerjakan sesuatu sebelum memanggil next, lalu sesudahnya saat response kembali, jadi urutannya seperti bawang: masuk A-B-C, keluar C-B-A. Middleware juga bisa short-circuit, misalnya langsung mengembalikan 401 tanpa memanggil next. Karena itu urutan menentukan perilaku: exception handler dipasang paling awal supaya membungkus semua yang sesudahnya, kebalikan dari Express di mana error handler ditaruh paling akhir. Lalu HTTPS redirection, routing, CORS, authentication, authorization, baru endpoint seperti MapControllers. Authentication harus sebelum authorization karena kita harus tahu siapa user-nya dulu sebelum memutuskan boleh atau tidak, dan CORS sebelum keduanya. Middleware dibuat sekali seumur aplikasi, jadi service Scoped seperti DbContext diterima lewat parameter InvokeAsync, bukan constructor."

Checklist: apakah Anda menyebut…

  • pipeline = rangkaian middleware yang disusun di Program.cs, dijalankan berurutan?
  • setiap middleware bisa bekerja sebelum dan sesudah next (masuk A-B-C, keluar C-B-A)?
  • middleware bisa short-circuit (tidak memanggil next), contoh 401?
  • exception handler paling awal supaya menangkap exception dari middleware sesudahnya (beda dengan Express)?
  • urutan inti: routing → CORS → authentication → authorization → endpoint?
  • middleware dibuat sekali, jadi service Scoped diterima di parameter InvokeAsync?

Lulus kalau minimal 4 dari 6. Kalau kurang, ulangi besok pagi sebelum lesson berikutnya (spacing).

Bonus 60 detik

"Di mana Anda menyimpan connection string dan secret, dan bagaimana Service membacanya?"
Bandingkan dengan jawaban contoh dan checklist-nya

"Konfigurasi di ASP.NET Core berlapis. Paling bawah appsettings.json, lalu ditimpa appsettings.Development.json atau appsettings.Production.json sesuai environment, lalu User Secrets kalau di Development, lalu environment variable, dan paling atas argumen command-line. Secret tidak pernah saya taruh di appsettings yang di-commit. Saat development saya pakai dotnet user-secrets, yang disimpan di profil user di luar repo tapi tidak dienkripsi. Di production pakai environment variable, misalnya ConnectionStrings__Default dengan double underscore, atau Key Vault. Service tidak membaca IConfiguration langsung; saya buat class seperti JwtOptions, daftarkan dengan Configure<JwtOptions>(GetSection("Jwt")), lalu inject IOptions<JwtOptions>, supaya bertipe kuat dan tiap class hanya tahu setting miliknya. Kalau nilainya bisa berubah saat runtime saya pakai IOptionsMonitor. Padanannya di Node: process.env plus modul config/ yang divalidasi."

Checklist: apakah Anda menyebut…

  • lapisan: appsettings.json → appsettings.{Environment}.json → User Secrets → environment variable → command-line?
  • sumber yang belakangan menimpa yang sebelumnya?
  • secret tidak di appsettings yang di-commit; development pakai User Secrets (di luar repo, tidak terenkripsi)?
  • production: environment variable (pakai __ untuk hierarki) atau secret store seperti Key Vault?
  • Options Pattern: class bertipe + Configure<T>(GetSection) + inject IOptions<T>?
  • IOptions vs IOptionsSnapshot (Scoped, per request) vs IOptionsMonitor (Singleton, reload)?

Lulus kalau minimal 4 dari 6. Kedua pertanyaan ini terdaftar di bank soal dan bisa muncul di review 0007.

Bacaan utama (±10 menit) Microsoft Learn: ASP.NET Core middleware (.NET 8), terutama gambar pipeline di awal dan bagian "Middleware order". Untuk konfigurasi, baca bagian "Options interfaces" di halaman Options pattern.

Ringkasan untuk dicetak: Reference: Middleware dan Options Pattern · Istilah: GLOSSARY.md

Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "kenapa preflight CORS bisa gagal kalau UseCors ditaruh sesudah UseAuthorization?" atau "kapan saya butuh IOptionsSnapshot, bukan IOptions?"

Sumber: [1] ASP.NET Core middleware (.NET 8) · [2] Write custom ASP.NET Core middleware · [3] Configuration in ASP.NET Core · [4] Options pattern in ASP.NET Core · [5] Safe storage of app secrets in development · [6] Enable CORS in ASP.NET Core · [7] Express: Using middleware · [8] Express: Error handling