Formularz z jednym polem działa przewidywalnie, dopóki użytkownik nie chce dodać kilku produktów, adresów albo uczestników. Wtedy potrzebna jest tablica wartości przesyłana w ramach formularza, poprawne nazwy pól oraz logika, która bezpiecznie odtworzy dane po stronie serwera. Pokażę, jak działa form array, jak budować takie formularze w HTML i Angularze oraz jak obsłużyć je w aplikacji ASP.NET Core.
Tablica pól formularza upraszcza obsługę powtarzalnych danych
- Jedna nazwa pola może reprezentować wiele przesłanych wartości.
- Indeksy w nazwach pomagają ASP.NET Core zbudować kolekcję obiektów.
- FormArray w Angularze sprawdza się przy dynamicznym dodawaniu i usuwaniu wierszy.
- Walidację trzeba wykonać po obu stronach, bo dane z przeglądarki nie są zaufane.
- Puste i usunięte elementy wymagają świadomej obsługi, szczególnie przy edycji rekordów.
Co oznacza tablica danych w formularzu
Tablica formularza to sposób przesłania wielu wartości tego samego rodzaju w jednym żądaniu HTTP. Przykładem może być lista produktów w zamówieniu, kilka numerów telefonu albo zestaw odpowiedzi udzielonych przez użytkownika. W praktyce nie chodzi o specjalny element HTML, lecz o konwencję nazewnictwa pól i sposób odczytania ich po stronie serwera.
Najprostszy wariant wygląda tak:
Po wysłaniu formularza serwer otrzyma trzy wartości powiązane z nazwą tags. To wystarcza dla tablic prostych, ale przy bardziej złożonych danych lepiej użyć indeksów:
W tym przypadku każdy element ma własny indeks i kilka właściwości. Taki zapis jest szczególnie wygodny w aplikacjach .NET, ponieważ mechanizm model bindingu może automatycznie zamienić dane formularza na listę obiektów modelu.
Jak zbudować tablicę w zwykłym HTML
HTML nie narzuca jednej składni dla tablic. Najważniejszy jest atrybut name, ponieważ to on staje się kluczem wysyłanym w formularzu. Atrybut id służy głównie do powiązania pola z etykietą, skryptem lub stylami.
Wiele wartości prostego typu
Gdy przesyłasz listę identyfikatorów albo tagów, możesz użyć tej samej nazwy w kilku kontrolkach:
Po stronie serwera otrzymasz kolekcję zaznaczonych wartości. Niezaznaczone checkboxy nie wysyłają nic, dlatego kod powinien poprawnie obsłużyć także pustą tablicę. To drobny szczegół, który często wychodzi dopiero przy formularzu edycji.
Lista obiektów
Jeżeli każdy element ma kilka pól, indeks powinien obejmować cały obiekt. W aplikacji zakupowej może to wyglądać następująco:
Warto zadbać o to, aby indeksy były spójne, szczególnie gdy wiersze można usuwać za pomocą JavaScriptu. W przeciwnym razie serwer może pominąć część danych albo połączyć je w sposób inny, niż zakłada interfejs. Przy prostym formularzu wystarczy ponowne przeliczenie indeksów przed wysłaniem.
Dynamiczne wiersze z FormArray w Angularze
W Angularze odpowiednikiem dynamicznej tablicy pól jest FormArray. Używam go wtedy, gdy liczba elementów nie jest znana z góry albo użytkownik może dodawać i usuwać wiersze bez przeładowania strony.
Przykładowy model formularza może wyglądać tak:
import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
orderForm = new FormGroup({
items: new FormArray([
this.createItem()
])
});
createItem() {
return new FormGroup({
productId: new FormControl('', Validators.required),
quantity: new FormControl(1, [
Validators.required,
Validators.min(1)
])
});
}
get items() {
return this.orderForm.controls.items;
}
addItem() {
this.items.push(this.createItem());
}
removeItem(index: number) {
this.items.removeAt(index);
}W szablonie każdy element tablicy otrzymuje własny indeks:
Najważniejsza zasada brzmi: FormArray przechowuje kontrolki, a nie same wartości. Dzięki temu Angular zna stan, błędy i walidację każdego wiersza. Sam zwykły array JavaScriptu nie daje podobnej kontroli nad formularzem.
W praktyce nie dodawałbym pustych wierszy bez ograniczenia. Ustaliłbym limit, na przykład 50 elementów, a przed wysłaniem sprawdził, czy formularz jest poprawny. Chroni to interfejs przed przypadkowym rozrostem i ogranicza ilość danych przesyłanych do API.

