Skip to content

Bölüm 3 / 20

Formlar ve girdiler

input türleri, select, checkbox, label, gönderim.

16 dakikalık okuma

Kurumsal yazılımın büyük kısmı formdur. Kullanıcı veri girer, sistem kaydeder — arada duran her şey bu dersin konusu. Form yazmak kolay görünür; doğru form yazmak, hangi girdi türünün gerçekte ne yaptığını, hangi alanın gönderilip hangisinin gönderilmediğini, istemci doğrulamasının neden güvenlik olmadığını ve çift gönderimin nasıl engelleneceğini bilmeyi gerektirir. 6. hatta API tarafını yazmıştın; burası aynı verinin toplandığı uçtur.

01Ne öğreneceksin

  • <form> ögesi: action, method, enctype ve hangisi ne zaman
  • name özniteliğinin zorunluluğu — adı olmayan alan gönderilmez
  • Girdi türleri ve gerçek davranışları; number'ın tuzakları
  • inputmode, autocomplete ve mobil klavye deneyimi
  • label, fieldset, legend ile doğru etiketleme
  • Onay kutusu ve radyo düğmesinin value davranışı
  • Yerleşik doğrulama: required, pattern, :user-invalid, Constraint Validation API
  • İstemci doğrulaması neden güvenlik değildir
  • Gönderim: klasik POST, PRG deseni, fetch + FormData
  • Çift gönderim, disabled/readonly farkı, idempotentlik
  • Erişilebilir hata sunumu: aria-describedby, aria-invalid, odak yönetimi

02<form> ögesi

<form action="/basvuru/kaydet" method="post">
  <label for="ad">Ad Soyad</label>
  <input type="text" id="ad" name="adSoyad" required>

  <button type="submit">Gönder</button>
</form>
ÖznitelikAnlamıNot
actionVerinin gideceği adresBoş bırakılırsa bulunduğun sayfaya gönderilir
methodget ya da postHTML formu yalnız bu ikisini destekler — PUT/DELETE için JavaScript gerekir
enctypeGövdenin kodlanma biçimiYalnız post ile anlamlı
novalidateYerleşik doğrulamayı kapatırKendi doğrulama akışını kurarken kullanılır
autocomplete="off"Tarayıcı otomatik doldurmasını kapatırGenelde kullanma — kullanıcıya zarar verir; tarayıcılar çoğu zaman yok sayar

GET mi POST mu

GETPOST
Veri neredeURL'de sorgu dizesindeİstek gövdesinde
Yer imi / paylaşımOlur — adres sonucu taşırOlmaz
Tarayıcı geçmişi ve günlüklerDeğerler görünürGörünmez
Boyut sınırıPratikte ~2000 karakter (sunucu/ara sunucu sınırı)Yok denecek kadar geniş
YenilemedeSorunsuz"Formu yeniden gönder?" uyarısı
Yan etkiOlmamalı (idempotent)Olabilir

Kural: arama, filtre ve listeleme GET (kullanıcı sonucu paylaşabilsin, yer imine eklesin, geri düğmesi çalışsın); kayıt, güncelleme, silme POST. Parola alanı olan bir formu GET ile göndermek, parolayı adres çubuğuna, tarayıcı geçmişine, sunucu erişim günlüğüne ve ara sunucu kayıtlarına yazmak demektir.

enctype: üç değer

DeğerGövdeNe zaman
application/x-www-form-urlencodedad=Ali&yas=30Varsayılan; metin alanları
multipart/form-dataSınırlayıcılarla ayrılmış bölümlerDosya yüklemede zorunlu
text/plainDüz metinKullanma — hata ayıklama dışında işe yaramaz

En sık yapılan hatalardan biri budur: forma <input type="file"> koyup enctype'ı değiştirmeyi unutmak. Sunucuya dosya değil, yalnızca dosya adı gider ve hata mesajı bunu açıkça söylemez.

name olmadan veri yok

