Grafikus C# – Windows Forms tanfolyam › 15.6 lecke

Továbbtanulás · 15.6 lecke · hálózat

Webszolgáltatások használata grafikus programból

A 15.5 leckében láttad a webes szolgáltatás (API) oldalát. Most a kliens oldalon dolgozunk: a Windows Forms programod hívja az API-t, és megjeleníti az eredményt. Ez a hibrid megoldás: megvan az asztali program kényelme, az adat pedig közös, a szerveren él. A feladat első ránézésre egyszerű (HttpClient, JSON), a nehézség a valós hálózat: a kapcsolat lassú lehet, megszakadhat, a szerver hibát adhat, a felület közben nem fagyhat le. Ebben a leckében megtanulod az HttpClient helyes használatát, a JSON feldolgozását, az async/await alkalmazását a Formban, a szolgáltatás-osztályt és az interface-t, az időtúllépést és a megszakítást, a hibakezelést, és a nyilvános API-k használatának szabályait.

  • kb. 110 perc
  • haladó szint
  • API-kliens
Anime stílusú fiú és lány egy C# programozási feladatot beszél meg

A lecke végére

  • használod az HttpClientet (egy példány, BaseAddress, Timeout) a szolgáltatás hívására;
  • JSON-t olvasol és küldesz (GetFromJsonAsync, PostAsJsonAsync);
  • a hívást async/await segítségével úgy végzed, hogy a felület nem fagy le;
  • szolgáltatás-osztályt és interface-t írsz (12.8 lecke), amely mögé a hálózat rejtőzik;
  • kezeled az időtúllépést, a megszakítást, a hálózati és a szerverhibát;
  • betartod a nyilvános API-k használatának szabályait (kulcs, kvóta, feltételek, adatvédelem).

Mielőtt elkezded

Ehhez a leckéhez a 15.5 lecke (API és HTTP), a 11. fejezet (async/await), a 10. fejezet (JSON), a 12.8 lecke (szolgáltatás és interface) és a 13.6 lecke (kivételek) szükséges.

15.5 Desktop és webes felületek →

HttpClient: a kérés elküldése és a válasz feldolgozása

Az alapok (using System.Net.Http; using System.Net.Http.Json;)C#
// EGYETLEN példány az egész programban: az HttpClient-et nem hozzuk létre minden híváshoz (kapcsolatok fogynak el), és nem using-gal
private static readonly HttpClient kliens = new HttpClient
{
BaseAddress = new Uri("https://localhost:5001/"),
Timeout = TimeSpan.FromSeconds(10) // időtúllépés: különben a kérés akár perceket is várhat
};

// OLVASÁS: a válasz JSON-ját közvetlenül objektumokká (lista) alakítja
List<Diak> lista = await kliens.GetFromJsonAsync<List<Diak>>("api/diakok");

// LÉTREHOZÁS: az objektumot JSON-ként elküldi; a választ külön vizsgáljuk
HttpResponseMessage valasz = await kliens.PostAsJsonAsync("api/diakok", new { nev = "Anna" });

if (valasz.IsSuccessStatusCode) // 200–299 közötti állapotkód
{
Diak uj = await valasz.Content.ReadFromJsonAsync<Diak>();
}
else
{
string uzenet = await valasz.Content.ReadAsStringAsync(); // a szerver hibaüzenete (például 400-as válasz szövege)
// az állapotkód: (int)valasz.StatusCode
}

