Læringsmål
- Mappe CRUD til HTTP-metoder mod REST API.
- Bruge
GetFromJsonAsync,PostAsJsonAsyncmed DTO/records. - Håndtere
HttpRequestException, statuskoder og tomme svar. - Registrere typed HttpClient i Program.cs (se DI-lektion).
Hvornår bruger I det? Når UI skal vise data uden for jeres egen database — Pokémon-lister, LoL-champions, Rick and Morty-episoder. HttpClient er broen mellem Blazor-state og OpgaverAPI; DI-lektionen viser hvor I registrerer klienten.
Tjek dig selv før du går videre
- Du håndterer IsSuccessStatusCode og viser fejl i UI.
- Du kan læse ProblemDetails/title fra API-svar.
- Du har afspillet async-lab og HTTP-sim.
Typisk elev-fejl: alt virker lokalt, men i Dokploy får I 404 — symptom: tom liste, ingen fejltekst. Fix: tjek BaseAddress + relative path (én konvention for slash) og at nginx proxyer /api til rigtig backend.
CRUD over HTTP: POST create, GET read, PUT/PATCH update, DELETE delete. I Blazor er kaldene async — UI-tråden blokeres ikke mens I venter på Pokémon-JSON.
Se HttpClient i aktion
Samme request din Blazor-app sender — afspil GET, POST eller 401 uden nøgle.
# Vælg scenarie og tryk Afspil
Async-tidslinje
Se rækkefølgen: UI viser loading → await HTTP → UI opdateres med resultat.
- Bruger klikker «Hent Pokémon»
- loading = true (render)
- await GetFromJsonAsync — tråd frigives
- JSON deserialiseres til List<PokeDto>
- loading = false, liste vises

