Skip to content

Bölüm 6 / 14

ASP.NET Core'a giriş

Host, Program.cs, istek boru hattı (pipeline).

10 dakikalık okuma

Şimdiye kadar konsol uygulamaları yazdın; bu ders sunucuya geçiş noktasıdır. Bir web uygulaması, sürekli çalışan ve gelen istekleri karşılayan bir programdır — yani ana akışı senin döngün değil, gelen isteklerdir. Bu dersin hedefi, Program.cs'teki on beş satırın her birinin ne yaptığını ve bir HTTP isteğinin soketten yanıta kadar hangi durakları geçtiğini net biçimde görmen.

01Ne öğreneceksin

  • Barındırıcı (host) kavramı: uygulamanın ömrünü kim yönetir
  • Program.cs'in iki yarısı: servis kaydı ve boru hattı kurulumu
  • Kestrel nedir, ters vekil arkasında neden çalışır
  • Bir isteğin baştan sona yolculuğu
  • Uç nokta yönlendirme (routing) ve uç nokta kavramına genel bakış
  • Minimal API ile denetleyici (controller) arasındaki seçim
  • Uygulama ömrü olayları ve düzgün kapanma (graceful shutdown)
  • launchSettings.json, ortam ve geliştirme kolaylıkları

02Barındırıcı ve Program.cs

Modern bir ASP.NET Core uygulamasının tamamı tek dosyada başlar:

var builder = WebApplication.CreateBuilder(args);

// ── 1. yarı: servis kaydı (kompozisyon kökü)
builder.Services.AddControllers();
builder.Services.AddScoped<ISiparisDeposu, EfSiparisDeposu>();
builder.Services.AddOptions<OdemeSecenekleri>()
       .Bind(builder.Configuration.GetSection("Odeme")).ValidateOnStart();

var app = builder.Build();          // ← konteyner burada kapanır: yeni kayıt eklenemez

// ── 2. yarı: istek boru hattı (sıra önemlidir — 7. ders)
app.UseExceptionHandler("/hata");
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();                          // sunucuyu başlatır ve bloke eder

Dosyanın iki yarısı iki farklı soruyu cevaplar: hangi nesneler var (servis kaydı) ve gelen istek hangi adımlardan geçer (boru hattı). 4. hattaki kompozisyon kökü tam olarak birinci yarıdır — tüm bağımlılıklar burada bilinir, geri kalan her yerde yalnız arayüzler görünür.

builder.Build() çağrısı bir sınırdır: o noktadan sonra servis kaydı eklenemez, çünkü konteyner kurulmuştur. Aynı çağrı, 4. dersteki ValidateOnBuild denetimlerinin çalıştığı yerdir.

Barındırıcı (host), uygulamanın etrafındaki iskelettir ve CreateBuilder onu hazır kurar: yapılandırma sağlayıcıları (3. ders), loglama (5. ders), DI konteyneri (4. ders), ortam bilgisi ve web sunucusu. Bu yüzden bir denetleyicide ILogger<T> ya da IOptions<T> istemek için ek kayıt gerekmez. Web olmayan uygulamalar (zamanlanmış işler, kuyruk tüketicileri) aynı iskeleti Host.CreateApplicationBuilder ile kullanır — fark yalnız HTTP sunucusunun olmamasıdır.

03Kestrel ve ters vekil

Kestrel, ASP.NET Core'un kendi HTTP sunucusudur; çapraz platformdur ve uygulamanın içinde çalışır. Eski .NET Framework'te web uygulaması IIS'in içinde barınırdı; modern .NET'te uygulama kendi sunucusunu taşır ve bir konsol programı gibi çalışır.

Buna rağmen üretimde Kestrel çoğunlukla bir ters vekil (reverse proxy) arkasına konur — IIS, nginx, Apache, ya da bir bulut yük dengeleyici. Sebepleri: TLS sonlandırma ve sertifika yönetimi, birden çok uygulamayı tek adres/port üzerinden yayınlama, statik dosya sunumu ve önbellekleme, yavaş istemcilere karşı tamponlama, istek boyutu ve hız sınırlama. Kestrel doğrudan internete de açılabilir, ama vekil arkasında çalışmak yerleşik pratiktir.