// TÖRLÉS
HttpResponseMessage torles = await kliens.DeleteAsync("api/diakok/3");
Egy példányAz HttpClient a háttérben kapcsolatokat tart nyitva és újrahasznál. Ha minden híváshoz újat hozol létre (és eldobod), a gép kifogyhat a hálózati kapcsolatokból (socket exhaustion). Egy static readonly példány vagy a IHttpClientFactory a helyes megoldás. Ez ritka kivétel a „mindent using-gal zárj” szabály alól (13.9 lecke).
BaseAddress és TimeoutA BaseAddress után csak a relatív útvonalat adod meg ("api/diakok", a BaseAddress végén legyen /). A Timeout alapja 100 másodperc, ami egy felületi programban túl hosszú: állíts be néhány másodpercet.
JSON kezelésA System.Net.Http.Json bővítések (GetFromJsonAsync, PostAsJsonAsync, ReadFromJsonAsync) a 10. fejezet System.Text.Json kezelését használják: a mezőnevek megfeleltetése alapból kis-nagybetű-függetlenül működik a webes (JsonSerializerDefaults.Web) beállítással, ezért a nev a Diak.Nev tulajdonságba kerül.
Állapotkód vizsgálataAz HttpClient a 400-as és 500-as válaszra nem dob kivételt (csak hálózati hibára): a IsSuccessStatusCode vagy az EnsureSuccessStatusCode() (az utóbbi kivételt dob) vizsgálja a sikert. A GetFromJsonAsync viszont kivételt dob, ha a válasz nem sikeres.

Jósolj!

A szerver 400-as állapotkódot és hibaüzenetet ad (üres név). A kódod PostAsJsonAsync-et hív, és a választ nem vizsgálja. Mit gondol a felhasználó?

Jóslat ellenőrzése

Hogy sikerült a mentés: az HttpClient nem dob kivételt a 400-as válaszra, a program tovább fut, de az adat nem került be. Mindig vizsgáld a választ (IsSuccessStatusCode), és jelezd a felhasználónak a szerver üzenetét.

Szolgáltatás-osztály és interface: a hálózat elrejtése

IDiakApi.cs, ApiHibaException.cs, DiakApiKliens.csC#
// 1. Az interface: a Form ezt látja (12.8 lecke), nem az HttpClient-et
public interface IDiakApi
{
Task<List<Diak>> ListazAsync(CancellationToken ct = default);
Task<Diak> UjAsync(string nev, CancellationToken ct = default);
Task TorolAsync(int id, CancellationToken ct = default);
}

// 2. Saját kivétel: a hálózati/szerver hibákat EGY típusba fordítjuk, a Form-nak ennyit kell kezelnie
public class ApiHibaException : Exception
{
public bool Ujraprobalhato { get; } // hálózati/időtúllépés: érdemes újra; 400-as: nem

public ApiHibaException(string uzenet, bool ujraprobalhato, Exception belso = null) : base(uzenet, belso) { Ujraprobalhato = ujraprobalhato; }
}

