İlk controller'ı yazmak: routing, attribute'lar ve action metotları
İlk gerçek controller'ımızı yazıyoruz. Routing nasıl çalışır, attribute'lar ne işe yarar, action metotları nasıl kurgulanır ve gelen bir istek doğru metoda nasıl ulaşır, adım adım işliyoruz.
Önceki yazıda controller ile Minimal API arasındaki farkı konuştuk ve seride ağırlıklı olarak controller tabanlı ilerleyeceğimizi söyledim. Sözümüzü tutuyoruz. Bugün ilk gerçek controller'ımızı sıfırdan yazıyor, gelen bir isteğin bu sınıfa nasıl ulaştığını ve içeride ne olduğunu satır satır anlıyoruz. Bu yazı biraz daha "elleri kirletme" havasında; o yüzden okuduğun her örneği kendi projende de dene.
Controller Aslında Nedir?
İsim kulağa havalı geliyor ama controller, özünde sıradan bir C# sınıfı. Onu özel yapan tek şey, ASP.NET Core'un bu sınıfı "gelen HTTP isteklerini karşılayan yer" olarak tanıması. Yani bir controller, dış dünyadan gelen isteklerle senin iş mantığın arasında duran bir kabul memuru gibi. İstek gelir, controller onu karşılar, doğru işlemi yapar ve bir cevap döner.
Bir controller genelde tek bir kaynakla ilgilenir. Kullanıcılarla ilgili her şey UsersController'da, ürünlerle ilgili her şey ProductsController'da toplanır. Bu, ilk yazıda konuştuğumuz kaynak odaklı düşünmenin koda yansımış hali. Bir sınıfa baktığında hangi kaynakla ilgilendiğini adından anlayabilmen lazım.
İlk Controller'ı Oluşturmak
İkinci yazıda kurduğumuz KullaniciApi projesine geri dönüyoruz. Projenin içinde Controllers adında bir klasör olabilir; yoksa oluştur. İçine UsersController.cs adında bir dosya aç ve şunu yaz:
using Microsoft.AspNetCore.Mvc;
namespace KullaniciApi.Controllers;
[ApiController]
[Route("users")]
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult GetAll()
{
var users = new[] { "Anna", "Tom", "Sophie" };
return Ok(users);
}
}
Projeyi çalıştırıp tarayıcıdan /users adresine gittiğinde, üç isimlik bir JSON dizisiyle karşılaşacaksın. Az önce ilk endpoint'ini yazdın. Şimdi bu kısacık kodun her parçasını tek tek açalım, çünkü buradaki her satırın bir görevi var.
Attribute'lar: Sınıfa Takılan Etiketler
Köşeli parantez içindeki [ApiController], [Route("users")], [HttpGet] gibi ifadelere attribute diyoruz. Bunları, sınıfa ya da metoda iliştirdiğin küçük etiketler gibi düşün. Kod çalışmasa bile orada dururlar ve .NET'e "bu sınıf şöyle davransın" diye talimat verirler.
[ApiController], bu sınıfın bir Web API controller'ı olduğunu söyler ve beraberinde birkaç kolaylık getirir. En kullanışlısı, gelen veride bir doğrulama hatası olduğunda bunu senin elle kontrol etmene gerek kalmadan otomatik olarak yakalayıp düzgün bir hata cevabı dönmesi. Bu konuya validation yazısında geri döneceğiz.
[Route("users")], bu sınıftaki tüm endpoint'lerin hangi adresin altında toplanacağını belirler. Yani bu sınıftaki her şey /users ile başlayan bir adrese cevap verecek. Bunu sınıfın kapısına asılmış bir tabela gibi düşün: "Buradan itibaren her şey users hakkında."
Routing: İstek Doğru Metoda Nasıl Ulaşıyor?
Routing, gelen bir isteğin adresine bakıp onu doğru controller'ın doğru metoduna yönlendirme işidir. Bir telefon santralini düşün: arayan kişi bir numara çevirir, santral de onu doğru birime bağlar. Routing de tam olarak bunu yapar, sadece numara yerine URL kullanır.
Yukarıdaki örnekte iki parça birleşiyor. Sınıfın üstündeki [Route("users")] adresin ilk kısmını, metodun üstündeki [HttpGet] ise hem HTTP metodunu hem de o metodun bu adrese cevap vereceğini belirtiyor. İkisi birleşince ortaya "GET /users isteği geldiğinde GetAll metodunu çalıştır" kuralı çıkıyor.
Şimdi işi biraz daha ilginç hale getirelim. Tek bir kullanıcıyı id'sine göre getirmek istiyoruz. Bunun için sınıfa yeni bir metot ekliyoruz:
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
return Ok($"İstenen kullanıcı: {id}");
}
Buradaki [HttpGet("{id}")], adrese bir parça daha ekliyor. Artık bu metot /users/{id} adresine cevap veriyor; süslü parantez içindeki id ise bir yer tutucu. Yani /users/42 adresine bir istek geldiğinde .NET, adresteki 42 değerini alıp metodun id parametresine yerleştiriyor. Tarayıcıdan /users/42 adresine gittiğinde "İstenen kullanıcı: 42" cevabını göreceksin. Adresten metoda otomatik geçen bu değere route parametresi diyoruz.
Action Metotları: İşin Asıl Yapıldığı Yer
Controller içindeki, bir isteğe cevap veren public metotlara action metodu denir. GetAll ve GetById, ikisi de birer action metodu. İsimlerini sen koyarsın; .NET hangi metodun çağrılacağını metodun adına değil, üstündeki attribute'lara bakarak belirler. Yani metoda GetAll yerine Hepsi desen de, [HttpGet] orada durduğu sürece çalışır. Yine de anlamlı isimler vermek, kodu okuyan bir sonraki kişiye (ki o kişi çoğu zaman altı ay sonraki sensindir) büyük bir iyilik.
Bu metotların dönüş tipine dikkat et: IActionResult. Bu, "bu metot bir HTTP cevabı döndürecek ama tam olarak ne tür bir cevap olacağı duruma göre değişebilir" demenin yolu. Bazen başarılı bir sonuç, bazen bir hata, bazen "bulunamadı" cevabı dönebilirsin. IActionResult bu esnekliği sağlar.
Doğru Cevabı Dönmek
Örneklerde Ok(...) yardımcı metodunu kullandık. Bu, cevabı 200 OK durum koduyla birlikte döndürür; yani "her şey yolunda, işte istediğin veri" demenin kısa yolu. ControllerBase sınıfından miras aldığımız için elimizde buna benzer bir sürü hazır yardımcı var. Birkaç örnek:
return Ok(user); // 200 - başarılı, veriyle birlikte
return NotFound(); // 404 - kaynak bulunamadı
return BadRequest("Hata"); // 400 - istekte bir sorun var
return NoContent(); // 204 - başarılı ama dönecek veri yok
Bunları küçük yardımcılar olarak düşün: her biri hem doğru durum kodunu ayarlıyor hem de gerekiyorsa veriyi ekliyor. Doğru durum kodunu dönmek, API'ni kullanan tarafın ne olduğunu anlaması için kritik. 200 mü döndü, 404 mü, bu tek başına çok şey anlatır. Durum kodlarına bir sonraki yazıda çok daha detaylı gireceğiz; şimdilik Ok ve NotFound'u tanıman yeterli.
Küçük Bir Deneme
Bir sonraki yazıya geçmeden önce şunu yap: UsersController'a küçük bir mantık ekle. GetById metodunun içinde, gelen id değeri sıfırdan küçük veya sıfırsa BadRequest dön; değilse Ok ile devam et. Sonra tarayıcıdan hem geçerli bir id, hem de eksi bir değer dene ve dönen cevapların nasıl değiştiğini gör. Bu küçük alıştırma, action metodunun içinde karar vermenin ne kadar doğal olduğunu sana gösterecek.
Bir sonraki yazıda HTTP durum kodlarını ve doğru cevap dönmenin inceliklerini derinlemesine ele alacağız: IActionResult ile Results arasındaki fark, hangi durumda hangi kodu dönmen gerektiği ve API'ni kullananların hayatını kolaylaştıran küçük detaylar. Bugün yazdığın bu ilk controller'ı silme; onun üzerine inşa etmeye devam edeceğiz.