Aplikacje mobilne · Dane

Modele danych i walidacja formularzy w .NET MAUI

Aplikacje ciągle pracują z danymi z formularzy: imię, wzrost, waga, e-mail, wiek. Kilkanaście osobnych zmiennych szybko staje się nieczytelne, a niesprawdzone dane psują działanie programu. Poznasz modele danych (klasy grupujące dane) i walidację (sprawdzanie przed użyciem).

  • około 55 minut
  • .NET MAUI · model · walidacja · double?
  • INF.04.6.2

Po tej lekcji: zbudować klasę modelu z typami nullowalnymi, napisać osobny walidator zwracający listę błędów, bezpiecznie zamienić tekst na liczbę i przekazać model na kolejną stronę.

Formularz, który nie przepuszcza błędnych danych

Klawiatura numeryczna nie gwarantuje poprawnej liczby. Najpierw zbierzemy błędy, a dopiero po udanej kontroli stworzymy model i otworzymy podsumowanie.

Najpierw uruchomisz mały ekran. Potem połączysz go z kodem i sprawdzisz konkretne wyniki. Dodatki wykonuj dopiero, gdy wersja podstawowa działa. Quiz i oceniane zadania pozostają w biurku po zalogowaniu.

Laboratorium · ćwiczenie w przeglądarce, do którego odsyłają rozgrzewki na końcu lekcji

1 · Sprawdź w działaniu

Co się stanie przy błędnych danych

Formularz startuje z błędnymi danymi. Kliknij „Wyślij” i zobacz listę błędów. Potem wpisz poprawne dane, aby zobaczyć powstający model. Przełącz też zamianę na double.Parse, a zobaczysz, czemu wywołanie przed walidacją jest ryzykowne.

Najpierw przewidź, potem sprawdź: W modelu walidacji przetestuj puste imię i niepoprawny wzrost jednocześnie. Sprawdź, czy lista błędów pomaga poprawić wszystko za jednym razem.

Ta interakcja wymaga włączonego JavaScriptu.

Najpierw walidacja (TryParse), dopiero potem double.Parse. Błędów zbieraj listę, aby użytkownik poprawił wszystko za jednym razem. Model pomaga zobaczyć mechanizm; właściwą aplikację uruchomisz z plików poniżej.

1. Przygotuj miejsce pracy

Co dodajesz, a co podmieniasz?

Stronę dodaj przez Projekt → Dodaj → Nowy element → .NET MAUI ContentPage (XAML). Powstaje para .xaml i .xaml.cs. Dla ContentView wybierz szablon ContentView; dla modeli i serwisów zwykłą klasę. Jeśli plik już istnieje, podmień wskazaną zawartość — nie twórz drugiej klasy o tej samej nazwie.

PlikRola w ćwiczeniu
Models/DaneFormularza.csLogika i obsługa danych
Logika/WalidatorFormularza.csLogika i obsługa danych
MainPage.xamlWygląd strony lub kontrolki
MainPage.xaml.csLogika i obsługa danych
Podsumowanie.xaml.csLogika i obsługa danych
Podsumowanie.xamlWygląd strony lub kontrolki

Zanim uruchomisz: trzy połączenia

x:Class="MojaAplikacja.NazwaStrony" w XAML musi zgadzać się z namespace MojaAplikacja i nazwą klasy C#. InitializeComponent() tworzy kontrolki opisane w XAML.

x:Name="PoleImie" daje kodowi C# dostęp przez PoleImie.Text. Z kolei Clicked="Zapisz_Clicked" wskazuje metodę o tej nazwie w pliku .xaml.cs. Sam napis przycisku w Text niczego nie podpina.

Gdy dodajesz XAML z obsługą zdarzeń, od razu uzupełnij też odpowiadający C#. Uruchamiaj dopiero po skończeniu oznaczonej pary plików; brakujący handler nie jest błędem środowiska.

2. Uruchom sam wygląd — jeszcze bez logiki