// 3. A megvalósítás
public class DiakApiKliens : IDiakApi
{
private readonly HttpClient kliens;

public DiakApiKliens(HttpClient kliens) { this.kliens = kliens; } // kívülről kapja: tesztelhető (13.8 lecke)

public async Task<List<Diak>> ListazAsync(CancellationToken ct = default)
{
return await Vegrehajt(async () => await kliens.GetFromJsonAsync<List<Diak>>("api/diakok", ct) ?? new List<Diak>());
}

public async Task<Diak> UjAsync(string nev, CancellationToken ct = default)
{
return await Vegrehajt(async () =>
{
HttpResponseMessage v = await kliens.PostAsJsonAsync("api/diakok", new { nev }, ct);

if (!v.IsSuccessStatusCode) { throw new ApiHibaException(await v.Content.ReadAsStringAsync(), false); } // a szerver üzenete

return await v.Content.ReadFromJsonAsync<Diak>(ct);
});
}

public async Task TorolAsync(int id, CancellationToken ct = default)
{
await Vegrehajt(async () =>
{
HttpResponseMessage v = await kliens.DeleteAsync("api/diakok/" + id, ct);

if (v.StatusCode == HttpStatusCode.NotFound) { throw new ApiHibaException("A diák már nem létezik (valaki más törölte).", false); }
if (!v.IsSuccessStatusCode) { throw new ApiHibaException("A törlés nem sikerült (" + (int)v.StatusCode + ").", true); }

return true;
});
}

// KÖZPONTI hibakezelés: minden hálózati hibát ApiHibaException-né alakít
private async Task<T> Vegrehajt<T>(Func<Task<T>> muvelet)
{
try { return await muvelet(); }
catch (ApiHibaException) { throw; } // már a mi hibánk: tovább
catch (TaskCanceledException ex) when (!ex.CancellationToken.IsCancellationRequested) { throw new ApiHibaException("A szerver nem válaszolt időben.", true, ex); } // időtúllépés
catch (HttpRequestException ex) { throw new ApiHibaException("Nem érhető el a szerver. Ellenőrizd a hálózatot.", true, ex); }
catch (JsonException ex) { throw new ApiHibaException("A szerver váratlan választ adott.", false, ex); }
// a felhasználó általi megszakítás (OperationCanceledException) átmegy: a Form külön kezeli
}
}
Az interface hasznaA Form az IDiakApi-t ismeri: a hálózat a megvalósításban van. Teszteléskor egy hamis (FakeDiakApi) osztály adhatja az adatot szerver nélkül (13.8 lecke), és később a megvalósítás (HTTP helyett fájl vagy adatbázis) cserélhető a Form módosítása nélkül.
Hibák egy típusbaA hálózat sokféle hibát adhat (HttpRequestException, időtúllépés, JSON hiba, szerverüzenet). A szolgáltatás ezeket egyetlen ApiHibaException-né alakítja érthető üzenettel: a Formnak elég egyet elkapnia, és a felhasználó nem technikai szöveget lát (13.4 lecke).
Időtúllépés és megszakításAz HttpClient.Timeout lejártakor TaskCanceledException keletkezik, akárcsak a felhasználói megszakításnál (CancellationToken). A ex.CancellationToken.IsCancellationRequested vizsgálat különbözteti meg: ha a felhasználó nem kérte, akkor időtúllépés.
Újrapróbálható?A Ujraprobalhato jelzi, hogy érdemes-e újra megpróbálni (ideiglenes hálózati hiba), vagy sem (hibás adat, 400-as válasz). Ezt a Form Újra gombja használhatja; automatikus újrapróbáláskor növekvő várakozással és korlátozott számú alkalommal tedd.

Jósolj!

A szerver leáll, a felhasználó a Frissítés gombra kattint. A szolgáltatás HttpClient.Timeout értéke az alapértelmezett 100 másodperc. Mit tapasztal a felhasználó?

Jóslat ellenőrzése

Attól függ, hogyan áll a kapcsolat: ha a szerver gép elérhető, de nem válaszol, a kérés akár 100 másodpercig várhat, mielőtt hibát jelezne (a gomb tiltva, a felület „semmit sem csinál”). Ha a gép nem elérhető, hamarabb jön a hálózati hiba. Állíts be rövid Timeout-ot (5–10 mp), jelezz folyamatban állapotot, és kínálj Megszakítás gombot.

A Form oldala: nem fagy le, nem omlik össze

Form1.cs (DataGridView, gombok, állapotsor)C#
private readonly IDiakApi api;
private CancellationTokenSource megszakitas; // a futó művelet megszakításához

public Form1(IDiakApi api) { InitializeComponent(); this.api = api; }

private async void Form1_Shown(object sender, EventArgs e) { await Frissit(); }

// EGY segéd, amely minden hálózati műveletet körbevesz: tiltás, állapot, hibakezelés, megszakítás
private async Task Fut(Func<CancellationToken, Task> muvelet, string folyamatban)
{
Foglalt(true, folyamatban);

using (megszakitas = new CancellationTokenSource())
{
try { await muvelet(megszakitas.Token); }
catch (OperationCanceledException) { tsslUzenet.Text = "Megszakítva."; }
catch (ApiHibaException ex)
{
tsslUzenet.Text = ex.Message;
btnUjra.Visible = ex.Ujraprobalhato; // csak ideiglenes hibánál kínálunk újrapróbálást
// a részleteket naplózd: naplo.Hiba("API hiba", ex); (13.7 lecke)
}
}

megszakitas = null;
Foglalt(false, null);
}

