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,enctypeve hangisi ne zamannameözniteliğinin zorunluluğu — adı olmayan alan gönderilmez- Girdi türleri ve gerçek davranışları;
number'ın tuzakları inputmode,autocompleteve mobil klavye deneyimilabel,fieldset,legendile doğru etiketleme- Onay kutusu ve radyo düğmesinin
valuedavranışı - 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/readonlyfarkı, 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>
| Öznitelik | Anlamı | Not |
|---|---|---|
action | Verinin gideceği adres | Boş bırakılırsa bulunduğun sayfaya gönderilir |
method | get ya da post | HTML formu yalnız bu ikisini destekler — PUT/DELETE için JavaScript gerekir |
enctype | Gövdenin kodlanma biçimi | Yalnız post ile anlamlı |
novalidate | Yerleşik doğrulamayı kapatır | Kendi doğrulama akışını kurarken kullanılır |
autocomplete="off" | Tarayıcı otomatik doldurmasını kapatır | Genelde kullanma — kullanıcıya zarar verir; tarayıcılar çoğu zaman yok sayar |
GET mi POST mu
GET | POST | |
|---|---|---|
| Veri nerede | URL'de sorgu dizesinde | İstek gövdesinde |
| Yer imi / paylaşım | Olur — adres sonucu taşır | Olmaz |
| Tarayıcı geçmişi ve günlükler | Değerler görünür | Görünmez |
| Boyut sınırı | Pratikte ~2000 karakter (sunucu/ara sunucu sınırı) | Yok denecek kadar geniş |
| Yenilemede | Sorunsuz | "Formu yeniden gönder?" uyarısı |
| Yan etki | Olmamalı (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ğer | Gövde | Ne zaman |
|---|---|---|
application/x-www-form-urlencoded | ad=Ali&yas=30 | Varsayılan; metin alanları |
multipart/form-data | Sınırlayıcılarla ayrılmış bölümler | Dosya yüklemede zorunlu |
text/plain | Düz metin | Kullanma — 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:
disabledalanlar — hiç gönderilmez. Bir alanı kullanıcıya göstermek ama değiştirtmemek ve yine de göndermek istiyorsanreadonlykullan.- İş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ür | Ne yapar | Bilinmesi gereken |
|---|---|---|
text | Tek satır metin | Varsayılan; maxlength ile sınırla |
email | E-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 |
password | Karakterleri gizler | Yalnız görsel; güvenliği HTTPS sağlar |
number | Sayısal, artırma okları | Değeri okurken dize döner; valueAsNumber kullan. Geçersiz metinde value boş dize olur — girilen bozuk veriyi göremezsin |
tel | Telefon klavyesi | Hiçbir doğrulama yapmaz — biçimler ülkeden ülkeye değişir; pattern ekle |
url | Adres kontrolü | Şema zorunlu: ornek.com geçersiz, https://ornek.com geçerli |
date / time / datetime-local | Tarayıcının tarih seçicisi | Değer her zaman ISO biçimindedir (2026-09-15), görünen biçim kullanıcının diline göre değişir |
search | Temizleme düğmesi, arama klavyesi | Mobilde Enter tuşu "Ara" olur |
file | Dosya seçici | accept ve multiple; enctype şart. accept filtredir, kısıt değildir |
hidden | Görünmez ama gönderilir | Gizli değildir — kaynak kodda görünür ve değiştirilebilir |
range | Kaydırıcı | Kesin değer gerekiyorsa yanına sayı göster |
color | Renk seçici | Her zaman #rrggbb döner |
checkbox / radio | Çoklu / tekli seçim | Radyo 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:
| Öznitelik | Kontrol |
|---|---|
required | Boş bırakılamaz |
minlength / maxlength | Karakter sayısı (maxlength yazmayı engeller, doğrulama üretmez) |
min / max / step | Sayı ve tarih aralığı |
pattern | Düzenli ifade — baştan sona eşleşmeli, ^$ yazmaya gerek yok |
type | email, 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-describedbyalanı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
- 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. Hepsinelabel[for]bağla. nametesti: bir alandanname'i sil, formuGETile gönder ve adres çubuğunda o alanın olmadığını gör.disabledvsreadonly: aynı alanı bir kezdisabled, bir kezreadonlyyapıp gönder. Hangisi adres çubuğunda göründü?- Onay kutusu: KVKK kutusunu işaretlemeden gönder — sorgu dizesinde ne var? Sonra gizli alan desenini uygulayıp tekrarla.
- Radyo grubu: iki radyo düğmesine farklı
namever ve ikisini birden seçebildiğini gör; sonra düzelt. numbertuzağı:type="number"alana konsoldanalan.valueile bak; sonra geçersiz bir metin girip tekrar bak.valueAsNumberne döndürüyor?- Mobil klavye: T.C. alanını
inputmode="numeric"ile ve onsuz DevTools cihaz emülasyonunda karşılaştır. - Doğrulama görsel:
input:invalidile kırmızı çerçeve ver, sayfayı yenile — alanlar daha yazmadan kırmızı mı?:user-invalidile değiştirip farkı gör. - Özel kural:
setCustomValidityile "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. - İstemci doğrulaması güvenlik değildir: DevTools'ta
requiredözniteliğini elle sil ve boş formu gönder. Kaç saniye sürdü? - Dosya yükleme:
<input type="file">ekle veenctype'ı değiştirmeden gönder; Network panelinde gövdeye bak. Sonramultipart/form-dataekleyip karşılaştır. FormData: aynıname'e sahip üç onay kutusu koy;Object.fromEntriesilegetAllsonuçlarını karşılaştır.- Ç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.
- 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."