W świeżym projekcie zastąp MainPage.xaml poniższym ekranem. W MainPage.xaml.cs zachowaj konstruktor z InitializeComponent(); usuń szablonowy licznik i OnCounterClicked, bo nie ma ich już w XAML. Na tym etapie App i AppShell pozostają z szablonu. To pełny, tymczasowy XAML — później świadomie go podmienisz, nie dokleisz do drugiego ContentPage.

MainPage.xaml — etap tymczasowyxaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.MainPage">
    <VerticalStackLayout Padding="24" Spacing="14">
        <Label Text="Dane do sprawdzenia" FontSize="24" />
        <Entry Placeholder="Imię" />
        <Entry Placeholder="Wzrost w cm" Keyboard="Numeric" />
    </VerticalStackLayout>
</ContentPage>
  1. Pierwsza próba Uruchom. Zobaczysz kontrolki, ale przyciski bez Clicked jeszcze nic nie robią. Sprawdź rozmiar i kolejność elementów. Zatrzymaj aplikację przed następnym etapem.

Zacznijmy od teczki

Jedna teczka zamiast kilkunastu karteczek

Model to zwykła klasa C#, która opisuje dane: ma tylko właściwości, bez logiki interfejsu. Dzięki niej dane z formularza trzymasz w jednym obiekcie i możesz przekazać je na inną stronę jednym argumentem konstruktora.

Walidacja to sprawdzenie danych, zanim ich użyjesz (wyświetlisz, zapiszesz, wyślesz). Zgodnie z zasadą z poprzedniej lekcji leży w osobnej klasie, a strona tylko decyduje, jak pokazać błędy.

3 · Co sprawdzać

Typowe reguły w formularzach mobilnych

RegułaJak sprawdzić w C#
Pole nie może być pustestring.IsNullOrWhiteSpace(tekst) zwraca true dla null, pustego napisu i samych spacji.
Tekst musi być liczbądouble.TryParse(tekst, out var wynik) zwraca false, gdy tekst nie jest liczbą, bez rzucania wyjątku.
Liczba w dopuszczalnym zakresieZwykłe porównania: if (wzrost <= 0 || wzrost > 300).
Dobra praktyka: walidator zwraca informację o błędach, a stronę decyduje, jak je pokazać (np. `DisplayAlert`).

3. Wskaż stronę startową ćwiczenia

Po dodaniu docelowych stron podmień App.xaml.cs na poniższy. App.xaml zostaje z szablonu. Od tej chwili startuje MainPage, a nie wcześniejszy tymczasowy MainPage z AppShell. Uzupełnij jej pliki w kolejnych etapach, zanim ponownie uruchomisz projekt.

App.xaml.cscsharp
namespace MojaAplikacja;

public partial class App : Application
{
    public App() => InitializeComponent();
    protected override Window CreateWindow(IActivationState? activationState)
        => new Window(new NavigationPage(new MainPage()));
}
  1. CreateWindow Tworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.
  2. new MainPage() Tworzy instancję docelowej strony. Aby testować drugi niezależny przykład, zatrzymaj program, zmień tylko ten typ i uruchom ponownie.

4. Połącz wygląd z działaniem

Teraz zbuduj docelowy przykład. Pełne pliki poniżej zastępują swoje wcześniejsze wersje. Przeczytaj opis nad kodem i objaśnienia pod nim; potem sprawdź przepływ danych, nie tylko brak czerwonych podkreśleń.

Gdy przykład ma kilka plików zależnych od siebie, najpierw dodaj wszystkie wskazane typy, a potem uruchom całość. Ustaw breakpoint w obsłudze głównego przycisku i prześledź jedną próbę.

Budowa krok po kroku

DaneFormularza.cs

Dodaj DaneFormularza w folderze Models. Model przenosi wartości między stronami, bez odwołań do kontrolek. double? oznacza liczbę lub brak liczby.

