Dataniedziela, 9 sierpnia 2026 Czas08:04:49
← Aplikacje webowe

Lekcja 30. Data binding i formularze w Blazor — @bind i EditForm

Trudny / egzaminacyjny

Po 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

  1. Dodaj do modelu Produkt właściwość Opis z atrybutem [StringLength(500)] i odpowiadający jej <InputTextArea @bind-Value="produkt.Opis" /> w formularzu.

  2. 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 w EditForm.

  3. Zbuduj formularz z polem <InputSelect @bind-Value="produkt.KategoriaId"> wypełnianym listą kategorii pobraną w OnInitializedAsync (na razie ze sztywnej listy w pamięci, docelowo z EF Core), oraz dodaj WŁASNY atrybut walidacyjny (dziedziczący po ValidationAttribute, 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.