Aplikacje mobilne · Sieć
REST API w .NET MAUI: pobieranie danych z internetu
Prawdziwe aplikacje rzadko działają w izolacji: pobierają dane z serwera (produkty, wiadomości, pogoda) i wysyłają je z powrotem. Zobaczysz, jak aplikacja MAUI wysyła żądanie HTTP, odbiera odpowiedź w formacie JSON i zamienia ją na obiekty C#, oraz jak obsłużyć awarie sieci.
- około 60 minut
- .NET MAUI · HttpClient · JSON · Connectivity
- INF.04.6.2
Po tej lekcji: pobrać listę z REST API przez HttpClient, zamienić JSON na obiekty (ReadFromJsonAsync), nadać uprawnienie INTERNET na Androidzie i obsłużyć brak sieci oraz błędy serwera.
Lista wpisów pobierana z API
Dane na ekranie nie zawsze pochodzą z telefonu. Rozdzielimy żądanie HTTP, zamianę JSON na obiekty i wyświetlenie listy, a potem sprawdzimy błąd połączenia.
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ę dzieje, gdy sieć zawodzi
Pobierz dane przy poprawnych ustawieniach. Potem zmieniaj odpowiedź serwera (404, 500, brak odpowiedzi), wyłącz internet, odznacz uprawnienie INTERNET oraz try/catch i sprawdź, co zobaczy użytkownik w każdym przypadku. Zwróć uwagę też na licznik instancji HttpClient.
Najpierw przewidź, potem sprawdź: W modelu REST wybierz odpowiedź sukcesu i błąd. Porównaj kod HTTP z danymi JSON. Brak sieci nie powinien wyglądać jak poprawna, tylko pusta lista.
Zawsze zakładaj, że sieć może zawieść: sprawdź połączenie, użyj try/catch i pokaż użytkownikowi czytelny komunikat. 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.
| Plik | Rola w ćwiczeniu |
|---|---|
| AndroidManifest.xml (fragment) | Fragment w istniejącym pliku |
| Modele/Post.cs | Logika i obsługa danych |
| Serwisy/PostService.cs | Logika i obsługa danych |
| MainPage.xaml | Wygląd strony lub kontrolki |
| MainPage.xaml.cs | Logika i obsługa danych |
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.
<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="Wpisy z internetu" FontSize="24" />
<Button Text="Pobierz — jeszcze bez żądania" />
<ActivityIndicator IsRunning="False" />
</VerticalStackLayout>
</ContentPage>
- 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 wiadomości
Aplikacja to witryna na dane z serwera
Aplikacja z wiadomościami pobiera artykuły z serwera redakcji, pogodowa pobiera prognozę z zewnętrznego serwisu, sklepowa pobiera katalog produktów. W każdym z tych przypadków aplikacja mobilna jest „witryną” wyświetlającą dane, które przyszły przez sieć.
REST API to komunikacja przez zwykłe żądania HTTP, tymi samymi metodami co w przeglądarce: GET pobiera, POST wysyła nowy obiekt, PUT/PATCH aktualizuje, DELETE usuwa. Serwer odpowiada danymi w formacie JSON.
2 · Ściągawka
Najważniejsze elementy
| Element | Rola |
|---|---|
HttpClient | Wbudowany w .NET mechanizm wysyłania żądań HTTP. Tworzysz go raz jako pole klasy. |
GetAsync, PostAsJsonAsync | Wysyłają żądanie GET i POST (z obiektem zamienionym na JSON). |
EnsureSuccessStatusCode() | Zgłasza wyjątek, gdy serwer odpowiedział kodem błędu (404, 500). Dzięki temu nie próbujesz parsować treści błędu jako listy. |
ReadFromJsonAsync<T>() | Zamienia JSON z odpowiedzi na obiekt C# (np. List<Post>). |
Connectivity.Current.NetworkAccess | Sprawdza, czy urządzenie ma dostęp do internetu, zanim wyślesz żądanie. |
uses-permission INTERNET | Android musi jawnie zadeklarować, że aplikacja używa sieci. |
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.
namespace MojaAplikacja;
public partial class App : Application
{
public App() => InitializeComponent();
protected override Window CreateWindow(IActivationState? activationState)
=> new Window(new NavigationPage(new MainPage()));
}
CreateWindowTworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.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
Platforms/Android/AndroidManifest.xml
To fragment do istniejącego AndroidManifest.xml (fragment), nie cały plik. Zachowaj pozostałe ustawienia szablonu i dodaj podaną sekcję w odpowiednim miejscu.
Bez uprawnienia INTERNET każde żądanie sieciowe kończy się błędem, ale tylko na urządzeniu z Androidem. Na Windows może działać, co bywa mylące przy testowaniu.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
android.permission.INTERNETZezwala aplikacji na używanie sieci.android.permission.ACCESS_NETWORK_STATEPozwala sprawdzić stan sieci (używa tegoConnectivity).
Budowa krok po kroku
Post.cs
Otwórz Modele/Post.cs. Podmień wskazany plik po utworzeniu typów, do których się odwołuje. Objaśnienia pod kodem pokazują, które wartości przechodzą do następnego etapu.
Właściwości odpowiadają polom JSON (wielkość liter pasuje bez dodatkowych ustawień).
namespace MojaAplikacja.Modele;
public class Post
{
public int UserId { get; set; }
public int Id { get; set; }
public string Title { get; set; } = "";
public string Body { get; set; } = "";
}
Budowa krok po kroku
PostService.cs
HttpClient ma BaseAddress i jest polem serwisu. GET pobiera JSON jako obiekty Post, a POST wysyła obiekt. Sprawdzenie statusu HTTP odróżnia błąd od poprawnych danych.
Strona nie wie nic o adresach URL ani o JSON-ie. Woła PobierzPostyAsync() i dostaje gotową listę obiektów.
using System.Net.Http.Json;
using MojaAplikacja.Modele;
namespace MojaAplikacja.Serwisy;
public class PostService
{
// Jedna instancja HttpClient na cały serwis (pole klasy), a nie nowa przy każdym żądaniu
private readonly HttpClient _httpClient = new()
{
BaseAddress = new Uri("https://jsonplaceholder.typicode.com/")
};
public async Task<List<Post>> PobierzPostyAsync(int? userId = null)
{
string adres = userId is null ? "posts?_limit=10" : $"posts?userId={userId}";
using HttpResponseMessage response = await _httpClient.GetAsync(adres);
response.EnsureSuccessStatusCode(); // kod 404, 500 itd. zgłosi wyjątek
return await response.Content.ReadFromJsonAsync<List<Post>>() ?? new List<Post>();
}
public async Task<Post?> DodajPostAsync(Post nowyPost)
{
using HttpResponseMessage response = await _httpClient.PostAsJsonAsync("posts", nowyPost);
response.EnsureSuccessStatusCode();
// Serwer odpowiada „utworzonym” obiektem z nadanym Id
return await response.Content.ReadFromJsonAsync<Post>();
}
}
private readonly HttpClient _httpClient = new() { BaseAddress = ... }Jedna instancja na serwis. Tworzenie wielu w krótkim czasie może wyczerpać porty sieciowe systemu.userId is null ? "posts?_limit=10" : $"posts?userId={userId}"Adres z parametrem zapytania. Tak rozwiązujesz zadanie z filtrowaniem po użytkowniku.response.EnsureSuccessStatusCode()Zamienia odpowiedź z kodem błędu na wyjątek.ReadFromJsonAsync<List<Post>>() ?? new List<Post>()Deserializacja. Gdy wynik tonull, zwracamy pustą listę.PostAsJsonAsync("posts", nowyPost)Wysyła obiekt jako JSON metodą POST.
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.
Przycisk, spinner i lista z komunikatem EmptyView.
<?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"
xmlns:models="clr-namespace:MojaAplikacja.Modele"
x:Class="MojaAplikacja.MainPage" Title="REST API">
<Grid Padding="20" RowDefinitions="Auto,Auto,Auto,Auto,*" RowSpacing="14">
<Label Text="Wpisy pobrane z REST API" FontSize="24" FontAttributes="Bold" />
<Button Grid.Row="1" Text="Pobierz dane" Clicked="Pobierz_Clicked" />
<Button Grid.Row="2" Text="Wyślij testowy wpis" Clicked="Wyslij_Clicked" />
<ActivityIndicator Grid.Row="3" x:Name="Spinner" IsRunning="False" />
<CollectionView Grid.Row="4" x:Name="ListaPostow"
EmptyView="Brak danych. Kliknij przycisk pobierania.">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Post">
<Border Margin="0,5" Padding="12" Stroke="#808080">
<VerticalStackLayout>
<Label Text="{Binding Title}" FontAttributes="Bold" />
<Label Text="{Binding Body}" />
</VerticalStackLayout>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>
EmptyView="Brak danych. Kliknij przycisk pobierania."Prosty komunikat zamiast pustego ekranu.Grid RowDefinitions="Auto,Auto,Auto,*"Trzy wiersze dopasowane do zawartości i jeden, który wypełnia resztę.
Budowa krok po kroku
MainPage.xaml.cs
Handler włącza spinner, czeka na serwis i ustawia listę. finally wyłącza spinner także po wyjątku. Connectivity to wstępna wskazówka, nie gwarancja dostępności serwera.
Trzy warstwy ochrony: sprawdzenie połączenia, try/catch z dwoma rodzajami wyjątków i finally wyłączający spinner.
using MojaAplikacja.Serwisy;
namespace MojaAplikacja;
public partial class MainPage : ContentPage
{
private readonly PostService _postService = new();
public MainPage()
{
InitializeComponent();
}
private async void Pobierz_Clicked(object sender, EventArgs e)
{
// Najpierw sprawdzamy, czy urządzenie ma w ogóle dostęp do internetu
if (Connectivity.Current.NetworkAccess != NetworkAccess.Internet)
{
await DisplayAlertAsync("Brak internetu", "Połącz urządzenie z internetem i spróbuj ponownie.", "OK");
return;
}
Spinner.IsRunning = true;
try
{
ListaPostow.ItemsSource = await _postService.PobierzPostyAsync();
}
catch (HttpRequestException ex)
{
await DisplayAlertAsync("Błąd API", ex.Message, "OK"); // błąd sieci lub kod odpowiedzi
}
catch (Exception ex)
{
await DisplayAlertAsync("Błąd", ex.Message, "OK"); // każdy inny nieprzewidziany błąd
}
finally
{
Spinner.IsRunning = false; // wyłączamy zawsze, także po błędzie
}
}
private async void Wyslij_Clicked(object sender, EventArgs e)
{
Spinner.IsRunning = true;
try
{
var test = new MojaAplikacja.Modele.Post { UserId = 1, Title = "Próba API", Body = "Fikcyjne dane do ćwiczenia." };
var wynik = await _postService.DodajPostAsync(test);
await DisplayAlertAsync("Test POST", $"Serwer zwrócił id={wynik?.Id}. JSONPlaceholder symuluje zapis — wpis nie zostaje trwale zapisany.", "OK");
}
catch (Exception)
{
await DisplayAlertAsync("API", "Nie udało się wysłać danych testowych.", "OK");
}
finally { Spinner.IsRunning = false; }
}
}
Connectivity.Current.NetworkAccess != NetworkAccess.InternetLepiej od razu pokazać komunikat niż czekać na przekroczenie limitu czasu.catch (HttpRequestException ex)Błąd sieci lub odpowiedzi HTTP, z precyzyjnym komunikatem.catch (Exception ex)Zabezpieczenie na każdy inny nieprzewidziany błąd.finally { Spinner.IsRunning = false; }Wyłącza spinner zawsze, także po błędzie.- POST do JSONPlaceholder Testowy serwer zwraca udaną odpowiedź, lecz nie przechowuje nowego wpisu na stałe. To demonstracja komunikacji HTTP, nie gotowy backend. Connectivity nie gwarantuje dostępności konkretnego endpointu — nadal potrzebna jest obsługa wyjątku.
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 w komunikacji z serwerem
Brak uprawnienia INTERNET
<!-- brak uses-permission INTERNET w AndroidManifest.xml -->Kod jest poprawny, ale każde żądanie zawodzi tylko na Androidzie.
<uses-permission android:name="android.permission.INTERNET" />Android wymaga jawnej deklaracji dostępu do sieci.
Nowy `HttpClient` przy każdym żądaniu
using var client = new HttpClient(); // w każdej metodziePrzy częstym powtarzaniu może wyczerpać porty sieciowe systemu.
private readonly HttpClient _httpClient = new();Jedna instancja jako pole klasy.
Brak obsługi braku internetu i błędów serwera
ListaPostow.ItemsSource = await _postService.PobierzPostyAsync();Offline lub przy błędzie serwera aplikacja „zawiesza się” albo zamyka z nieobsłużonym wyjątkiem.
if (Connectivity.Current.NetworkAccess != NetworkAccess.Internet) { ... return; }
try { ... } catch (HttpRequestException ex) { ... }Sieć może zawieść z wielu powodów niezależnych od Twojego kodu.
Zakładanie sukcesu operacji sieciowej
„Serwer zawsze odpowie poprawnie.”Przerwane połączenie, przeciążony serwer czy zmiana adresu API psują aplikację.
Zawsze uwzględnij błąd w kodzie i pokaż użytkownikowi, co zrobić.Dobra aplikacja zachowuje się sensownie także wtedy, gdy coś zawiedzie.
Ćwiczenie z pomocą · samodzielna praca
6. Mała zmiana, którą zrobisz samodzielnie
Pokaż liczbę poprawnie pobranych wpisów.
Pokaż liczbę poprawnie pobranych wpisów. Dotychczasowe działanie ma pozostać poprawne.
? Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź
Dodaj Label na wynik liczbowy.
Krok 2 — pokaż podpowiedź
Po await zapamiętaj otrzymaną listę, ustaw ItemsSource i odczytaj Count.
Krok 3 — pokaż podpowiedź
Przy błędzie pokaż informację o nieudanym odczycie zamiast udawać świeży wynik0.
Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.
9 · Sprawdź się
Co zrobić, gdy sieć zawodzi?
Cztery sytuacje.
Podsumowanie
Co warto zapamiętać
- REST API to żądania HTTP (GET, POST, PUT/PATCH, DELETE) i odpowiedzi w JSON.
HttpClientjako jedno pole klasy.EnsureSuccessStatusCode()iReadFromJsonAsync<T>()do odpowiedzi.- Android:
uses-permission INTERNETw manifeście, inaczej każde żądanie zawiedzie. - Przed żądaniem:
Connectivity.Current.NetworkAccess. W kodzie:try/catch(zHttpRequestException) ifinally. - Całą komunikację trzymaj w osobnej klasie (serwisie), a strona tylko wyświetla wynik.
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ę →