Jak odebrać kolekcję w ASP.NET Core
Po stronie backendu najlepiej przyjąć model dopasowany do danych biznesowych, a nie odczytywać wszystkie wartości ręcznie z formularza. Przykładowe klasy mogą wyglądać tak:
public sealed class OrderViewModel
{
public List Items { get; set; } = [];
}
public sealed class OrderLineViewModel
{
public int ProductId { get; set; }
public int Quantity { get; set; }
} Akcja kontrolera może przyjąć model bez dodatkowego mapowania:
[HttpPost]
public IActionResult Create(OrderViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
foreach (var item in model.Items)
{
// Walidacja biznesowa i zapis
}
return RedirectToAction(nameof(Index));
}Dla pól o nazwach Items[0].ProductId i Items[1].Quantity ASP.NET Core spróbuje utworzyć listę elementów. To właśnie model binding, czyli mechanizm, który łączy dane z żądania z właściwościami obiektu C#.
Przy kolekcjach warto uważać na usuwanie środkowego elementu. Jeżeli formularz wysyła indeksy 0 i 2, pomijając 1, binder może nie odtworzyć listy tak, jak oczekujesz. W formularzach edycji bezpieczniejszym rozwiązaniem jest przesyłanie stabilnych identyfikatorów albo specjalnych pól indeksu.
Takie podejście rozdziela identyfikator wiersza od jego pozycji na ekranie. Ma sens wtedy, gdy użytkownik może sortować elementy, usuwać je w dowolnej kolejności albo edytować rekordy istniejące już w bazie.
Walidacja i bezpieczeństwo danych z tablicy
Walidacja po stronie przeglądarki poprawia wygodę, ale nie zabezpiecza aplikacji. Użytkownik może zmienić żądanie ręcznie, dlatego backend powinien ponownie sprawdzić każdy element kolekcji, jego typ, zakres i uprawnienia.
- Sprawdź, czy identyfikator produktu istnieje i jest dostępny dla bieżącego użytkownika.
- Odrzuć ilości równe 0, ujemne albo przekraczające ustalony limit.
- Ustal maksymalną liczbę elementów, na przykład 50 lub 100.
- Nie ufaj cenie ani nazwie przesłanej z przeglądarki, jeśli można je pobrać z bazy.
- Obsłuż pustą kolekcję i niepoprawne indeksy bez błędu aplikacji.
Dobrym zwyczajem jest walidacja na poziomie modelu oraz dodatkowa walidacja biznesowa w serwisie. Atrybut [Range(1, 100)] może sprawdzić zakres ilości, ale nie odpowie na pytanie, czy dany produkt jest jeszcze dostępny. To dwa różne poziomy kontroli.
Przy dużych formularzach znaczenie ma również rozmiar żądania. Dla kilkunastu wierszy zwykły POST jest naturalnym rozwiązaniem, lecz przy setkach rekordów lepiej rozważyć osobny endpoint JSON, stronicowanie albo zapis etapami. Tablica w formularzu nie jest automatycznie dobrym formatem dla każdej skali.
Najczęstsze błędy przy obsłudze tablic pól
Użycie atrybutu id zamiast name
Serwer nie buduje kolekcji na podstawie identyfikatorów DOM. Jeżeli pole ma tylko id="items-0-quantity", ale nie ma poprawnego name, jego wartość może w ogóle nie trafić do danych formularza. Zawsze sprawdzam najpierw nazwy pól w narzędziach deweloperskich.
Wysyłanie indeksów, które nie odpowiadają danym
Po usunięciu wiersza indeksy wizualne i dane mogą się rozjechać. Efekt bywa zdradliwy, bo formularz wygląda poprawnie, ale backend otrzymuje brakujący element albo właściwości przypisane do złego rekordu. Rozwiązaniem jest reindeksacja przed wysłaniem albo użycie stabilnych kluczy.
Brak rozróżnienia między pustym a usuniętym elementem
W formularzu edycji pusty wiersz może oznaczać błąd użytkownika, a usunięty wiersz może oznaczać polecenie usunięcia rekordu z bazy. Nie warto zgadywać tej intencji na podstawie pustych wartości. Lepiej przesłać identyfikator oraz jawny stan, na przykład IsDeleted.
Przeczytaj również: var w JavaScripcie - zakres, hoisting i pułapki starego kodu
Przyjmowanie kolekcji bez limitu
Dynamiczny przycisk „Dodaj” powinien mieć ograniczenie. Bez niego użytkownik może przypadkiem wysłać tysiące pól, a aplikacja będzie musiała je związać, zwalidować i zapisać. Limit po stronie frontendu pomaga, ale ten sam limit trzeba egzekwować na backendzie.
Prosty wybór rozwiązania dla aplikacji webowej
| Przypadek | Najlepsze rozwiązanie | Dlaczego |
|---|---|---|
| Kilka zaznaczonych wartości | Powtarzalne pola z tym samym name | Prosty format i łatwe odczytanie kolekcji |
| Lista obiektów w MVC | Indeksowane nazwy pól | Naturalne powiązanie z modelem C# |
| Dynamiczne dodawanie w Angularze | FormArray | Kontrola stanu i walidacji każdego wiersza |
| Duży formularz lub aplikacja SPA | JSON przesyłany do API | Lepsza kontrola struktury i obsługi błędów |
Moja praktyczna reguła jest prosta. Dla klasycznego formularza MVC wybieram silnie typowany model i indeksowane pola. Dla interfejsu Angular używam FormArray z FormGroup wewnątrz. Dopiero gdy liczba danych rośnie albo formularz staje się częścią rozbudowanego procesu, przechodzę na osobny kontrakt JSON.
Od czego zacząć przy wdrażaniu tablicy formularza
Najpierw opisz strukturę jednego elementu, a dopiero później sposób dodawania kolejnych. Jeżeli pojedynczy wiersz ma jasny model, walidację i identyfikator, cała kolekcja pozostaje łatwa do testowania.
Sprawdź trzy scenariusze: pustą listę, jeden element oraz kilka elementów z usuniętym środkowym wierszem. Te przypadki szybko ujawniają problemy z nazwami pól, indeksami i mapowaniem danych.
Dobrze zaprojektowana tablica w formularzu nie wymaga skomplikowanej magii. Potrzebuje spójnego kontraktu między HTML-em, frontendem i backendem, rozsądnych limitów oraz walidacji wykonywanej po stronie serwera. To właśnie te szczegóły decydują, czy dynamiczny formularz będzie wygodnym narzędziem, czy źródłem trudnych do odtworzenia błędów.
