Mert Özen Her Satırda Daha İleri
Backend .NET Core Web API — #06

Model binding: Gelen isteğin verisini metoda bağlamak (route, query, body, header)

Mert Özen 26 Tem 2026 11 dk 63 görüntülenme
Model binding: Gelen isteğin verisini metoda bağlamak (route, query, body, header)

Gelen bir isteğin içindeki veriyi .NET'in metot parametrelerine nasıl otomatik bağladığını işliyoruz: route, query string, gövde ve header'dan veri okumak, [FromBody] gibi attribute'lar ve sık hatalar.

Önceki yazılarda endpoint'ler yazdık, doğru durum kodlarını dönmeyi öğrendik. Ama bir şeyi hep hazır kabul ettik: metotların içine gelen veriler. GetById(int id) yazdığımızda o id değeri adresten metoda nasıl geçiyordu? İşte bu sihrin adı model binding. Bugün .NET'in, gelen bir isteğin dört bir yanındaki veriyi senin metot parametrelerine nasıl otomatik olarak taşıdığını en baştan anlıyoruz.

Model Binding Aslında Ne Yapıyor?

Bir HTTP isteği geldiğinde veri birçok yerde saklı olabilir: adresin içinde, adresin sonundaki soru işaretinden sonra, isteğin gövdesinde ya da başlıklarında. Bu verilerin hepsi, ham haliyle metin. Model binding, bu ham metni alıp senin C# metot parametrelerine, doğru tipe çevirerek yerleştiren köprüdür.

Bunu bir gümrük memuru gibi düşün: dışarıdan gelen paketleri açar, içindekileri doğru raflara yerleştirir. Sen metodunda int id yazdığında, model binding adresteki metni alıp bunu bir sayıya çevirmeye çalışır ve id parametresine koyar. Sen bu dönüşümle hiç uğraşmazsın; sadece ne istediğini metot imzasında beyan edersin, gerisini .NET halleder.

Veri Nereden Gelebilir?

Gelen bir istekte verinin bulunabileceği başlıca dört yer var. .NET çoğu zaman nereye bakacağını kendi anlar ama ne olduğunu bilmen, işler karıştığında sana çok zaman kazandırır.

Route'tan (adresin içinden)

Adresin bir parçası olan veriler. Zaten tanıdığın örnek bu:

[HttpGet("{id}")]
public IActionResult GetById(int id)
{
    return Ok($"İstenen kullanıcı: {id}");
}

Buradaki id, adresteki {id} yer tutucusundan gelir. Bir kaynağı benzersiz şekilde tanımlayan değerler (genellikle id'ler) için route en doğal yerdir.

Query string'den (adresin sonundan)

Adresin sonundaki soru işaretinden sonra gelen, genelde filtreleme ve sıralama için kullanılan veriler. Örneğin /users?page=2&size=10 gibi bir adreste page ve size birer query parametresidir:

[HttpGet]
public IActionResult GetAll(int page, int size)
{
    return Ok($"Sayfa {page}, boyut {size}");
}

Metot parametresinin adı query'deki anahtarla aynı olduğunda, .NET ikisini kendiliğinden eşleştirir. Zorunlu olmayan, isteğe bağlı bilgiler için query string biçilmiş kaftandır.

Gövdeden (request body)

Yeni bir kayıt oluştururken ya da güncellerken gönderdiğin, genelde JSON formatındaki asıl veri. Bir kullanıcı nesnesi göndermek istediğinde şöyle yazarsın:

[HttpPost]
public IActionResult Create([FromBody] User user)
{
    return Ok($"Oluşturulan kullanıcı: {user.Name}");
}

Buradaki [FromBody], .NET'e "bu parametreyi isteğin gövdesindeki JSON'dan doldur" der. İstemci JSON gönderir, model binding onu senin User sınıfına çevirir. Karmaşık nesneleri (birden çok alanı olan verileri) taşımanın standart yolu gövdedir.

Header'dan (başlıklardan)

İsteğin başlıklarında taşınan veriler. Daha az kullanılır ama bazı bilgiler (örneğin bir API anahtarı ya da özel bir izleme kimliği) için doğru yerdir:

[HttpGet("profil")]
public IActionResult GetProfile([FromHeader(Name = "X-Client-Id")] string clientId)
{
    return Ok($"İstek yapan istemci: {clientId}");
}

Burada [FromHeader] ile hangi başlıktan okuyacağını açıkça söylüyoruz. Header'lar genelde verinin kendisi için değil, istekle ilgili yan bilgiler için kullanılır.

.NET Nereye Bakacağını Nasıl Biliyor?

Çoğu zaman attribute yazmana bile gerek kalmaz, çünkü .NET makul varsayımlar yapar. Basit tipler (sayı, metin gibi) için önce route'a, sonra query string'e bakar. Karmaşık tipler (senin yazdığın sınıflar) için ise gövdeyi varsayar. Yani [FromBody] ve [FromQuery] gibi attribute'ları çoğu zaman yazmasan da çalışır.

Peki neden yazıyoruz? Çünkü açıklık her zaman kazanır. Kodunu okuyan biri [FromBody] gördüğünde verinin nereden geldiğini bir bakışta anlar; tahmin yürütmesine gerek kalmaz. Ayrıca varsayımların yetmediği durumlarda (mesela normalde route'tan gelecek bir değeri query'den okumak istediğinde) bu attribute'lar tek çıkış yolun olur. Kısacası .NET'in zekasına güven ama niyetini kodda açıkça beyan et.

Sık Yapılan Bir Hata

Yeni başlayanların en çok takıldığı yer şu: gövdeden veri bekledikleri halde nesnenin boş gelmesi. Sebebi genelde iki şeyden biridir. Ya istemci veriyi JSON olarak ve doğru içerik tipiyle göndermiyordur, ya da User sınıfının özellikleri ile gönderilen JSON'daki alan adları birbirini tutmuyordur. Model binding eşleştirmeyi isim üzerinden yapar; JSON'da name varken sınıfında FullName yazıyorsa, o alan boş kalır ve kimse sana hata vermez. Nesnen beklenmedik şekilde boş geldiğinde ilk bakman gereken yer, alan adlarının birebir uyuşup uyuşmadığıdır.

Küçük Bir Deneme

Elindeki UsersController'a iki yeni endpoint ekle. Birincisi query string'den page ve size alan bir GetAll, ikincisi gövdeden [FromBody] ile bir User alan bir Create. Sonra bir API test aracıyla (ya da tarayıcının geliştirici araçlarıyla) ikisine de istek gönder. Query parametrelerini değiştir, gövdeye farklı JSON'lar yolla ve metot parametrelerinin nasıl dolduğunu gözlemle. Bir de bilerek JSON'daki bir alan adını yanlış yaz; nesnenin o alanının nasıl boş kaldığını kendi gözünle gör. Bu küçük deney, model binding'in mantığını herhangi bir açıklamadan daha iyi oturtur.

Bir sonraki yazıda DTO kavramına geçeceğiz: neden gövdeden gelen veriyi ya da veritabanı nesnelerini doğrudan kullanmak yerine, araya özel taşıyıcı sınıflar koyduğumuzu konuşacağız. Bugün öğrendiğin model binding, o DTO'ların isteğin neresinden nasıl dolduğunu anlaman için tam da gereken temel.