Lekcja 30. Data binding i formularze w Blazor — @bind i EditForm
Trudny / egzaminacyjnyPo co się tego uczymy?
Formularze w Razor Pages (lekcje 6-7) wymagają wysłania CAŁEGO formularza (POST) i przeładowania strony, żeby zobaczyć wynik walidacji. Blazor pozwala na coś więcej — dwukierunkowe wiązanie danych (data binding) aktualizujące zmienne C# NATYCHMIAST, w miarę pisania, oraz walidację formularza bez przeładowania strony. Ta lekcja pokazuje mechanizm @bind i komponent EditForm — dokładnie ten sam poziom interaktywności formularzy, co w Angularze, tylko w C#.
Teoria
@bind — dwukierunkowe wiązanie danych. <input @bind="imie" /> automatycznie: (1) wyświetla aktualną wartość zmiennej imie w polu, i (2) aktualizuje zmienną imie, gdy użytkownik coś wpisze. To BARDZO podobne do [(ngModel)] w Angularze albo Text="{Binding Właściwość, Mode=TwoWay}" z lekcji o modelach danych w dziale MAUI — ale znacznie prostsze w zapisie. Domyślnie @bind aktualizuje zmienną dopiero po opuszczeniu pola (zdarzenie change); żeby reagować na KAŻDE naciśnięcie klawisza, używa się @bind:event="oninput".
Komponent EditForm to specjalny, wbudowany komponent Blazor do budowy formularzy z walidacją — przyjmuje parametr Model (obiekt z danymi formularza) i zdarzenie OnValidSubmit (wywoływane TYLKO, gdy walidacja przeszła pomyślnie). Wewnątrz niego:
| Komponent | Rola |
|---|---|
<DataAnnotationsValidator /> |
włącza sprawdzanie atrybutów Data Annotations (te same [Required], [Range] z lekcji 7!) na modelu formularza |
<ValidationSummary /> |
pokazuje ZBIORCZĄ listę wszystkich błędów walidacji naraz |
<ValidationMessage For="@(() => Model.Nazwa)" /> |
pokazuje błąd walidacji dla KONKRETNEGO pola |
<InputText @bind-Value="Model.Nazwa" /> |
gotowa kontrolka tekstowa zintegrowana z EditForm (analogiczne: InputNumber, InputDate, InputCheckbox, InputSelect) |
Ogromna zaleta: TA SAMA klasa modelu z Data Annotations z lekcji 7 działa BEZ ŻADNYCH zmian zarówno w Razor Pages/MVC, jak i w Blazor — reguły walidacji ([Required], [Range], [EmailAddress]...) są niezależne od tego, JAKIM mechanizmem UI budujesz formularz; to jeszcze jeden dowód, że wiedza zdobyta wcześniej w tym dziale przenosi się bezpośrednio na Blazor.
Walidacja NATYCHMIASTOWA, bez przeładowania strony. W przeciwieństwie do Razor Pages (gdzie błędy walidacji widać dopiero PO wysłaniu formularza, żądaniem POST i ponownym renderowaniem strony), EditForm w Blazor Server może pokazywać błędy na bieżąco, w miarę wypełniania pól, bo cały formularz "żyje" jako część interaktywnego komponentu, bez przeładowań.
Schemat
<input @bind="imie" />
│
│ użytkownik wpisuje tekst
▼
zmienna imie (w bloku @code) aktualizuje się NATYCHMIAST
│
▼
@imie w innym miejscu widoku pokazuje aktualną wartość (bez przeładowania)
EditForm Model="Produkt" OnValidSubmit="Zapisz"
├── DataAnnotationsValidator (te same atrybuty co w lekcji 7!)
├── InputText @bind-Value="Produkt.Nazwa"
│ └── ValidationMessage For="() => Produkt.Nazwa"
├── InputNumber @bind-Value="Produkt.Cena"
└── button type="submit"
│
│ klik "Zapisz"
▼
sprawdzenie WSZYSTKICH atrybutów Data Annotations na Produkt
│
niepoprawne ──┴── poprawne
│ │
▼ ▼
pokazanie błędów wywołanie OnValidSubmit -> metoda Zapisz()
(bez przeładowania) (np. zapis do bazy przez EF Core)
Przykład z życia
Formularz dodawania produktu w panelu sprzedawcy pokazuje błąd "Cena musi być dodatnia" NATYCHMIAST po wpisaniu ujemnej wartości, bez potrzeby klikania przycisku "Zapisz" i czekania na przeładowanie strony — to poziom płynności formularzy, jakiego użytkownicy oczekują dziś od nowoczesnych aplikacji, i dokładnie to oferuje EditForm w Blazor.
Components/Pages/DodajProdukt.razor
<!-- Components/Pages/DodajProdukt.razor -->
@page "/dodaj-produkt"
@rendermode InteractiveServer
<h1>Dodaj produkt</h1>
<EditForm Model="produkt" OnValidSubmit="Zapisz" FormName="dodajProdukt">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>Nazwa produktu</label>
<InputText @bind-Value="produkt.Nazwa" />
<ValidationMessage For="@(() => produkt.Nazwa)" />
</div>
<div>
<label>Cena</label>
<InputNumber @bind-Value="produkt.Cena" />
<ValidationMessage For="@(() => produkt.Cena)" />
</div>
<button type="submit">Zapisz produkt</button>
</EditForm>
@if (!string.IsNullOrEmpty(komunikat))
{
<p class="text-success">@komunikat</p>
}
DodajProdukt.razor.cs + Modele/Produkt.cs (identyczny jak w lekcji 7)
// Components/Pages/DodajProdukt.razor.cs
using Microsoft.AspNetCore.Components;
using System.ComponentModel.DataAnnotations;
namespace MojaStronaApp.Components.Pages;
public partial class DodajProdukt : ComponentBase
{
private Produkt produkt = new();
private string komunikat = "";
private void Zapisz()
{
// Tu docelowo: zapis do bazy przez EF Core (context.Produkty.Add(produkt); ...)
komunikat = $"Zapisano produkt: {produkt.Nazwa} ({produkt.Cena:F2} zł)";
produkt = new Produkt(); // wyczyszczenie formularza po zapisie
}
}
// Modele/Produkt.cs - DOKŁADNIE TA SAMA klasa co w lekcji 7 (Razor Pages)!
public class Produkt
{
[Required(ErrorMessage = "Podaj nazwę produktu.")]
[StringLength(100, MinimumLength = 2, ErrorMessage = "Nazwa musi mieć od 2 do 100 znaków.")]
public string Nazwa { get; set; } = "";
[Range(0.01, 100000, ErrorMessage = "Cena musi być liczbą dodatnią.")]
public double Cena { get; set; }
}
Komentarz i wyjaśnienie kodu
@bind-Value="produkt.Nazwa" na InputText to szczególna odmiana @bind przeznaczona dla gotowych komponentów formularzy Blazor (InputText, InputNumber...) — łączy właściwość modelu z KONKRETNĄ właściwością komponentu (Value) zamiast zwykłego atrybutu HTML.
ValidationMessage For="@(() => produkt.Nazwa)" używa WYRAŻENIA LAMBDA wskazującego KONKRETNĄ właściwość, dla której ma się pokazać błąd — ta sama technika "wskazywania właściwości przez kod, nie przez string", którą widziałeś przy Include(p => p.Kategoria) w lekcji o EF Core; kompilator sprawdza poprawność nazwy właściwości, więc literówka zostanie wykryta przy kompilacji.
Po udanym zapisie, produkt = new Produkt(); tworzy CAŁKOWICIE NOWY, pusty obiekt i przypisuje go do pola produkt — ponieważ formularz jest związany (@bind-Value) z WŁAŚCIWOŚCIAMI tego obiektu, podstawienie nowego, pustego obiektu automatycznie czyści WSZYSTKIE pola formularza na ekranie, bez ręcznego zerowania każdego pola osobno.
Ćwiczenie samodzielne
Zbuduj powyższy formularz, uruchom aplikację i sprawdź: (1) czy próba zapisania pustej nazwy pokazuje błąd BEZ przeładowania strony, (2) czy po poprawnym zapisie pola formularza faktycznie się czyszczą, (3) dodaj prostą etykietę <p>Podgląd: @produkt.Nazwa - @produkt.Cena zł</p> POZA formularzem i sprawdź, że aktualizuje się na żywo w miarę pisania (dzięki @bind:event="oninput" na polu Nazwa).
Zadania do pracy własnej
Dodaj do modelu
ProduktwłaściwośćOpisz atrybutem[StringLength(500)]i odpowiadający jej<InputTextArea @bind-Value="produkt.Opis" />w formularzu.Zbuduj formularz rejestracji z polami Hasło i Powtórz hasło, wykorzystując
[Compare("Haslo")]z lekcji 7 (te same Data Annotations!) — sprawdź, że komunikat o niezgodności haseł pojawia się poprawnie wEditForm.Zbuduj formularz z polem
<InputSelect @bind-Value="produkt.KategoriaId">wypełnianym listą kategorii pobraną wOnInitializedAsync(na razie ze sztywnej listy w pamięci, docelowo z EF Core), oraz dodaj WŁASNY atrybut walidacyjny (dziedziczący poValidationAttribute, jak w zadaniu z lekcji 7) sprawdzający niestandardową regułę biznesową — sprawdź, że działa identycznie w Blazor jak w Razor Pages, potwierdzając pełną przenaszalność Data Annotations między oboma podejściami.
Typowe błędy
Zapomnienie o <DataAnnotationsValidator /> wewnątrz EditForm — bez tego komponentu WSZYSTKIE atrybuty [Required]/[Range] na modelu są całkowicie ignorowane, formularz przyjmie dowolne dane.
Używanie zwykłego @bind zamiast @bind-Value na komponentach InputText/InputNumber — te gotowe komponenty formularzy Blazor wymagają specjalnej składni @bind-Value, zwykły @bind na zwykłym <input> HTML też działa, ale traci integrację z automatyczną walidacją EditForm.
Zapominanie o wyczyszczeniu formularza (nowy obiekt modelu) po udanym zapisie — bez tego stare dane pozostają widoczne w polach, myląc użytkownika, czy operacja faktycznie się powiodła i czy formularz jest gotowy na kolejny wpis.
Nawiązanie do egzaminu zawodowego
To materiał DODATKOWY — pokazuje jednak kluczową zaletę architektury tego kursu: Data Annotations z lekcji 7 działają IDENTYCZNIE w Razor Pages, MVC i Blazor, więc nauka walidacji nie jest przywiązana do jednego konkretnego stylu budowania UI. Do egzaminu INF.04.7 wystarczą Razor Pages/MVC (lekcje 3-8) — ta lekcja jest dla chętnych, którzy chcą zobaczyć bardziej interaktywny wariant tego samego mechanizmu. Kolejna lekcja dodaje routing i wspólny layout dla wielostronicowej aplikacji Blazor.