private void Foglalt(bool foglalt, string uzenet)
{
btnFrissit.Enabled = btnUj.Enabled = btnTorol.Enabled = !foglalt;
btnMegse.Enabled = foglalt;
UseWaitCursor = foglalt;
if (uzenet != null) { tsslUzenet.Text = uzenet; }
if (foglalt) { btnUjra.Visible = false; }
}

private Task Frissit() { return Fut(async ct => { dgvDiakok.DataSource = await api.ListazAsync(ct); tsslUzenet.Text = "Betöltve."; }, "Betöltés..."); }

private async void btnUj_Click(object sender, EventArgs e)
{
string nev = txtNev.Text.Trim();

if (nev.Length == 0) { tsslUzenet.Text = "Add meg a nevet."; return; } // gyors helyi ellenőrzés (a szerver is ellenőriz!)

await Fut(async ct => { await api.UjAsync(nev, ct); txtNev.Clear(); dgvDiakok.DataSource = await api.ListazAsync(ct); }, "Mentés...");
}

private async void btnTorol_Click(object sender, EventArgs e)
{
Diak d = dgvDiakok.CurrentRow?.DataBoundItem as Diak;

if (d == null) { tsslUzenet.Text = "Előbb jelölj ki egy diákot."; return; }

await Fut(async ct => { await api.TorolAsync(d.Id, ct); dgvDiakok.DataSource = await api.ListazAsync(ct); }, "Törlés...");
}

private void btnMegse_Click(object sender, EventArgs e) { megszakitas?.Cancel(); }
private async void btnUjra_Click(object sender, EventArgs e) { await Frissit(); }

// Program.cs: összeállítás (composition root, 12.9 lecke)
var http = new HttpClient { BaseAddress = new Uri("https://localhost:5001/"), Timeout = TimeSpan.FromSeconds(8) };
Application.Run(new Form1(new DiakApiKliens(http)));
Nem blokkolAz await alatt a felületi szál szabad marad (11. fejezet): az ablak mozgatható, a Megszakítás gomb működik. A válasz megérkezése után a kód a felületi szálon folytatódik, ezért közvetlenül frissítheted a vezérlőket (Invoke nem kell).
Egy segéd (Fut)A „tiltás, állapot, hibakezelés, megszakítás” minden hálózati műveletnél ugyanaz: ezt egy metódusba tesszük, nem másoljuk be minden eseménykezelőbe. Az async void csak az eseménykezelő szintjén megengedett (11.6 lecke): a Fut kezeli a hibát, így nem szökik ki kezeletlen kivétel.
VisszajelzésA felhasználónak mindig tudnia kell, mi történik: Betöltés... állapot, tiltott gombok, várakozó kurzor, érthető hibaüzenet, és megszakítási lehetőség. A hosszú, visszajelzés nélküli várakozás „lefagyásnak” tűnik, és a felhasználó többször kattint.
Ellenőrzés mindkét oldalonA helyi ellenőrzés (üres név) a gyors visszajelzésért van; a szerver ellenőrzése (15.5 lecke) a biztonságért: a Form a szerver üzenetét is megjeleníti.

Jósolj!

A felhasználó a Frissítés gomb nyomása után azonnal újra rákattint, mielőtt az első kérés befejeződne (a gombot nem tiltottad le). Mi történhet?

Jóslat ellenőrzése

Két kérés fut egyszerre; az eredmények bármilyen sorrendben érkezhetnek, és a régebbi válasz felülírhatja az újabbat (versenyhelyzet a felületen). A művelet idejére tiltsd a gombokat (Foglalt(true)), vagy az előző kérést szakítsd meg, mielőtt az újat indítod.

Nyilvános API-k használata: a játékszabályok