Bu, formların en temel ve en çok unutulan kuralıdır. Sunucuya gönderilen anahtar name'dir; id yalnızca sayfa içi eşleşme ve CSS içindir. name yazılmamış bir alan gönderilmez ve hiçbir hata vermez. Ayrıca şunlar da gönderilmez:

  • disabled alanlar — hiç gönderilmez. Bir alanı kullanıcıya göstermek ama değiştirtmemek ve yine de göndermek istiyorsan readonly kullan.
  • İşaretlenmemiş onay kutuları — hiç görünmezler, "false" bile gelmez.
  • Seçilmemiş radyo grupları.
  • <form> dışında kalan alanlar — istisna: form="formId" özniteliğiyle dışarıdaki bir alan forma bağlanabilir.

Onay kutusu tuzağı sunucu kodunu doğrudan etkiler:

<input type="checkbox" name="kvkk">
<!-- işaretliyse:   kvkk=on
     işaretli değilse: hiç gönderilmez -->

<!-- Değeri açıkça belirt; ayrıca gizli bir alanla "yok" durumunu da gönder -->
<input type="hidden"   name="kvkk" value="hayir">
<input type="checkbox" name="kvkk" value="evet">
<!-- İşaretliyse iki değer birden gider; sunucu SONUNCUSUNU alır -->

Bu desen ASP.NET Core'un <input asp-for> yardımcı etiketinin kendiliğinden ürettiği şeydir — 5. hatta gördüğün model bağlamanın (model binding) onay kutularında neden hep doğru çalıştığının cevabı budur.

03Girdi türleri

TürNe yaparBilinmesi gereken
textTek satır metinVarsayılan; maxlength ile sınırla
emailE-posta klavyesi + biçim kontrolüKontrol çok gevşektir: a@b geçerli sayılır. Gerçek doğrulama doğrulama postası göndermektir
passwordKarakterleri gizlerYalnız görsel; güvenliği HTTPS sağlar
numberSayısal, artırma oklarıDeğeri okurken dize döner; valueAsNumber kullan. Geçersiz metinde value boş dize olur — girilen bozuk veriyi göremezsin
telTelefon klavyesiHiçbir doğrulama yapmaz — biçimler ülkeden ülkeye değişir; pattern ekle
urlAdres kontrolüŞema zorunlu: ornek.com geçersiz, https://ornek.com geçerli
date / time / datetime-localTarayıcının tarih seçicisiDeğer her zaman ISO biçimindedir (2026-09-15), görünen biçim kullanıcının diline göre değişir
searchTemizleme düğmesi, arama klavyesiMobilde Enter tuşu "Ara" olur
fileDosya seçiciaccept ve multiple; enctype şart. accept filtredir, kısıt değildir
hiddenGörünmez ama gönderilirGizli değildir — kaynak kodda görünür ve değiştirilebilir
rangeKaydırıcıKesin değer gerekiyorsa yanına sayı göster
colorRenk seçiciHer zaman #rrggbb döner
checkbox / radioÇoklu / tekli seçimRadyo grubu aynı name ile kurulur

type="number" ne zaman kullanılmaz: telefon numarası, TC kimlik numarası, kart numarası, posta kodu, fatura numarası. Bunlar sayı gibi görünür ama sayı değildir — baştaki sıfırlar anlamlıdır, aritmetik yapılmaz ve tarayıcının artırma okları anlamsızdır. Ayrıca fare tekerleği yanlışlıkla değeri değiştirir. Doğrusu type="text" + inputmode="numeric" + pattern:

<label for="tc">T.C. Kimlik No</label>
<input type="text" id="tc" name="tcKimlikNo"
       inputmode="numeric" pattern="[0-9]{11}" maxlength="11"
       autocomplete="off" required>

inputmode klavyeyi belirler, doğrulama yapmaz; pattern doğrulama yapar, klavyeyi değiştirmez. İkisi birlikte kullanılır ve bu ayrımı bilmek kıdem göstergesidir.