Model ma tylko właściwości. Zwróć uwagę na typy z ?.

Models/DaneFormularza.cscsharp
namespace MojaAplikacja.Models;

public class DaneFormularza
{
    public string Imie { get; set; } = "";
    public double? WzrostCm { get; set; }
    public double? WagaKg { get; set; }
    public string Plec { get; set; } = "Nie podano";
}
  1. double? WzrostCm Typ nullowalny: liczba albo brak wartości (null). Zwykły double zawsze ma wartość (domyślnie 0), więc nie odróżnisz „wpisano 0” od „pole jeszcze puste”.
  2. string Imie { get; set; } = "" Wartość domyślna pusty napis zapobiega null w tekście.
  3. string Plec = "Nie podano" Sensowna wartość domyślna dla niewybranej płci.

Budowa krok po kroku

WalidatorFormularza.cs

Najpierw zbieramy komunikaty do List<string>. Wynik krotki zawiera bool ok oraz listę błędów. TryParse i IsFinite odrzucają błędny zapis, NaN i nieskończoność.

Statyczna klasa zwraca krotkę: czy dane są poprawne oraz listę błędów.

Logika/WalidatorFormularza.cscsharp
namespace MojaAplikacja.Logika;

public static class WalidatorFormularza
{
    public static (bool ok, List<string> bledy) Sprawdz(
        string? imieTxt, string? wzrostTxt, string? wagaTxt)
    {
        var bledy = new List<string>();

        if (string.IsNullOrWhiteSpace(imieTxt))
            bledy.Add("Podaj imię.");

        var wzrost = SprobujLiczbe(wzrostTxt);
        if (wzrost is null)
            bledy.Add("Wzrost musi być liczbą.");
        else if (wzrost <= 0 || wzrost > 300)
            bledy.Add("Wzrost w zakresie 1-300 cm.");

        var waga = SprobujLiczbe(wagaTxt);
        if (waga is null)
            bledy.Add("Waga musi być liczbą.");
        else if (waga <= 0 || waga > 400)
            bledy.Add("Waga w zakresie 1-400 kg.");

        return (bledy.Count == 0, bledy);
    }

    private static double? SprobujLiczbe(string? tekst)
    {
        if (double.TryParse(tekst, out var wynik) && double.IsFinite(wynik))
            return wynik;
        return null;
    }
}
  1. (bool ok, List<string> bledy) Krotka: dwa wyniki naraz, nazwane ok i bledy.
  2. var wzrost = SprobujLiczbe(wzrostTxt) Zamienia tekst na double?: liczba albo null.
  3. if (wzrost is null) ... else if (...) Najpierw sprawdzamy, czy to w ogóle liczba, a dopiero potem zakres.
  4. return (bledy.Count == 0, bledy) Brak błędów oznacza dane poprawne.
  5. private static double? SprobujLiczbe Pomocnicza metoda: TryParse w bezpiecznym opakowaniu.

Budowa krok po kroku

MainPage.xaml

Dodaj lub otwórz MainPage.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.

Formularz w ScrollView, z klawiaturą numeryczną dla liczb.

MainPage.xamlxaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.MainPage" Title="Formularz">
    <ScrollView>
        <VerticalStackLayout Padding="16" Spacing="12">

            <Label Text="Imię" />
            <Entry x:Name="PoleImie" Placeholder="Wpisz imię" />

            <Label Text="Wzrost (cm)" />
            <Entry x:Name="PoleWzrost" Keyboard="Numeric" Placeholder="np. 175" />

            <Label Text="Waga (kg)" />
            <Entry x:Name="PoleWaga" Keyboard="Numeric" Placeholder="np. 70" />

            <Label Text="Płeć" />
            <Picker x:Name="PolePlec">
                <Picker.ItemsSource>
                    <x:Array Type="{x:Type x:String}">
                        <x:String>Kobieta</x:String>
                        <x:String>Mężczyzna</x:String>
                        <x:String>Nie podano</x:String>
                    </x:Array>
                </Picker.ItemsSource>
            </Picker>

            <Button Text="Wyślij" Clicked="PrzyciskWyslij_Clicked" />

        </VerticalStackLayout>
    </ScrollView>