SzempontMire figyelj?
Feltételek és licencMinden API-nak van használati feltétele (mire használható, kell-e hivatkozni, kereskedelmi használat). Olvasd el, mielőtt a programodba építed.
API-kulcsSok szolgáltatás kulcsot ad. A kulcs titok: nem kerül a forráskódba és a verziókezelőbe (15.5 lecke), hanem beállításba vagy környezeti változóba (14.7 lecke). Asztali programban a kulcsot a felhasználó is kiolvashatja: nyilvános programba soha ne tegyél fizetős vagy veszélyes kulcsot.
Kvóta és sebességkorlátA szolgáltató korlátozza, hány kérést küldhetsz percenként/naponta; túllépéskor 429 állapotkód érkezik. Ne kérdezz ciklusban és feleslegesen: gyorsítótárazz, és várj a korlát szerint.
Csak HTTPSTitkosítatlan kapcsolaton a kulcs és az adat lehallgatható. Ellenőrizd, hogy a cím https.
AdatvédelemHa személyes adatot küldesz egy külső szolgáltatónak, az adat elhagyja a felhasználó gépét: tájékoztasd, és csak a szükségeset küldd. Iskolai környezetben ne küldj valódi diákadatot külső szolgáltatásnak.
Változás és megszűnésA külső API megváltozhat vagy megszűnhet: legyen tartalék viselkedés (gyorsítótár, érthető üzenet), és az API-hívás egy osztályban (interface mögött) legyen, hogy könnyen cserélhető legyen.
HitelességA külső adat nem feltétlenül hibátlan vagy megbízható: ellenőrizd a bemenetet (13.5 lecke), és ne bízz meg vakon benne (például ne jeleníts meg HTML-ként).
GyorsítótárHa az adat ritkán változik, mentsd el a legutóbbi választ (fájl, 14.7 lecke), és használd, amíg friss, vagy a hálózat nem érhető el: gyorsabb, kevesebb kérés, és offline is működik. Jelezd a felhasználónak, ha a megjelenített adat nem friss.
NaplózásNaplózd a kéréseket és a hibákat (cím, állapotkód, időtartam), de soha a kulcsot és a személyes adatot (13.7 lecke). A lassú vagy sikertelen hívások naplója a hibakeresés legjobb forrása.
TesztelésA teszt ne a valódi szolgáltatást hívja: használj hamis interface-megvalósítást (FakeDiakApi), vagy egy hamis HttpMessageHandler-t, amely előre megadott választ ad: gyors, megismételhető, nem fogyaszt kvótát.
ÚjrapróbálásIdeiglenes hibánál (időtúllépés, 503) az újrapróbálás hasznos, de korlátozott számban és növekvő várakozással (1, 2, 4 mp): a végtelen, gyors újrapróbálás maga is terheli a szervert.

Jósolj!

A programod egy ciklusban minden billentyűleütésnél külön kérést küld egy nyilvános keresőszolgáltatásnak, amelynek percenként 60 kérés a kvótája. Mi történik gyors gépelésnél?

Jóslat ellenőrzése

Gyorsan elfogy a kvóta: a szolgáltatás 429-es hibát ad (vagy letilt). Megoldás: késleltetett keresés (debounce: csak akkor kér, ha a felhasználó egy rövid ideig (~400 ms) nem gépel), a régi kérés megszakítása, és gyorsítótár.

📷 Képernyőkép helye

Mit tegyél ide: a Windows Forms diák-kliens a listával, az állapotsorral és a hibaüzenettel (leállított szerver).Javasolt fájlnév: winforms-15-6-kliens.webp · Alt szöveg: A Windows Forms API-kliens

Hibakeresés: te vagy a hibadetektív

Mi a baj, és hogyan javítod?

Gondold végig az okot, és csak utána nyisd le a megoldást.

1. A felület lefagy a hívás alatt

var lista = kliens.GetFromJsonAsync<List<Diak>>("api/diakok").Result;   // .Result: blokkol
Hiba és javítás

A .Result (vagy .Wait()) a felületi szálat blokkolja, és holtpontot (deadlock) is okozhat. Használj await-et, és az eseménykezelőt tedd async void-dá (11. fejezet).

2. A sikertelen mentést a program sikeresnek hiszi