autocomplete: kapatmak değil, doğru söylemek

autocomplete bir açma/kapama anahtarı değil, anlam bildirimidir. Tarayıcıya alanın ne olduğunu söylersin; tarayıcı ve parola yöneticisi doğru değeri doldurur. Bu bir kolaylık değil, ölçülebilir bir tamamlama oranı farkıdır ve WCAG 2.1'de erişilebilirlik ölçütüdür (1.3.5 Identify Input Purpose):

<input name="ad"     autocomplete="given-name">
<input name="soyad"  autocomplete="family-name">
<input name="eposta" autocomplete="email">
<input name="tel"    autocomplete="tel">
<input name="adres"  autocomplete="street-address">
<input name="il"     autocomplete="address-level1">
<input name="parola" autocomplete="current-password">
<input name="yeni"   autocomplete="new-password">   <!-- parola üreticisini tetikler -->
<input name="kod"    autocomplete="one-time-code">  <!-- SMS kodunu önerir -->

autocomplete="off" yalnız gerçekten tekrarlanmaması gereken alanlarda anlamlıdır (tek kullanımlık doğrulama alanları gibi). Adres ya da e-posta alanında kapatmak kullanıcıya zarar verir ve modern tarayıcılar bunu genelde yok sayar zaten.

04Etiketleme ve gruplama

Her girdinin bir etiketi olmalıdır. İki yol vardır:

<!-- 1. Açık eşleşme: label[for] ↔ input[id] -->
<label for="eposta">E-posta</label>
<input type="email" id="eposta" name="eposta">

<!-- 2. Örtük: input, label'ın içinde -->
<label>E-posta <input type="email" name="eposta"></label>

Etiketin üç somut faydası vardır ve bunlar süs değildir: (1) ekran okuyucu alana gelince etiketi okur; (2) etikete tıklamak alanı odaklar — mobilde dokunma hedefini büyütür; (3) onay kutusu ve radyo düğmesinde tıklanabilir alan minik kareden koca yazıya çıkar. Açık eşleşme tercih edilir, çünkü CSS ile düzenlemesi kolaydır ve id zaten hata mesajlarını bağlarken gerekecektir.

Yer tutucu (placeholder) etiket yerine geçmez. Yazmaya başlayınca kaybolur, kontrast oranı düşüktür, ekran okuyucu desteği tutarsızdır ve kullanıcı alanın ne olduğunu unuttuğunda geri getirmenin yolu yoktur. Yer tutucu yalnızca örnek biçim göstermek için kullanılır: etiket "Doğum tarihi", yer tutucu "GG.AA.YYYY".

Gruplama. Birbirine bağlı seçenekler <fieldset> ile sarılır, grubun başlığı <legend> olur. Radyo gruplarında bu şarttır — aksi hâlde ekran okuyucu kullanıcısı "Evet" seçeneğini duyar ama neye evet dediğini duymaz:

<fieldset>
  <legend>Başvuru türü</legend>

  <input type="radio" id="t-yeni" name="tur" value="yeni" checked>
  <label for="t-yeni">Yeni başvuru</label>

  <input type="radio" id="t-yenileme" name="tur" value="yenileme">
  <label for="t-yenileme">Yenileme</label>
</fieldset>

Radyo grubunda hepsinin name'i aynı, value'ları farklı, id'leri benzersizdir. Aynı name paylaşmayan iki radyo düğmesi birbirini kapatmaz — "iki seçenek de aynı anda seçilebiliyor" hatasının tek sebebi budur.

select, datalist, textarea

<label for="il">İl</label>
<select id="il" name="il" required>
  <option value="">Seçiniz</option>
  <optgroup label="Marmara">
    <option value="34">İstanbul</option>
    <option value="16">Bursa</option>
  </optgroup>
</select>