Vekil arkasında bilinmesi gereken bir ayrıntı vardır: uygulama, istemcinin gerçek IP adresini ve şemasını (http/https) doğrudan göremez; bunlar X-Forwarded-For / X-Forwarded-Proto başlıklarında gelir ve UseForwardedHeaders ile okunmaları gerekir. Aksi hâlde günlüklerde herkes aynı IP'den görünür ve HTTPS yönlendirmesi yanlış çalışır. 17. hatta ters vekil yapılandırmasını, 13. hatta ise bu başlıkların güvenliğini (yalnız güvenilen vekilden kabul etmek) göreceksin.

04Bir isteğin yolculuğu

Kıdemli geliştiriciyi ayıran şey, hatanın hangi durakta oluştuğunu bilmektir. Sıra şudur:

1  İstemci  ──TCP/TLS──▶  Ters vekil (nginx/IIS)
2  Vekil    ──HTTP────▶  Kestrel
3  Kestrel: isteği ayrıştırır → HttpContext oluşturur → DI kapsamı açılır
4  Middleware boru hattı: sırayla çalışır (istisna → https → kimlik → yetki → ...)
5  Yönlendirme: URL kalıbına uyan uç nokta seçilir
6  Uç nokta çalışır: model bağlama, doğrulama, action/handler gövdesi
7  Yanıt geri yolda middleware'lerden ters sırayla geçer
8  Kestrel yanıtı yazar → DI kapsamı kapanır (scoped nesneler atılır)

Üç ayrıntı bu listeyi işe yarar kılar. (1) DI kapsamı 3. adımda açılır ve 8. adımda kapanır; 4. dersteki "scoped = istek başına" cümlesinin fiziksel karşılığı budur ve DbContext'in ne zaman atıldığını açıklar. (2) Yönlendirme boru hattının ortasındadır: UseRouting'den önceki middleware'ler hangi uç noktanın seçileceğini bilmez, sonrakiler bilir — yetkilendirmenin neden UseRouting ile MapControllers arasında durması gerektiği buradan çıkar. (3) Yanıt geri yolda ters sırayla geçer; bu yüzden yanıtı değiştirmek isteyen bir middleware'in next çağrısından sonraki kısmında çalışması gerekir.

HttpContext tek bir isteğin tüm durumudur: Request (yol, başlıklar, gövde, sorgu), Response (durum kodu, başlıklar, gövde), User (kimlik), Items (istek boyunca taşınan geçici veri), RequestAborted (istemci bağlantıyı kestiğinde tetiklenen iptal jetonu — 3. hattaki iptal jetonunun web karşılığı) ve RequestServices (isteğin DI kapsamı). HttpContext iş parçacığı güvenli değildir ve yanıt yazıldıktan sonra geçersizdir; bir arka plan işine HttpContext taşımak klasik bir hatadır — gereken veri kopyalanıp geçilir.

05Uç noktalar: minimal API ve denetleyici

Yönlendirme, gelen URL'yi bir uç noktaya (endpoint) eşler. İki yazım biçimi vardır ve ikisi de aynı altyapıyı kullanır:

// Minimal API — küçük yüzeyler, mikro servisler, hızlı prototip
app.MapGet("/siparisler/{no:int}", async (int no, ISiparisDeposu depo, CancellationToken ct) =>
{
    var siparis = await depo.Getir(no, ct);
    return siparis is null ? Results.NotFound() : Results.Ok(SiparisDto.From(siparis));
});