Az üres név mentése után a lista nem változik, de hibaüzenet sincs.

Ok és javítás

A 400-as válaszra az HttpClient nem dob kivételt: vizsgáld az IsSuccessStatusCode-ot, és jelezd a szerver üzenetét.

3. A program percekig vár, ha nincs hálózat

A szerver nem elérhető, a felület „lefagyott”.

Ok és javítás

Hosszú (alap 100 mp) Timeout és nincs visszajelzés. Állíts be rövid Timeout-ot, jelezz folyamatot, tiltsd a gombokat, és adj Megszakítás gombot.

4. Elfogynak a hálózati kapcsolatok

Hosszabb használat után hálózati hibák jönnek.

Ok és javítás

Minden híváshoz új HttpClient jön létre. Egyetlen, megosztott példány vagy IHttpClientFactory.

5. A felhasználó látja a technikai hibaüzenetet

Az ablakban „System.Net.Http.HttpRequestException: ...” jelenik meg.

Ok és javítás

A szolgáltatás nem alakítja át a kivételt. A Vegrehajt központi hibakezelő érthető üzenetre fordítja (ApiHibaException), a részleteket a napló kapja (13.7 lecke).

6. A kulcs kikerült a nyilvános kódba

Az API-kulcs a forráskódban van, és a kódot feltöltötték.

Ok és javítás

Azonnal cseréld a kulcsot (a törlés a történetben nem elég); a kulcsot beállításból vagy környezeti változóból olvasd (14.7 lecke); a nyilvános programba ne tegyél fizetős kulcsot.

Mini projekt: diák-kliens a 15.5 lecke API-jához

Készíts Windows Forms klienst a 15.5 lecke webes szolgáltatásához (DiakWeb), hibatűrő módon:

  1. Előkészítés: indítsd el a 15.5 lecke API-ját (dotnet run), jegyezd fel a címét (a port a kimenetben látszik).
  2. Projekt: Windows Forms (DiakKliens); vezérlők: dgvDiakok, txtNev, btnUj, btnTorol, btnFrissit, btnMegse, btnUjra, állapotsor (tsslUzenet).
  3. Modell és interface: Diak, IDiakApi, ApiHibaException (a leckéből).
  4. Szolgáltatás: DiakApiKliens a központi Vegrehajt hibakezeléssel; a cím a beállításból (Beallitasok, 14.7 lecke), nem beégetve.
  5. Form: a Fut segéddel: Foglalt állapot, megszakítás, async kezelők; az indításkor automatikus betöltés.
  6. Hibatesztek: (a) üres név (a szerver 400-ast ad: az üzenet jelenjen meg); (b) nem létező azonosító törlése (másik ablakból töröld előbb); (c) a szerver leállítva (érthető üzenet, Újra gomb); (d) lassú válasz (a szerver Task.Delay(15000) késleltetéssel: a Timeout 8 mp és a Megszakítás gomb); (e) hibás JSON-válasz.
  7. Naplózás: a kérések és hibák naplózása (13.7 lecke), személyes adat nélkül; az időtartam mérésével (Stopwatch).
  8. Gyorsítótár (extra): az utolsó sikeres lista mentése fájlba (14.7 lecke); a szerver leállásakor a gyorsítótár tartalmának megjelenítése „Nem friss adat” jelzéssel.
  9. Teszt: FakeDiakApi osztály, amely a szerver nélkül adja az adatokat; a Form működésének és a hibakezelésnek az ellenőrzése vele (13.8 lecke).