</ContentPage>
  1. Keyboard="Numeric" Telefon pokaże klawiaturę z cyframi.
  2. Picker z x:Array Lista płci zapisana w XAML.

Budowa krok po kroku

MainPage.xaml.cs

Nie twórz modelu przed walidacją. return kończy handler po błędnych danych; tylko poprawna ścieżka dochodzi do PushAsync.

Najpierw walidacja, potem model, potem nowa strona. Kolejność jest zawsze taka sama.

MainPage.xaml.cscsharp
using MojaAplikacja.Logika;
using MojaAplikacja.Models;

namespace MojaAplikacja;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private async void PrzyciskWyslij_Clicked(object sender, EventArgs e)
    {
        var (ok, bledy) = WalidatorFormularza.Sprawdz(
            PoleImie.Text, PoleWzrost.Text, PoleWaga.Text);

        if (!ok)
        {
            await DisplayAlertAsync("Popraw dane", string.Join("\n", bledy), "OK");
            return;
        }

        var dane = new DaneFormularza
        {
            Imie = PoleImie.Text,
            WzrostCm = double.Parse(PoleWzrost.Text),
            WagaKg = double.Parse(PoleWaga.Text),
            Plec = PolePlec.SelectedItem?.ToString() ?? "Nie podano"
        };

        await Navigation.PushAsync(new Podsumowanie(dane));
    }
}
  1. var (ok, bledy) = WalidatorFormularza.Sprawdz(...) Dekonstrukcja krotki do dwóch zmiennych.
  2. string.Join("\n", bledy) Wszystkie błędy w jednym oknie, każdy w nowej linii. Znak \n to koniec linii.
  3. double.Parse(PoleWzrost.Text) Bezpieczne dopiero po walidacji, bo wiemy, że tekst jest liczbą.
  4. PolePlec.SelectedItem?.ToString() ?? "Nie podano" Ochrona przed brakiem wyboru w pickerze.
  5. Navigation.PushAsync(new Podsumowanie(dane)) Cały model przechodzi na kolejną stronę jednym argumentem.

Budowa krok po kroku

Podsumowanie.xaml.cs

Ten ekran dostaje gotowy model jako argument konstruktora. BindingContext ustawia źródło dla wszystkich jego etykiet Binding.

Strona dostaje model w konstruktorze i ustawia go jako BindingContext. Etykiety w XAML wiążą się z właściwościami modelu przez {Binding}.

Podsumowanie.xaml.cscsharp
using MojaAplikacja.Models;

namespace MojaAplikacja;

public partial class Podsumowanie : ContentPage
{
    public Podsumowanie(DaneFormularza dane)
    {
        InitializeComponent();
        BindingContext = dane;   // wiązania {Binding ...} czytają właściwości modelu
    }
}
  1. BindingContext = dane Wskazuje źródło danych dla wiązań.
  2. {Binding WzrostCm, StringFormat='Wzrost: {0} cm'} Wiązanie z formatowaniem tekstu w samym XAML.

Budowa krok po kroku

Widok podsumowania danych

Dodaj lub otwórz Podsumowanie.xaml i podmień jego zawartość. Najpierw odszukaj kontener, potem nazwane kontrolki oraz wskazane zdarzenia. Pozostaw zgodne x:Class i nazwę klasy w pliku .xaml.cs.

BindingContext ustawiony w konstruktorze jest źródłem dla tych właściwości. x:DataType opisuje typ modelu dla kompilowanych wiązań. Formularz uruchom w NavigationPage, jak w lekcji11.