// Denetleyici — çok sayıda uç nokta, ortak davranış, öznitelik tabanlı düzen
[ApiController]
[Route("siparisler")]
public sealed class SiparislerController(ISiparisDeposu depo) : ControllerBase
{
    [HttpGet("{no:int}")]
    public async Task<IActionResult> Getir(int no, CancellationToken ct)
    {
        var siparis = await depo.Getir(no, ct);
        return siparis is null ? NotFound() : Ok(SiparisDto.From(siparis));
    }
}

Seçim ölçütü: uç nokta sayısı ve ortak davranış ihtiyacı. Az sayıda uç nokta ve sade işlerde minimal API daha az tören ister ve biraz daha hızlıdır; uç nokta sayısı arttıkça ve doğrulama, yetki, filtre gibi ortak davranışlar gerektikçe denetleyicilerin düzeni kazandırır. İkisi aynı uygulamada birlikte de kullanılabilir. Ayrıntıları — rota kalıpları, model bağlama, sonuç tipleri — 8 ve 9. derslerin konusudur.

Bir uyarı: uç nokta gövdesi ince kalmalıdır. 4. hattaki iş bölümü burada aynen geçerlidir — denetleyici, isteği DTO'ya çevirir, uygulama servisini çağırır, sonucu HTTP'ye çevirir. İş kuralı denetleyiciye yazıldığında test edilebilirlik kaybolur ve aynı kural ikinci bir giriş noktasında (kuyruk tüketicisi, zamanlanmış iş) tekrar yazılmak zorunda kalır.

06Uygulama ömrü ve düzgün kapanma