Megoldás megnyitása (próbáld ki előbb önállóan!)
FakeDiakApi.cs, késleltetett végpont és gyorsítótár (using System.Text.Json;)C#
// ---- FakeDiakApi: hálózat nélküli megvalósítás a teszthez és a demóhoz ----
public class FakeDiakApi : IDiakApi
{
private readonly List<Diak> adat = new List<Diak> { new Diak { Id = 1, Nev = "Anna" }, new Diak { Id = 2, Nev = "Béla" } };
public bool Hibazzon { get; set; } // a teszt ezzel váltja ki a hibát
private int kovetkezo = 3;

public async Task<List<Diak>> ListazAsync(CancellationToken ct = default)
{
await Task.Delay(300, ct); // a hálózat lassúságának szimulálása

if (Hibazzon) { throw new ApiHibaException("Nem érhető el a szerver. Ellenőrizd a hálózatot.", true); }

return adat.ToList();
}

public Task<Diak> UjAsync(string nev, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(nev)) { throw new ApiHibaException("A név kötelező, legfeljebb 40 karakter.", false); }

Diak d = new Diak { Id = kovetkezo++, Nev = nev.Trim() };
adat.Add(d);

return Task.FromResult(d);
}

public Task TorolAsync(int id, CancellationToken ct = default)
{
if (adat.RemoveAll(x => x.Id == id) == 0) { throw new ApiHibaException("A diák már nem létezik (valaki más törölte).", false); }

return Task.CompletedTask;
}
}

// ---- a 15.5 API-ban egy LASSÚ végpont a időtúllépés próbájához (csak fejlesztéshez!) ----
app.MapGet("/api/lassu", async () => { await Task.Delay(15000); return Results.Ok(); });

// ---- Gyorsítótár: sikeres lista mentése, hiba esetén visszaolvasása ----
private async Task<List<Diak>> ListaGyorsitotarral(CancellationToken ct)
{
string fajl = Path.Combine(Utak.AdatMappa, "diakok-cache.json"); // az Utak osztály: 14.7 lecke

try
{
List<Diak> friss = await api.ListazAsync(ct);
File.WriteAllText(fajl, JsonSerializer.Serialize(friss));
tsslUzenet.Text = "Betöltve.";

return friss;
}
catch (ApiHibaException ex) when (File.Exists(fajl))
{
tsslUzenet.Text = ex.Message + " Nem friss adatot látsz (gyorsítótár).";

return JsonSerializer.Deserialize<List<Diak>>(File.ReadAllText(fajl)) ?? new List<Diak>();
}
}

Várt tapasztalat: a FakeDiakApi szerver nélkül is bemutatja a Formot és a hibakezelést (a Hibazzon igazra állításával a hibaüzenet és az Újra gomb); a /api/lassu végpont a 8 mp-es Timeout miatt „A szerver nem válaszolt időben” üzenetet ad, a Megszakítás gombra az azonnali „Megszakítva.”; a gyorsítótár a szerver leállásakor az utolsó ismert listát mutatja figyelmeztetéssel. A pontos hibaszövegek és időzítések környezetfüggők. A /api/lassu végpontot soha ne hagyd benne az éles szolgáltatásban.

📷 Képernyőkép helye

Mit tegyél ide: a kliens normál működés közben, és a „Nem érhető el a szerver” hibaüzenettel az Újra gombbal.Javasolt fájlnév: winforms-15-6-kesz.webp · Alt szöveg: A kész diák-kliens normál és hibás állapotban

Három önálló gyakorlófeladat

Egyszerre csak egyet csinálj: tervezd meg, írd meg, futtasd, ellenőrizd, és csak utána nyisd ki a megoldást.

1. Késleltetett keresés

Írj kereső mezőt, amely csak akkor küld kérést, ha a felhasználó 400 ms-ig nem gépelt, és a régi kérést megszakítja.

Egy lehetséges megoldás

Egy System.Windows.Forms.Timer (400 ms), amelyet minden TextChanged újraindít; a Tick eseményben megszakítod az előző CancellationTokenSource-t, újat hozol létre, és hívod az API-t. A kvótát és a felesleges kéréseket így kímélöd.

2. Újrapróbálás növekvő várakozással

Írj segédet, amely ideiglenes hibánál legfeljebb háromszor próbálkozik újra 1, 2, majd 4 másodperc várakozással.

Egy lehetséges megoldás

Ciklus 0–2: try a művelet; catch (ApiHibaException ex) when (ex.Ujraprobalhato && kiserlet < 3): await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, kiserlet)), ct); a végén a hiba továbbmegy. A megszakítás (ct) a várakozást is megszakítja.

