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:
- Ortamdan durdurma sinyali gelir (
SIGTERM, Ctrl+C). - Sunucu yeni istek kabul etmeyi bırakır.
- Devam eden istekler için bir süre beklenir (varsayılan 30 saniye,
HostOptions.ShutdownTimeoutile ayarlanır). IHostApplicationLifetime.ApplicationStoppingtetiklenir; arka plan servisleri iptal jetonunu görür ve temiz biter.- 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
- Kur: yeni bir web API projesi oluştur,
dotnet runile çalıştır ve tarayıcıdan bir uç noktayı çağır. Konsolda hangi adres ve port yazıyor? - İ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? - Minimal uç nokta:
MapGet("/saglik", ...)ile bir uç nokta ekle; bir servisi doğrudan parametre olarak enjekte et. - Kapsam kanıtı: 4. dersteki
IzServisi'ni scoped kaydet ve iki farklı yerden (bir middleware ve bir uç nokta) aynı istek içinde çözümleyipGuid'leri karşılaştır. Aynı mı? - İptal: uç noktada
CancellationTokenparametresi al ve içinde 10 saniyelik birTask.Delayyap. İsteği yarıda kes (tarayıcı sekmesini kapat) ve jetonun tetiklendiğini logla. - Kapanma:
ApplicationStopping'e bir log kaydı bağla. Ctrl+C ile kapat ve sırayı gözle. - Ortam:
ASPNETCORE_ENVIRONMENT=Productionile ç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."