app.Run() sunucuyu başlatır ve kapanış sinyaline kadar bloke eder. Kapanış, bulut ve kapsayıcı ortamlarında sık yaşanan normal bir olaydır (yeni sürüm dağıtımı, ölçekleme, düğüm bakımı) ve düzgün kapanma davranışı önemlidir:

  1. Ortamdan durdurma sinyali gelir (SIGTERM, Ctrl+C).
  2. Sunucu yeni istek kabul etmeyi bırakır.
  3. Devam eden istekler için bir süre beklenir (varsayılan 30 saniye, HostOptions.ShutdownTimeout ile ayarlanır).
  4. IHostApplicationLifetime.ApplicationStopping tetiklenir; arka plan servisleri iptal jetonunu görür ve temiz biter.
  5. Konteyner atılır (singleton'ların Dispose çağrıları burada).
app.Lifetime.ApplicationStarted.Register(() => logger.LogInformation("Hazır"));
app.Lifetime.ApplicationStopping.Register(() => logger.LogInformation("Kapanıyor"));

Bunun pratik sonucu: uzun süren işleri istek içinde yapmamak ve arka plan servislerinde iptal jetonunu gerçekten dinlemek gerekir. Jetonu yok sayan bir BackgroundService, zaman aşımı dolana kadar kapanışı geciktirir ve dağıtım süresini uzatır. Kapsayıcıda ayrıca sağlık uçları (health checks) bu akışın parçasıdır: kapanış başladığında hazırlık ucu olumsuz dönerse yük dengeleyici trafiği kesip düşen istek sayısını sıfırlar. 17 ve 18. hatlarda bu düzeni kuracaksın.

Geliştirme tarafında iki dosya işini kolaylaştırır: launchSettings.json (yalnız yerelde geçerli — profil, port, ortam değişkenleri) ve dotnet watch run (kaynak değişince yeniden derleyip başlatır). Ortam Development olduğunda ayrıntılı hata sayfası ve API keşif araçları açılır; bunların üretimde kapalı olması, 3. dersteki "değişken yoksa Production varsayılır" kuralının güvenlik gerekçesidir.

07Mini alıştırma — İlk servisi ayağa kaldır

Yaklaşık 20 dakika · dotnet new webapi

  1. Kur: yeni bir web API projesi oluştur, dotnet run ile çalıştır ve tarayıcıdan bir uç noktayı çağır. Konsolda hangi adres ve port yazıyor?
  2. İki yarı: Program.cs'i okuyup satırları "servis kaydı" ve "boru hattı" diye ikiye ayır. builder.Build()'dan sonra bir servis kaydetmeyi dene — ne oluyor?
  3. Minimal uç nokta: MapGet("/saglik", ...) ile bir uç nokta ekle; bir servisi doğrudan parametre olarak enjekte et.
  4. Kapsam kanıtı: 4. dersteki IzServisi'ni scoped kaydet ve iki farklı yerden (bir middleware ve bir uç nokta) aynı istek içinde çözümleyip Guid'leri karşılaştır. Aynı mı?
  5. İptal: uç noktada CancellationToken parametresi al ve içinde 10 saniyelik bir Task.Delay yap. İsteği yarıda kes (tarayıcı sekmesini kapat) ve jetonun tetiklendiğini logla.
  6. Kapanma: ApplicationStopping'e bir log kaydı bağla. Ctrl+C ile kapat ve sırayı gözle.
  7. Ortam: ASPNETCORE_ENVIRONMENT=Production ile çalıştır ve bilerek bir istisna fırlat. Development'a göre yanıt nasıl değişti, neden?

08Kendini sına

Program.cs'te ne oluyor? builder.Build() neden bir sınır?

Dosyanın iki yarısı vardır: birinci yarı servis kaydıdır — hangi arayüzün hangi uygulamaya bağlanacağı, yani 4. hattaki kompozisyon kökünün platformdaki karşılığı; ikinci yarı istek boru hattıdır — gelen isteğin hangi adımlardan geçeceği. builder.Build() konteyneri kurar ve o noktadan sonra yeni servis kaydı eklenemez; ayrıca ValidateOnBuild denetimleri burada çalışır. Barındırıcı, CreateBuilder ile birlikte yapılandırma sağlayıcılarını, loglamayı, DI konteynerini, ortam bilgisini ve web sunucusunu hazır kurar — bu yüzden bir denetleyicide ILogger<T> ya da IOptions<T> istemek için ek kayıt gerekmez. Web olmayan uygulamalar aynı iskeleti Host.CreateApplicationBuilder ile kullanır; fark yalnız HTTP sunucusunun olmamasıdır. app.Run() sunucuyu başlatır ve kapanış sinyaline kadar bloke eder.

Bir HTTP isteği hangi duraklardan geçer?

İstemci TCP/TLS ile ters vekile bağlanır, vekil isteği Kestrel'e iletir; Kestrel isteği ayrıştırır, HttpContext oluşturur ve DI kapsamı açılır; middleware boru hattı sırayla çalışır; yönlendirme URL kalıbına uyan uç noktayı seçer; uç nokta çalışır (model bağlama, doğrulama, gövde); yanıt geri yolda middleware'lerden ters sırayla geçer; Kestrel yanıtı yazar ve DI kapsamı kapanır, scoped nesneler atılır. Üç ayrıntı önemlidir: kapsamın açılıp kapanma anı "scoped = istek başına" cümlesinin fiziksel karşılığıdır ve DbContext'in ne zaman atıldığını açıklar; yönlendirme boru hattının ortasındadır, bu yüzden yetkilendirme UseRouting ile uç nokta eşlemesi arasında durmalıdır; yanıtı değiştirmek isteyen middleware'in kodu next çağrısından sonra çalışmalıdır. HttpContext iş parçacığı güvenli değildir ve yanıt yazıldıktan sonra geçersizdir — arka plan işine taşınmaz, gereken veri kopyalanır.

Kestrel nedir, neden ters vekil arkasında çalıştırılır?

Kestrel, ASP.NET Core'un uygulamanın içinde çalışan çapraz platform HTTP sunucusudur; eski .NET Framework'te uygulama IIS'in içinde barınırken modern .NET'te uygulama kendi sunucusunu taşır ve konsol programı gibi çalışır. Üretimde yine de genellikle bir ters vekil (nginx, IIS, yük dengeleyici) arkasına konur: TLS sonlandırma ve sertifika yönetimi, birden çok uygulamayı tek adres üzerinden yayınlama, statik dosya sunumu ve önbellekleme, yavaş istemcilere karşı tamponlama, istek boyutu ve hız sınırlama için. Vekil arkasında uygulama istemcinin gerçek IP'sini ve şemasını doğrudan göremez; bunlar X-Forwarded-For ve X-Forwarded-Proto başlıklarıyla gelir ve UseForwardedHeaders ile okunmalıdır — yoksa günlüklerde herkes aynı IP'den görünür ve HTTPS yönlendirmesi yanlış çalışır. Bu başlıklar yalnız güvenilen vekilden kabul edilmelidir.

Düzgün kapanma nasıl işler, buna göre nasıl kod yazarsın?

Ortamdan durdurma sinyali (SIGTERM, Ctrl+C) gelir; sunucu yeni istek kabul etmeyi bırakır; devam eden istekler için bir süre beklenir (varsayılan 30 saniye, ShutdownTimeout ile ayarlanır); ApplicationStopping tetiklenir ve arka plan servisleri iptal jetonunu görüp temiz biter; en sonda konteyner atılır ve singleton'ların Dispose'ları çalışır. Pratik sonuçları: uzun süren işler istek içinde yapılmaz, arka plan servislerinde iptal jetonu gerçekten dinlenir — jetonu yok sayan bir BackgroundService zaman aşımı dolana kadar kapanışı geciktirir ve dağıtım süresini uzatır. Kapsayıcı ortamında sağlık uçları bu akışın parçasıdır: kapanış başlayınca hazırlık ucu olumsuz döner, yük dengeleyici trafiği keser ve düşen istek sayısı sıfırlanır. Kapanma, bulutta bir arıza değil normal bir olaydır — dağıtım, ölçekleme ve düğüm bakımı her gün bunu tetikler.

09Sınav köşesi

Yazılıda nasıl sorulur

(1) Kavram: Kestrel nedir; ters vekil neden kullanılır. (2) Karşılaştırma: eski ASP.NET (IIS içinde barınma, System.Web) ile ASP.NET Core farkı. (3) Yapı: Program.cs'in iki yarısı ve builder.Build()'ın anlamı. (4) En sık çıkan: bir HTTP isteğinin işlenme adımlarını sırayla yazınız. (5) Kavram: HttpContext nedir, neler içerir; neden arka plana taşınmaz. (6) Yönlendirme: uç nokta kavramı; minimal API ile controller farkı ve seçim ölçütü. (7) Ömür: IHostApplicationLifetime olayları ve düzgün kapanma adımları. (8) Ortam: launchSettings.json ne işe yarar, üretimde geçerli midir. (9) DI: istek kapsamı ne zaman açılır ve kapanır.

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Model — "ASP.NET Core uygulaması kendi sunucusunu, Kestrel'i taşır; konsol programı gibi çalışır ve üretimde genelde ters vekil arkasına konur." (2) Program.cs — "iki yarısı vardır: servis kaydı, yani kompozisyon kökü; ve istek boru hattı. Build() çağrısından sonra kayıt eklenemez." (3) İstek yolculuğu — "Kestrel isteği ayrıştırır, DI kapsamı açılır, middleware'ler sırayla çalışır, yönlendirme uç noktayı seçer, uç nokta çalışır, yanıt geri yolda ters sırayla geçer ve kapsam kapanınca scoped nesneler atılır." (4) Uç nokta — "az sayıda uç noktada minimal API, çok uç noktalı ve ortak davranış gereken serviste controller kullanırım; her ikisinde de gövdeyi ince tutar, iş kuralını uygulama servisine bırakırım." (5) Kıdem vuruşu — "kapanmayı normal bir olay olarak tasarlarım: ApplicationStopping'i dinler, arka plan işlerinde iptal jetonuna uyar, sağlık uçlarıyla yük dengeleyicinin trafiği kesmesini sağlarım. Vekil arkasında UseForwardedHeaders'ı unutmam, yoksa gerçek istemci IP'si ve şeması kaybolur."