3. Hamis HttpMessageHandler

Tesztelj a DiakApiKliens-t valódi szerver nélkül.

Egy lehetséges megoldás

Egy HttpMessageHandler leszármazott, amelynek SendAsync metódusa előre megadott HttpResponseMessage-et ad (200 és JSON törzs, 400 és szöveg, vagy kivétel); new HttpClient(handler) { BaseAddress = ... } átadható a DiakApiKliens-nek: ellenőrizheted a hibaátalakítást.

Gyakorlótár: 6 további feladat

Három szinten gyakorolhatsz: alap, közepes és kihívás. Csak a próbálkozás után nyisd le a megoldást.

Alap szint

1. Hány HttpClient?

Hány HttpClient példányt hozol létre egy programban?

Megoldás megnyitása

Egyet (vagy IHttpClientFactory-t használsz): a kapcsolatok újrahasznosulnak.

2. Mit ad a 400-as válasz?

Dob-e kivételt az HttpClient a 400-as válaszra?

Megoldás megnyitása

Nem (csak hálózati hibára): az IsSuccessStatusCode-ot vagy az EnsureSuccessStatusCode()-ot kell használni.

Közepes szint

3. async a Formban

Miért nem szabad .Result-ot használni a felületi szálon?

Megoldás megnyitása

Blokkolja a felületet, és holtpontot okozhat; await kell.

4. Időtúllépés

Hogyan különbözteted meg a felhasználói megszakítást az időtúllépéstől?

Megoldás megnyitása

Mindkettő TaskCanceledException; ha a saját CancellationToken megszakítást kért, akkor felhasználói megszakítás, különben időtúllépés.

Kihívás szint

5. Interface haszna

Miért kell az IDiakApi a Formnak?

Megoldás megnyitása

A Form nem függ a hálózattól: teszteléskor hamis megvalósítás adható, és a megvalósítás cserélhető a Form módosítása nélkül.

6. Kvóta

Mit teszel, ha a szolgáltatás 429-et ad?

Megoldás megnyitása

Csökkented a kérések számát (gyorsítótár, késleltetett keresés), és a megadott várakozás után próbálod újra; ha a válasz tartalmaz újrapróbálási időt (Retry-After), annyit vársz.

Gyors önellenőrzés

Válaszolj fejben, és csak utána nyisd le a választ.

1. Mit csinál a GetFromJsonAsync?

Elküldi a kérést, és a JSON választ közvetlenül objektummá alakítja.

2. Miért kell Timeout?

Hogy a program ne várjon percekig egy nem válaszoló szerverre.

3. Mit csinál a CancellationToken?

Lehetővé teszi a futó művelet megszakítását (például Megszakítás gombbal).

4. Miért fordítod saját kivételre a hálózati hibákat?

A Formnak egyetlen hibatípust kell kezelnie, érthető üzenettel, és a technikai részletek a naplóba kerülnek.

5. Mit mutat a felhasználónak várakozáskor?

Folyamatban állapotot, tiltott gombokat, várakozó kurzort és Megszakítás gombot.

6. Hol tartod az API-kulcsot?

Beállításban vagy környezeti változóban, nem a forráskódban.

7. Mit nem naplózol?

A kulcsot és a személyes adatot.

Mit tudsz már?

  • Az HttpClienttel (egy példány, Timeout) JSON-t olvasol és küldesz egy webes szolgáltatásnak.
  • A hívást async/await-tel úgy végzed, hogy a felület nem fagy le, és megszakítható.
  • Szolgáltatás-osztályt és interface-t írsz, amely mögé a hálózat és a hibák rejtőznek.
  • Betartod a nyilvános API-k szabályait, és hibatűrő, tesztelhető klienst építesz.

Következő lecke: Önálló záróprojekt ötletek

A tanfolyam zárása: ötletek, értékelési szempontok és útmutató a saját projekted megtervezéséhez.