Podsumowanie.xamlxaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:models="clr-namespace:MojaAplikacja.Models"
             x:Class="MojaAplikacja.Podsumowanie" x:DataType="models:DaneFormularza">
    <VerticalStackLayout Padding="20" Spacing="12">
        <Label Text="{Binding Imie}" />
        <Label Text="{Binding WzrostCm, StringFormat='Wzrost: {0} cm'}" />
        <Label Text="{Binding WagaKg, StringFormat='Waga: {0} kg'}" />
        <Label Text="{Binding Plec}" />
    </VerticalStackLayout>
</ContentPage>
  1. Przeczytaj powiązanie x:Name identyfikuje kontrolkę, a Binding odczytuje właściwość obiektu ustawionego jako BindingContext. To dwa różne mechanizmy.

5. Odbiór: czy naprawdę działa?

Wykonaj próby w swojej aplikacji, nie tylko w modelu na stronie. Zaznaczaj dopiero po sprawdzeniu wyniku. Funkcje urządzenia wymagają testu na właściwej platformie.

8 · Czego unikać

Cztery błędy z modelami i walidacją

`double` zamiast `double?` w modelu
Tak nie
public double WzrostCm { get; set; }

Pole zawsze ma wartość (0), więc nie odróżnisz „wpisano 0” od „nic nie wpisano”.

Tak
public double? WzrostCm { get; set; }

Typ nullowalny wyraża brak wartości.

`double.Parse` przed walidacją
Tak nie
double wzrost = double.Parse(PoleWzrost.Text);

Dla „abc” zgłasza wyjątek i aplikacja się wywraca.

Tak
if (double.TryParse(PoleWzrost.Text, out var wzrost)) { ... }

Najpierw TryParse, dopiero potem bezpieczny Parse.

Walidacja w code-behind
Tak nie
// 40 linii warunków w PrzyciskWyslij_Clicked

Logika miesza się z interfejsem i nie da się jej użyć w innym formularzu.

Tak
var (ok, bledy) = WalidatorFormularza.Sprawdz(...);

Osobna klasa jest wielokrotnego użytku i łatwiejsza do przetestowania.

Tylko jeden błąd na raz
Tak nie
if (string.IsNullOrWhiteSpace(imie)) return "Podaj imię.";

Użytkownik poprawia jedno pole i dostaje kolejny błąd, którego wcześniej nie widział.

Tak
bledy.Add("Podaj imię.");   // i dalej kolejne reguły

Lista błędów pozwala pokazać wszystkie problemy od razu.

Ćwiczenie z pomocą · samodzielna praca

6. Mała zmiana, którą zrobisz samodzielnie

Dodaj do danych pole Miasto, bez ograniczeń liczbowych.

Oczekiwany rezultat
Dodaj do danych pole Miasto, bez ograniczeń liczbowych.
Dotychczasowe działanie ma pozostać poprawne.
Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź

Dodaj string Miasto do modelu i Entry na pierwszej stronie.

Krok 2 — pokaż podpowiedź

Uzupełnij inicjalizator DaneFormularza po udanej walidacji.

Krok 3 — pokaż podpowiedź

Dodaj Label z Binding Miasto na podsumowaniu, którego BindingContext jest tym modelem.

Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.

9 · Sprawdź się

Model, walidacja czy strona?

Cztery sytuacje.

Ta interakcja wymaga włączonego JavaScriptu.

Podsumowanie

Co warto zapamiętać

  1. Model to klasa z właściwościami bez logiki UI. Typy z ? (double?) wyrażają brak wartości.
  2. Walidator to osobna klasa zwracająca (ok, lista błędów). Pokazanie błędów to zadanie strony.
  3. Zawsze TryParse, a dopiero po walidacji Parse.
  4. Zbieraj wszystkie błędy naraz (string.Join).
  5. Model przekazujesz na kolejną stronę jednym argumentem, a strona ustawia go jako BindingContext.

Dalej na swoim koncie

Rozgrzewki, quiz i zadania są w biurku ucznia.

Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.

Zaloguj się →
Źródła i dalsza nauka