@page "/pokemon"
@inject HttpClient Http
<button @onclick="Load" disabled="@loading">Hent Pokémon</button>
@if (loading) { <p>Henter…</p> }
@if (error is not null) { <p class="err">@error</p> }
@if (items is not null)
{
<ul>@foreach (var p in items) { <li>@p.Name</li> }</ul>
}
@code {
List<PokeDto>? items;
bool loading;
string? error;
async Task Load()
{
loading = true;
error = null;
try
{
items = await Http.GetFromJsonAsync<List<PokeDto>>("/go/pokemon?limit=20");
}
catch (HttpRequestException ex)
{
error = $"API fejl: {ex.StatusCode}";
}
finally { loading = false; }
}
public record PokeDto(string Name, int Id);
}
DTO og JSON-shape
Property-navne skal matche JSON (case-insensitive default). Brug [JsonPropertyName("id")] ved behov. Records er gode til read-only API-data.
public record ChampionDto(string Id, string Name);
// Explicit check
var response = await Http.GetAsync("/LeagueOfLegends/Champions");
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
throw new InvalidOperationException($"{response.StatusCode}: {body}");
}
var champs = await response.Content.ReadFromJsonAsync<ChampionDto[]>();
Hvornår bruger I explicit ReadFromJsonAsync? Når I skal læse body ved fejl (ProblemDetails), logge rå JSON i DevTools, eller håndtere 204 No Content uden exception. GetFromJsonAsync er kortere til «happy path» GET — men kaster ved 4xx/5xx.
// OpgaverAPI returnerer camelCase — default JsonSerializer er case-insensitive
public record PokeDto(int Id, string Name);
// Hvis API skifter til snake_case, brug [JsonPropertyName] — ikke dynamic
[JsonPropertyName("champion_id")]
public string ChampionId { get; init; } = "";
Elev-fejl → symptom → fix: DTO har Id, JSON har id — alt er 0/null. Fix: tjek Network → Response; tilpas property-navne eller attributter; undgå at «gætte» med JsonDocument i produktion.
X-Api-Key og headers
using var request = new HttpRequestMessage(HttpMethod.Get, "/LeagueOfLegends/Champions");
request.Headers.Add("X-Api-Key", apiKey);
var response = await Http.SendAsync(request);
Bedre: encapsuler i ILolService så komponenter ikke kender header-detaljer.
Hvornår hvilken header? X-Api-Key til personlig OpgaverAPI-nøgle (LoL m.fl.). Authorization: Bearer når jeres egen backend eller Mercantec Auth JWT skal med. Pokémon-endpoints uden nøgle: ingen header — men stadig Accept: application/json via factory.
- Hent nøgle efter login på kataloget — gem i sessionStorage.
ILolServicelæser nøgle viaIApiKeyStoreog sætter header på hvert kald.- Ved 401: vis «Log ind og opret nøgle» — ikke raw exception i UI.
Typed client
builder.Services.AddHttpClient<IPokemonApi, PokemonApi>(c =>
c.BaseAddress = new Uri("https://api-opgaver.mercantec.tech"));
Hvornår typed client frem for rå HttpClient i komponent? Så snart I har mere end ét endpoint (liste + detail + søgning). Pokémon-demo med kun GET kan starte med @inject HttpClient; LoL med nøgle og flere routes bør have ILolService registreret i Program.cs.
Faldgrube: At læse JSON ind i dynamic eller JsonDocument overalt — det skalerer dårligt. Definér DTO’er; fang mismatches tidligt med tests eller tydelige fejl i UI.
Faldgrube: Glemt trailing slash i BaseAddress + absolute path — dobbelte eller manglende slashes giver 404. Følg én konvention (base med slash, relative paths uden leading slash).
Quiz 1
Hvilken HTTP-metode svarer typisk til «read/list» i REST?
Quiz 2
LoL-endpoint returnerer 401 — mest sandsynlige årsag?
ProblemDetails og statuskoder
Hvorfor det betyder noget: OpgaverAPI svarer med meningsfulde statuskoder — 401 (manglende nøgle), 404 (forkert route), 503 (nedetid). Brugeren og censor skal se en handlingsbar besked, ikke en hvid skærm.
Klik en statuskode
Se hvad I bør gøre i Blazor-UI — samme koder I møder i Network-fanen.
public async Task<(List<Mon>? Data, string? Error)> GetPokemonAsync(CancellationToken ct)
{
using var response = await _http.GetAsync("/go/pokemon?limit=20", ct);
if (response.IsSuccessStatusCode)
return (await response.Content.ReadFromJsonAsync<List<Mon>>(ct), null);
ProblemDetails? problem = null;
try { problem = await response.Content.ReadFromJsonAsync<ProblemDetails>(ct); }
catch { /* body var ikke JSON */ }
var title = problem?.Title ?? response.ReasonPhrase ?? "Ukendt fejl";
return (null, $"{(int)response.StatusCode}: {title}");
}
I UI: vis Error i rød tekst og tilbyd «Prøv igen» — særligt ved timeout på skolens WiFi.
| Status | Typisk på OpgaverAPI | Handling i UI |
|---|---|---|
| 401 | Manglende/ugyldig X-Api-Key (LoL) | Link til katalog + nøgle; ikke retry i loop |
| 404 | Forkert path eller id | «Ikke fundet» + tjek route/DTO |
| 503 | Nedetid / gateway | Kort besked + retry-knap |
| 200 + tom body | Sjældent | Tom-state, ikke crash på deserialize |
Sådan fejler det i praksis: GetFromJsonAsync kaster ved 401 — uncaught exception i Blazor Server. Eller I viser hele JSON-body i @error inkl. HTML — farligt hvis API ændres. Parse ProblemDetails, vis kort dansk tekst.
Timeout og CancellationToken
builder.Services.AddHttpClient<IPokemonService, PokemonService>(c =>
{
c.BaseAddress = new Uri(apiBase);
c.Timeout = TimeSpan.FromSeconds(15);
});
// I komponent — send token videre fra livscyklus
mons = await Api.GetPokemonAsync(cancellationToken);
Fang TaskCanceledException når brugeren navigerer væk eller netværket dør — skeln mellem timeout og bruger-cancel.
catch (TaskCanceledException ex) when (!ct.IsCancellationRequested)
{
error = "OpgaverAPI svarede ikke i tide — prøv igen.";
}
catch (OperationCanceledException)
{
return; // bruger forlod siden — vis ikke fejl
}
Hvornår sende CancellationToken? Altid videre fra OnParametersSetAsync / OnInitializedAsync til service-metoder — især ved LoL-detail når brugeren klikker hurtigt gennem champion-listen.
Mini-opgave
GET /go/pokemon?limit=5 med fejl-UI ved forkert path; vis status + title fra ProblemDetails.
Quiz 3
503 ProblemDetails — hvad vises?
Læs hos Microsoft
Prøv med API
- Katalog · Rick and Morty · LoL (API-nøgle)
- Implementér loading + fejltekst på alle API-sider — ingen «stille» fejl.
Baseret på MAGS Blazor-wiki og Microsoft Learn — Blazor.