<!-- Serbest metin + öneri listesi -->
<input list="birimler" name="birim">
<datalist id="birimler">
  <option value="Bilgi İşlem"><option value="İnsan Kaynakları">
</datalist>

<textarea name="aciklama" rows="5" maxlength="500"></textarea>

Üç ayrıntı: (1) <select required>'in çalışması için ilk seçeneğin value="" olması gerekir — aksi hâlde her zaman bir değer seçili sayılır. (2) <option>'un value'su yoksa görünen metin gönderilir; il adı yerine plaka kodu göndermek istiyorsan value yazmak zorundasın. (3) <textarea>'nın başlangıç değeri value özniteliğiyle değil, etiketler arasındaki içerikle verilir; ve açılış etiketinden hemen sonraki satır sonu yok sayılır — girintili yazarsan metnin başına boşluk gelir.

<datalist> ile <select> farkı bir karardır: seçenek kümesi kapalıysa (il listesi) select, kullanıcı listede olmayanı da yazabilmeliyse datalist. datalist'in görünümü CSS ile değiştirilemez ve tarayıcılar arasında tutarsızdır; bu yüzden ciddi otomatik tamamlama ihtiyacında kendi bileşenini yazarsın (ve o zaman erişilebilirlik yükü de senin olur — 6. derste bu maliyeti konuşacağız).

05Yerleşik doğrulama

Tarayıcı, kod yazmadan doğrulama yapabilir:

ÖznitelikKontrol
requiredBoş bırakılamaz
minlength / maxlengthKarakter sayısı (maxlength yazmayı engeller, doğrulama üretmez)
min / max / stepSayı ve tarih aralığı
patternDüzenli ifade — baştan sona eşleşmeli, ^$ yazmaya gerek yok
typeemail, url, number kendi biçim kontrolünü yapar

pattern kullanırken title yaz — tarayıcı hata mesajında onu gösterir ve kullanıcı "istenen biçime uyunuz" yerine gerçek kuralı görür.

CSS tarafındaki önemli ayrım: :invalid sayfa açılır açılmaz eşleşir — yani kullanıcı daha hiçbir şey yazmadan tüm zorunlu alanlar kırmızı olur. Doğrusu :user-invalid'dir; yalnızca kullanıcı alanla etkileşime girdikten sonra eşleşir:

input:user-invalid { border-color: #dc2626; }
input:user-valid   { border-color: #16a34a; }

Daha ince denetim için Constraint Validation API vardır:

const form = document.querySelector("#basvuru");
const tc   = form.elements.tcKimlikNo;

tc.addEventListener("input", () => {
  tc.setCustomValidity("");                     // önce temizle, yoksa hep geçersiz kalır
  if (tc.value && !tcGecerliMi(tc.value)) {
    tc.setCustomValidity("T.C. kimlik numarası doğrulama algoritmasına uymuyor.");
  }
});

form.addEventListener("submit", (e) => {
  if (!form.checkValidity()) {                  // reportValidity() ayrıca baloncuğu gösterir
    e.preventDefault();
    form.querySelector(":invalid")?.focus();    // ilk hatalı alana odaklan
  }
});

setCustomValidity boş dizeyle sıfırlanmazsa alan sonsuza kadar geçersiz kalır; bu, API'nin en sık düşülen tuzağıdır. validity nesnesi hangi kuralın bozulduğunu söyler: valueMissing, typeMismatch, patternMismatch, rangeOverflow, tooShort, customError.

Değişmez kural

İstemci doğrulaması güvenlik değildir; kullanıcı deneyimidir. DevTools'ta required özniteliğini silmek, pattern'i değiştirmek ya da curl ile doğrudan uç noktaya istek atmak saniyeler sürer. Tarayıcı doğrulaması kullanıcıyı gidiş-gelişten kurtarır; veriyi sunucu doğrular. Aynı kurallar iki yerde birden yazılır ve bu tekrar kabul edilmiş bir maliyettir. maxlength uzun metni engeller ama API'ye doğrudan 10 MB'lık bir alan gönderilmesini engellemez.

06Gönderim: üç yol

1. Klasik gönderim ve PRG deseni

Form POST ile gönderilir, sunucu işi yapar ve doğrudan HTML döndürmez; 303 See Other ile bir sonuç sayfasına yönlendirir. Buna Post/Redirect/Get denir. Sebebi somuttur: kullanıcı sonuç sayfasında F5'e bastığında tarayıcı son isteği tekrarlar — PRG yoksa bu, ikinci bir kayıt demektir. Yönlendirme sonrası son istek bir GET'tir, yenilemek zararsızdır ve geri düğmesi "formu yeniden gönder" uyarısı çıkarmaz.

2. fetch ile gönderim

form.addEventListener("submit", async (e) => {
  e.preventDefault();
  const veri = new FormData(form);              // name'i olan alanları toplar

  const yanit = await fetch(form.action, {
    method: "POST",
    body: veri                                  // Content-Type'ı KENDİN yazma
  });

  if (!yanit.ok) { /* 6. hattaki hata sözleşmesi burada okunur */ }
});

Tuzak: FormData gönderirken Content-Type başlığını elle yazarsan istek bozulur. Çünkü multipart/form-data başlığının içinde tarayıcının ürettiği rastgele bir boundary değeri vardır; sen yazarsan o değer kaybolur ve sunucu gövdeyi ayrıştıramaz. Başlığı tarayıcıya bırak.

JSON göndermek istiyorsan dönüştürmen gerekir — ve dönüşümde bir ayrıntı vardır:

const nesne = Object.fromEntries(new FormData(form));
// DİKKAT: aynı ada sahip çoklu alanlarda (çok seçimli, onay kutusu grubu)
// yalnız SONUNCU değer kalır. Hepsi için: veri.getAll("etiketler")

await fetch("/api/basvuru", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(nesne)
});

3. Çift gönderimi engellemek

Kullanıcı yavaş ağda gönder düğmesine iki kez basar. Üç katmanlı savunma gerekir:

let gonderiliyor = false;

form.addEventListener("submit", async (e) => {
  e.preventDefault();
  if (gonderiliyor) return;                     // 1. mantık kilidi
  gonderiliyor = true;

  const dugme = form.querySelector("button[type=submit]");
  dugme.disabled = true;                        // 2. görsel + etkileşim kilidi
  dugme.textContent = "Gönderiliyor…";

  try {
    await fetch(form.action, { method: "POST", body: new FormData(form) });
  } finally {
    gonderiliyor = false;
    dugme.disabled = false;
    dugme.textContent = "Gönder";
  }
});

Üçüncü katman sunucudadır ve asıl olan odur: idempotentlik anahtarı. İstemci her gönderimde benzersiz bir anahtar üretir (gizli alanda ya da başlıkta), sunucu o anahtarı daha önce gördüyse işlemi tekrarlamaz, ilk sonucu döndürür. İstemci tarafı kilitleri ağ kopmalarında, sekme çoğaltmada ve geri düğmesinde yetersiz kalır; para ya da resmî kayıt söz konusuysa sunucu tarafı zorunludur (6. hatta gördüğün konu, 19. hatta tekrar karşına çıkacak).

Not: düğmeyi disabled yapmak submit olayı tetiklendikten sonra olmalıdır. Klasik (JavaScript'siz) bir formda düğmeyi erken devre dışı bırakırsan, düğmenin kendi name/value çifti gönderilmez — hangi düğmeye basıldığını ayırt eden sunucu kodu bozulur.

CSRF: tarayıcı, başka bir siteden gönderilen forma da oturum çerezini iliştirir. Bunu engelleyen mekanizma gizli bir belirteç alanı ve SameSite çerez özniteliğidir. Tam anlatımı 13. hattadır; burada bilinmesi gereken, "form gizli alanda bir __RequestVerificationToken taşıyor" ifadesinin ne olduğudur.

07Erişilebilir hata sunumu

Doğrulama hatasını göstermek yetmez; ekran okuyucu kullanıcısının da duyması gerekir. Doğru işaretleme şudur:

<label for="eposta">E-posta</label>
<input type="email" id="eposta" name="eposta"
       aria-describedby="eposta-yardim eposta-hata"
       aria-invalid="true" required>
<p id="eposta-yardim" class="yardim">Kurumsal adresinizi giriniz.</p>
<p id="eposta-hata" class="hata">E-posta adresi "@" işareti içermelidir.</p>
  • aria-describedby alanın açıklamasını ve hatasını bağlar; boşlukla ayrılmış birden çok id alabilir.
  • aria-invalid="true" alanın hatalı olduğunu duyurur; hata giderilince kaldırılır ya da "false" yapılır.
  • Hata metni rengin yanında metinle de anlatılmalıdır — yalnız kırmızı çerçeve, renk körü kullanıcılar için hiçbir şey söylemez (WCAG 1.4.1).
  • Gönderim başarısızsa odak ilk hatalı alana ya da sayfa başındaki hata özetine taşınır; kullanıcı hatanın nerede olduğunu aramak zorunda kalmaz.
  • Uzun formlarda başa bir hata özeti koy ve her maddeyi ilgili alana bağlantı yap; özet kutusu tabindex="-1" ile odaklanabilir olmalıdır.

Hata metninin dili de bir tasarım kararıdır ve 6. derste ayrıntısını göreceksin: hata ne olduğunu, neden olduğunu ve ne yapılacağını söylemeli. "Geçersiz giriş" bunların hiçbirini söylemez.

08Mini alıştırma — Başvuru formu kur ve kır

Yaklaşık 20 dakika · tek dosya; sunucu için action'ı bir yankı servisine ya da boş bırakabilirsin

  1. Kur: ad, e-posta, T.C. kimlik no, doğum tarihi, il (select), başvuru türü (radyo), KVKK onayı (onay kutusu) ve açıklama (textarea) alanlarından oluşan bir form yaz. Hepsine label[for] bağla.
  2. name testi: bir alandan name'i sil, formu GET ile gönder ve adres çubuğunda o alanın olmadığını gör.
  3. disabled vs readonly: aynı alanı bir kez disabled, bir kez readonly yapıp gönder. Hangisi adres çubuğunda göründü?
  4. Onay kutusu: KVKK kutusunu işaretlemeden gönder — sorgu dizesinde ne var? Sonra gizli alan desenini uygulayıp tekrarla.
  5. Radyo grubu: iki radyo düğmesine farklı name ver ve ikisini birden seçebildiğini gör; sonra düzelt.
  6. number tuzağı: type="number" alana konsoldan alan.value ile bak; sonra geçersiz bir metin girip tekrar bak. valueAsNumber ne döndürüyor?
  7. Mobil klavye: T.C. alanını inputmode="numeric" ile ve onsuz DevTools cihaz emülasyonunda karşılaştır.
  8. Doğrulama görsel: input:invalid ile kırmızı çerçeve ver, sayfayı yenile — alanlar daha yazmadan kırmızı mı? :user-invalid ile değiştirip farkı gör.
  9. Özel kural: setCustomValidity ile "iki parola aynı olmalı" kuralını yaz. Sonra sıfırlama satırını bilerek sil ve alanın sonsuza kadar geçersiz kaldığını gözle.
  10. İstemci doğrulaması güvenlik değildir: DevTools'ta required özniteliğini elle sil ve boş formu gönder. Kaç saniye sürdü?
  11. Dosya yükleme: <input type="file"> ekle ve enctype'ı değiştirmeden gönder; Network panelinde gövdeye bak. Sonra multipart/form-data ekleyip karşılaştır.
  12. FormData: aynı name'e sahip üç onay kutusu koy; Object.fromEntries ile getAll sonuçlarını karşılaştır.
  13. Çift gönderim: Network panelinde ağı Slow 3G'ye düşür ve gönder düğmesine hızlıca üç kez bas. Kaç istek gitti? Sonra kilidi ekleyip tekrarla.
  14. Erişilebilirlik: formu yalnız klavyeyle (Tab, Boşluk, ok tuşları) doldurup gönder. Sıra mantıklı mı, odak her adımda görünüyor mu?

09Kendini sına

Bir form alanı doldurulduğu hâlde sunucuya ulaşmıyor. Muhtemel sebepler nelerdir?

Sırayla kontrol edilir: (1) name özniteliği yok — gönderilen anahtar name'dir, id değil; adı olmayan alan sessizce atlanır. (2) Alan disabled — devre dışı alanlar hiç gönderilmez; değeri korumak isteniyorsa readonly kullanılmalıdır. (3) Alan <form> etiketinin dışında kalmıştır — çözümü ya içeri almak ya da form="formId" özniteliği vermektir. (4) İşaretlenmemiş bir onay kutusu ya da seçilmemiş bir radyo grubu — bunlar "false" bile göndermez; gizli alan deseni gerekir. (5) enctype dosya yüklemesinde multipart/form-data değildir; dosya yerine yalnız ad gider. (6) JavaScript submit olayında preventDefault çağırmış ama alanı kendi gövdesine eklememiştir. (7) Aynı name'e sahip birden çok alan varken sunucu ya da Object.fromEntries yalnız sonuncuyu almıştır. Teşhis yolu tektir: Network panelinde isteğin Payload bölümüne bakıp gerçekte ne gittiğini görmek.

İstemci tarafı doğrulama varken sunucuda da doğrulama yapmak gerekir mi? Neden?

Kesinlikle gerekir ve asıl doğrulama sunucudakidir. Tarayıcı doğrulaması kullanıcı deneyimi içindir: kullanıcıyı sunucuya gidip dönmekten kurtarır, hatayı alanın yanında anında gösterir. Ama hiçbir güvenlik değeri yoktur — DevTools'tan required özniteliğini silmek, pattern'i değiştirmek ya da formu hiç kullanmadan curl/Postman ile doğrudan uç noktaya istek atmak saniyeler sürer. Saldırgan zaten tarayıcını kullanmaz. Sunucu tarafında doğrulanması gerekenler: zorunluluk, tip ve aralık, uzunluk, biçim, iş kuralları (bu il kodu var mı, bu tarih geçmişte mi), yetki (bu kullanıcı bu kaydı değiştirebilir mi) ve tutarlılık (veritabanı kısıtları — 7. hat). Uzunluk sınırı da güvenlik konusudur: maxlength yalnız yazmayı engeller, API'ye 10 MB'lık bir alan gönderilmesini engellemez. Sonuç: aynı kurallar iki yerde yazılır; bu tekrar bilinçli olarak kabul edilir, hatta bazı takımlar kuralları tek kaynaktan (şema) üretip her iki tarafa dağıtır.

Kullanıcı gönder düğmesine üst üste basınca aynı kayıt iki kez oluşuyor. Nasıl çözersin?

Üç katman gerekir ve yalnız ilk ikisi yetmez. (1) İstemci: submit olayında bir bayrakla ikinci gönderimi engelle, düğmeyi disabled yap ve metnini "Gönderiliyor…" olarak değiştir; işlem bitince finally içinde geri al. Düğmeyi olay tetiklendikten sonra devre dışı bırak, yoksa klasik formda düğmenin name/value çifti gitmez. (2) Gezinme deseni: klasik formlarda PRG (Post/Redirect/Get) uygula — POST sonrası 303 ile bir GET sayfasına yönlendir; böylece kullanıcı F5'e bastığında son istek zararsız bir GET olur. (3) Sunucu — asıl çözüm: idempotentlik anahtarı. İstemci gönderim başına benzersiz bir anahtar üretip gönderir; sunucu bu anahtarı daha önce işlediyse yeni kayıt açmaz, ilk işlemin sonucunu döndürür. Alternatif/ek olarak iş anlamında bir benzersizlik kısıtı konur (aynı kişi + aynı dönem için tek başvuru) ve çakışma yakalanır. Neden üçüncüsü şart: ağ koptuğunda istemci yanıtı görmez ama istek sunucuya ulaşmış olabilir; kullanıcı sekmeyi çoğaltabilir ya da geri gidip tekrar gönderebilir. İstemci kilidi bu senaryoların hiçbirini kapatmaz.

placeholder etiket yerine kullanılabilir mi? label neden zorunludur?

Kullanılamaz. Yer tutucunun beş sorunu vardır: (1) kullanıcı yazmaya başlayınca kaybolur — alanın ne olduğunu unutan kullanıcının geri getirme yolu yoktur; (2) kontrastı düşüktür ve WCAG kontrast ölçütünü çoğu tasarımda karşılamaz; (3) bazı ekran okuyucu ve tarayıcı bileşimlerinde hiç okunmaz ya da etiket sanılır; (4) otomatik doldurma çalıştığında yer tutucu kaybolduğu için alanların hangisi olduğu belirsizleşir; (5) çeviri ve otomatik test araçları için güvenilir bir tutamak değildir. <label> ise üç somut iş yapar: erişilebilirlik ağacında alana erişilebilir ad verir (ekran okuyucu odaklandığında bunu okur), tıklanabilir olduğu için özellikle onay kutusu ve radyo düğmesinde dokunma hedefini büyütür, ve alan her durumda görünür kalır. Doğru kullanım: etiket alanın üstünde ve her zaman görünür, yer tutucu yalnızca biçim örneği göstermek için ("GG.AA.YYYY"). Etiketi görsel olarak gizlemek zorundaysan display:none kullanma — o zaman ekran okuyucudan da gizlenir; ekran dışına taşıyan bir yardımcı sınıf kullanılır.

10Sınav köşesi

Yazılıda nasıl sorulur

(1) GET ile POST farkı ve hangisi ne zaman kullanılır. (2) enctype değerleri; dosya yüklemede hangisi zorunludur. (3) name ile id farkı. (4) disabled ile readonly farkı. (5) HTML5 girdi türlerini sayınız. (6) <label> neden kullanılır, for özniteliği neye bağlanır. (7) fieldset/legend ne işe yarar. (8) İstemci ve sunucu doğrulaması; hangisi güvenlik sağlar. (9) Radyo düğmeleri nasıl gruplanır. (10) required, pattern, min/max öznitelikleri. (11) PRG deseni nedir. (12) CSRF nedir (13. hattın konusu, formla bağlantısı sorulur).

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

Düzen: (1) Temel — "form action ve method ile tanımlanır; listeleme ve aramada GET, kayıt ve güncellemede POST kullanırım." (2) Kritik kural — "gönderilen anahtar name'dir; adı olmayan ya da disabled olan alan sunucuya hiç gitmez, işaretlenmemiş onay kutusu da gitmez — bunu bilmeyen ekipler sunucuda 'alan boş geliyor' hatasını uzun süre arar." (3) Doğrulama — "yerleşik doğrulamayı kullanıcı deneyimi için kullanırım, ama asıl doğrulama sunucudadır; DevTools'ta required'ı silmek beş saniye sürer." (4) Kıdem vuruşu — "TC ya da telefon gibi alanlarda type=number kullanmam; baştaki sıfırlar kaybolur ve tekerlek değeri değiştirir — text + inputmode + pattern yazarım." (5) Sağlamlık ve erişilebilirlik — "çift gönderimi istemci kilidi, PRG ve sunucuda idempotentlik anahtarı ile üç katmanda engellerim; her alanın label'ı olur, hatayı aria-describedby ile bağlar ve odağı ilk hatalı alana taşırım."