Hvorfor REST API-integration er afgørende for din mobilapp
Lad os starte med det åbenlyse: næsten enhver moderne mobilapp kommunikerer med en backend. Uanset om du henter brugerdata, synkroniserer indhold eller sender ordrer til en server — REST API-integration er fundamentet for din apps netværkskommunikation. Og i .NET MAUI har du faktisk alle værktøjerne til at gøre det rigtigt.
Men det kræver, at du forstår de tilgængelige muligheder og deres afvejninger.
Denne guide tager dig fra de grundlæggende HttpClient-kald hele vejen til en produktionsklar arkitektur med IHttpClientFactory, Refit-typede klienter, resilience-strategier med Polly og connectivity-tjek. Alt sammen med praktiske kodeeksempler, der fungerer med .NET MAUI i .NET 10.
Har du allerede læst vores guide om lokal SQLite-database i .NET MAUI? Denne artikel er det naturlige næste skridt — hvor SQLite håndterer lokal data, dækker vi her den fjerne datakommunikation.
HttpClient: Det grundlæggende fundament
Din første GET-forespørgsel
Al HTTP-kommunikation i .NET MAUI starter med HttpClient. Klassen giver dig asynkrone metoder til at sende forespørgsler og modtage svar fra enhver URI. Lad os starte simpelt — vi henter en liste af produkter fra et REST API:
using System.Net.Http.Json;
public class ProduktService
{
private readonly HttpClient _httpClient;
public ProduktService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<List<Produkt>?> HentProdukterAsync()
{
return await _httpClient.GetFromJsonAsync<List<Produkt>>("api/produkter");
}
}
Læg mærke til GetFromJsonAsync<T> fra System.Net.Http.Json-pakken. Denne extension-metode kombinerer HTTP-kaldet og JSON-deserialiseringen i ét kald og bruger automatisk System.Text.Json med webstandard-indstillinger. Det er klart at foretrække frem for manuelt at læse response-body'en og kalde JsonSerializer.Deserialize.
POST, PUT og DELETE
De øvrige CRUD-operationer følger samme mønster. Ingen overraskelser her:
public class ProduktService
{
private readonly HttpClient _httpClient;
public ProduktService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Produkt?> OpretProduktAsync(Produkt produkt)
{
var response = await _httpClient.PostAsJsonAsync("api/produkter", produkt);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<Produkt>();
}
public async Task OpdaterProduktAsync(int id, Produkt produkt)
{
var response = await _httpClient.PutAsJsonAsync($"api/produkter/{id}", produkt);
response.EnsureSuccessStatusCode();
}
public async Task SletProduktAsync(int id)
{
var response = await _httpClient.DeleteAsync($"api/produkter/{id}");
response.EnsureSuccessStatusCode();
}
}
Vigtigt: Brug altid PostAsJsonAsync og PutAsJsonAsync i stedet for manuelt at oprette StringContent med JSON. Det sparer dig for en del boilerplate og sætter automatisk den korrekte Content-Type-header.
Undgå socket exhaustion: IHttpClientFactory
Problemet med new HttpClient()
Helt ærligt — det her er en af de mest almindelige fejl i .NET-mobilapps. Mange udviklere opretter en ny HttpClient-instans for hvert API-kald, og det lyder jo uskyldigt nok. Problemet er bare, at når en HttpClient bortskaffes, frigives den underliggende TCP-socket ikke øjeblikkeligt.
Ved hyppige kald løber din app hurtigt tør for tilgængelige sockets. Det kaldes socket exhaustion, og det er ikke sjovt at debugge i produktion.
Du kunne bruge en singleton HttpClient, men så risikerer du at misse DNS-ændringer, hvis din app kører i lang tid. Løsningen? IHttpClientFactory.
Opsætning i MauiProgram.cs
Registrér din HttpClient via IHttpClientFactory i dependency injection-containeren:
using Microsoft.Extensions.Http;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Registrér HttpClient med named client
builder.Services.AddHttpClient("MinApi", client =>
{
client.BaseAddress = new Uri("https://api.minapp.dk/");
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.Timeout = TimeSpan.FromSeconds(30);
});
// Registrér typed client for ProduktService
builder.Services.AddHttpClient<ProduktService>(client =>
{
client.BaseAddress = new Uri("https://api.minapp.dk/");
});
return builder.Build();
}
}
IHttpClientFactory styrer automatisk livscyklussen af de underliggende HttpMessageHandler-instanser. Det pooler handlers, roterer dem efter en konfigurerbar levetid (standard er 2 minutter) og sikrer, at DNS-ændringer respekteres — alt sammen uden at du skal tænke over det. Ret elegant, faktisk.
Named vs. typed clients
Du har to tilgange til at registrere klienter:
- Named clients (
AddHttpClient("MinApi", ...)) — gode når flere services deler samme konfiguration. Du henter dem viaIHttpClientFactory.CreateClient("MinApi"). - Typed clients (
AddHttpClient<ProduktService>(...)) — den anbefalede tilgang for de fleste scenarier. DI-containeren injicerer automatisk en konfigureretHttpClienti din service-klasse. Renere, mere typesikkert og nemmere at teste.
I de fleste tilfælde vil du foretrække typed clients. De giver dig bedre compile-time sikkerhed og en mere naturlig API.
System.Text.Json: Optimér din serialisering
Genbrug JsonSerializerOptions
Hvis du har brug for brugerdefinerede serialiseringsindstillinger, er der én ting du absolut skal vide: genbrug den samme JsonSerializerOptions-instans. Hver instans cacher metadata om dine typer ved første brug, og det er trådsikkert — så opret den én gang og brug den overalt:
public static class JsonDefaults
{
public static readonly JsonSerializerOptions ApiOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() }
};
}
Brug dem derefter i dine kald:
var produkter = await _httpClient.GetFromJsonAsync<List<Produkt>>(
"api/produkter",
JsonDefaults.ApiOptions);
Source generators for maksimal ydelse
Til produktionsapps — og det gælder især dem der bruger NativeAOT eller fuld trimming i .NET 10 — bør du bruge System.Text.Json source generators. De genererer optimeret serialiseringskode ved compile-time og eliminerer reflection helt:
[JsonSerializable(typeof(List<Produkt>))]
[JsonSerializable(typeof(Produkt))]
[JsonSerializable(typeof(OrdreRequest))]
public partial class AppJsonContext : JsonSerializerContext
{
}
// Brug i dine kald
var produkter = await _httpClient.GetFromJsonAsync(
"api/produkter",
AppJsonContext.Default.ListProdukt);
Denne tilgang er særligt vigtig i .NET MAUI med .NET 10, hvor NativeAOT-understøttelse er modnet betydeligt. Source generators sikrer, at din serialisering fungerer korrekt med trimming aktiveret — og du får en mærkbar ydelsesforbedring oveni.
Refit: Typesikre REST-klienter uden boilerplate
Hvad er Refit?
Okay, nu kommer vi til min personlige favorit. Refit er et bibliotek, der konverterer dine REST API-endpoints til typede C#-interfaces. I stedet for manuelt at skrive HttpClient-kald definerer du et interface, og Refit genererer implementeringen automatisk ved compile-time via Roslyn source generators.
Resultatet? Mindre kode, færre fejl og en API-klient der er nærmest triviel at vedligeholde.
Installation og opsætning
Installér de nødvendige NuGet-pakker:
dotnet add package Refit
dotnet add package Refit.HttpClientFactory
Definér dit API-interface
Opret et interface, der afspejler dit REST API's endpoints. Det er her, magien sker:
using Refit;
public interface IProduktApi
{
[Get("/api/produkter")]
Task<List<Produkt>> HentAlleAsync();
[Get("/api/produkter/{id}")]
Task<Produkt> HentEfterIdAsync(int id);
[Post("/api/produkter")]
Task<Produkt> OpretAsync([Body] Produkt produkt);
[Put("/api/produkter/{id}")]
Task OpdaterAsync(int id, [Body] Produkt produkt);
[Delete("/api/produkter/{id}")]
Task SletAsync(int id);
[Get("/api/produkter/soeg")]
Task<List<Produkt>> SoegAsync([Query] string term, [Query] int side = 1);
}
Registrér med IHttpClientFactory
I MauiProgram.cs registrerer du Refit-klienten, så den automatisk bruger IHttpClientFactory under motorhjelmen:
using Refit;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
// Registrér Refit-klient med IHttpClientFactory
builder.Services
.AddRefitClient<IProduktApi>()
.ConfigureHttpClient(c =>
{
c.BaseAddress = new Uri("https://api.minapp.dk");
});
// Registrér ViewModel
builder.Services.AddTransient<ProdukterViewModel>();
return builder.Build();
}
}
Brug i din ViewModel
Nu kan du injicere API-interfacet direkte i din ViewModel — og koden bliver virkelig ren:
public partial class ProdukterViewModel : ObservableObject
{
private readonly IProduktApi _produktApi;
public ProdukterViewModel(IProduktApi produktApi)
{
_produktApi = produktApi;
}
[ObservableProperty]
private ObservableCollection<Produkt> _produkter = new();
[RelayCommand]
private async Task IndlaesProdukterAsync()
{
try
{
var resultat = await _produktApi.HentAlleAsync();
Produkter = new ObservableCollection<Produkt>(resultat);
}
catch (ApiException ex)
{
// Refit kaster ApiException med detaljer om HTTP-fejlen
await Shell.Current.DisplayAlert(
"Fejl",
$"Kunne ikke hente produkter: {ex.StatusCode}",
"OK");
}
}
}
Bemærk, at Refit kaster en ApiException (som arver fra HttpRequestException) med detaljerede oplysninger om HTTP-statuskoden og response-body'en. Det gør fejlhåndtering langt mere præcis end med rå HttpClient-kald.
Connectivity: Tjek netværksforbindelsen før API-kald
IConnectivity-interfacet
I en mobilapp kan brugeren til enhver tid miste netværksforbindelsen. I en tunnel, i et fly, eller bare i et område med elendig dækning. .NET MAUI tilbyder IConnectivity-interfacet til at tjekke netværksstatus, før du laver et API-kald:
// Registrér i MauiProgram.cs
builder.Services.AddSingleton<IConnectivity>(Connectivity.Current);
Brug det i din service eller ViewModel:
public class ProduktService
{
private readonly HttpClient _httpClient;
private readonly IConnectivity _connectivity;
public ProduktService(HttpClient httpClient, IConnectivity connectivity)
{
_httpClient = httpClient;
_connectivity = connectivity;
}
public async Task<List<Produkt>?> HentProdukterAsync()
{
if (_connectivity.NetworkAccess != NetworkAccess.Internet)
{
// Ingen internetforbindelse — returnér cachet data eller vis besked
return null;
}
return await _httpClient.GetFromJsonAsync<List<Produkt>>("api/produkter");
}
}
Reager på forbindelsesændringer
Du kan også lytte efter ændringer i netværksstatus i realtid, hvilket er virkelig nyttigt for at give brugeren visuel feedback:
public partial class AppViewModel : ObservableObject
{
private readonly IConnectivity _connectivity;
[ObservableProperty]
private bool _erOnline;
public AppViewModel(IConnectivity connectivity)
{
_connectivity = connectivity;
ErOnline = _connectivity.NetworkAccess == NetworkAccess.Internet;
_connectivity.ConnectivityChanged += OnConnectivityChanged;
}
private void OnConnectivityChanged(object? sender, ConnectivityChangedEventArgs e)
{
ErOnline = e.NetworkAccess == NetworkAccess.Internet;
}
}
Bemærk: På Windows kan NetworkAccess i sjældne tilfælde returnere Unknown i stedet for den faktiske status. Håndtér dette i din app ved at behandle Unknown som "muligvis online" og forsøge kaldet alligevel. Det er lidt frustrerende, men det er en kendt begrænsning.
Resilience: Robuste API-kald med Polly
Hvorfor du har brug for resilience-strategier
Netværkskald fejler. Det er ikke et spørgsmål om om, men hvornår.
Midlertidige fejl — en overbelastet server, et tabt pakke, en DNS-opslag der timeouder — sker konstant i den virkelige verden. Uden resilience-strategier crasher din app eller viser en fejlbesked for noget, der kunne have været løst med et simpelt genforsøg. Og det er en dårlig brugeroplevelse.
Microsoft.Extensions.Http.Resilience
Den moderne tilgang i .NET 10 er pakken Microsoft.Extensions.Http.Resilience, som bygger på Polly v8 og integrerer direkte med IHttpClientFactory:
dotnet add package Microsoft.Extensions.Http.Resilience
Standard resilience pipeline
Den nemmeste måde at tilføje resilience på er med AddStandardResilienceHandler. Én linje kode giver dig en komplet pipeline:
builder.Services
.AddHttpClient<ProduktService>(client =>
{
client.BaseAddress = new Uri("https://api.minapp.dk/");
})
.AddStandardResilienceHandler();
Denne ene linje tilføjer:
- Retry med eksponentiel backoff og jitter (undgår det såkaldte "thundering herd"-problem)
- Circuit breaker der midlertidigt blokerer kald, hvis serveren gentagne gange fejler
- Timeout per forsøg og samlet timeout
- Rate limiter for at beskytte mod overbelastning
Ret imponerende for én linje kode, ikke?
Brugerdefineret resilience-konfiguration
Hvis du har brug for mere kontrol (og det har man tit i produktionsapps), kan du konfigurere pipelinen manuelt:
builder.Services
.AddHttpClient<ProduktService>(client =>
{
client.BaseAddress = new Uri("https://api.minapp.dk/");
})
.AddResilienceHandler("produkt-pipeline", builder =>
{
// Retry: 3 forsøg med eksponentiel backoff
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
Delay = TimeSpan.FromMilliseconds(500)
});
// Circuit breaker: Åbn kredsløbet efter 5 fejl
builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
SamplingDuration = TimeSpan.FromSeconds(30),
FailureRatio = 0.5,
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(15)
});
// Timeout per forsøg
builder.AddTimeout(TimeSpan.FromSeconds(10));
});
Refit med resilience
Og her bliver det virkelig smart — du kan kombinere Refit og resilience ved bare at kæde metoderne:
builder.Services
.AddRefitClient<IProduktApi>()
.ConfigureHttpClient(c =>
{
c.BaseAddress = new Uri("https://api.minapp.dk");
})
.AddStandardResilienceHandler();
Med denne opsætning får du typesikre API-kald OG automatisk retry, circuit breaker og timeout — uden en eneste ekstra linje kode i din forretningslogik. Det er denne kombination, der gør .NET MAUI's HTTP-stack så stærk.
Autentificering: Bearer tokens med HttpClient
Tilføj authorization-header
De fleste REST API'er kræver autentificering. Den mest udbredte tilgang er at sende et Bearer-token i Authorization-headeren. I stedet for at tilføje det manuelt til hvert kald (hvilket hurtigt bliver rodet), kan du bruge en DelegatingHandler:
public class AuthHandler : DelegatingHandler
{
private readonly ISecureStorage _secureStorage;
public AuthHandler(ISecureStorage secureStorage)
{
_secureStorage = secureStorage;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var token = await _secureStorage.GetAsync("access_token");
if (!string.IsNullOrEmpty(token))
{
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
}
Registrér handler i DI-kæden
// Registrér AuthHandler som transient
builder.Services.AddTransient<AuthHandler>();
builder.Services.AddSingleton<ISecureStorage>(SecureStorage.Default);
// Tilføj handler til HttpClient-pipelinen
builder.Services
.AddHttpClient<ProduktService>(client =>
{
client.BaseAddress = new Uri("https://api.minapp.dk/");
})
.AddHttpMessageHandler<AuthHandler>()
.AddStandardResilienceHandler();
Bemærk rækkefølgen her: AuthHandler tilføjes før resilience-handleren, så tokenet inkluderes i hvert retry-forsøg. DelegatingHandler-mønsteret holder din service-kode ren — den behøver slet ikke at vide noget om autentificering.
Sikkerhedstip: Gem altid tokens i SecureStorage — aldrig i Preferences. SecureStorage bruger platformsspecifik kryptering (Android KeyStore, iOS Keychain, Windows DataProtectionProvider), mens Preferences lagrer data i klartekst. Det er en fejl, der kan koste dyrt.
Komplet arkitektur: Sæt det hele sammen
Så, lad os samle alle brikkerne. Her er den komplette MauiProgram.cs-opsætning, der kombinerer alle de teknikker, vi har gennemgået:
using Microsoft.Extensions.Http.Resilience;
using Refit;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Platform-services
builder.Services.AddSingleton<IConnectivity>(Connectivity.Current);
builder.Services.AddSingleton<ISecureStorage>(SecureStorage.Default);
// Auth handler
builder.Services.AddTransient<AuthHandler>();
// Refit API-klient med auth og resilience
builder.Services
.AddRefitClient<IProduktApi>()
.ConfigureHttpClient(c =>
{
c.BaseAddress = new Uri("https://api.minapp.dk");
c.Timeout = TimeSpan.FromSeconds(30);
})
.AddHttpMessageHandler<AuthHandler>()
.AddStandardResilienceHandler();
// Services og ViewModels
builder.Services.AddTransient<ProdukterViewModel>();
return builder.Build();
}
}
Denne arkitektur giver dig:
- Typesikre API-kald via Refit-interfaces
- Automatisk handler-lifecycle via IHttpClientFactory
- Gennemsigtig autentificering via DelegatingHandler
- Automatisk retry og circuit breaker via resilience-pipelinen
- Connectivity-tjek via IConnectivity i dine ViewModels
Det lyder måske som meget, men når det først er sat op, er det bemærkelsesværdigt let at vedligeholde.
Fejlhåndtering i praksis
En robust netværksarkitektur kræver god fejlhåndtering. Her er et mønster, der dækker de mest almindelige scenarier i en .NET MAUI-app:
public partial class ProdukterViewModel : ObservableObject
{
private readonly IProduktApi _produktApi;
private readonly IConnectivity _connectivity;
[ObservableProperty]
private bool _indlaeser;
[ObservableProperty]
private string? _fejlBesked;
public ProdukterViewModel(IProduktApi produktApi, IConnectivity connectivity)
{
_produktApi = produktApi;
_connectivity = connectivity;
}
[RelayCommand]
private async Task IndlaesProdukterAsync()
{
FejlBesked = null;
if (_connectivity.NetworkAccess != NetworkAccess.Internet)
{
FejlBesked = "Ingen internetforbindelse. Tjek dine netværksindstillinger.";
return;
}
try
{
Indlaeser = true;
var produkter = await _produktApi.HentAlleAsync();
// Opdatér UI med produkter...
}
catch (ApiException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
FejlBesked = "Din session er udløbet. Log venligst ind igen.";
// Navigér til login...
}
catch (ApiException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
FejlBesked = "Ressourcen blev ikke fundet.";
}
catch (HttpRequestException)
{
FejlBesked = "Kunne ikke kontakte serveren. Prøv igen senere.";
}
catch (TaskCanceledException)
{
FejlBesked = "Forespørgslen tog for lang tid. Tjek din forbindelse.";
}
finally
{
Indlaeser = false;
}
}
}
Nøglepointen: Fang specifikke undtagelser og giv brugeren handlingsrettede fejlbeskeder. "Noget gik galt" er aldrig en god brugeroplevelse — fortæl brugeren hvad de kan gøre ved problemet.
Lokal udvikling: Tip til Android og iOS
Når du udvikler mod et lokalt ASP.NET Core API, er der et par platformsspecifikke udfordringer, du skal kende til. De har bidt mange udviklere (inklusiv undertegnede) mere end én gang.
Android: Cleartext HTTP-trafik
Android blokerer som standard HTTP-trafik — kun HTTPS er tilladt. Til lokal udvikling skal du tillade det eksplicit i Platforms/Android/Resources/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>
Husk: 10.0.2.2 er Android-emulatorens adresse til din host-maskine. Brug aldrig localhost fra emulatoren — det peger på emulatoren selv, og du vil sidde og undre dig over, hvorfor intet virker.
iOS: NSAppTransportSecurity
iOS har tilsvarende restriktioner. Tilføj følgende i Platforms/iOS/Info.plist kun til udvikling:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
Advarsel: Fjern altid disse indstillinger fra din Release-konfiguration, før du publicerer til App Store eller Google Play. Brug #if DEBUG-direktiver til at konditionelt anvende dem. Det er en af de ting, der er nemme at glemme, men kan give problemer ved app review.
Ofte stillede spørgsmål
Skal jeg bruge HttpClient eller Refit i min .NET MAUI-app?
Til simple apps med få endpoints er HttpClient med IHttpClientFactory helt fint. Men så snart du har mere end 3-4 endpoints, giver Refit markant renere kode. Refit bruger HttpClient under motorhjelmen, så du mister ingen funktionalitet — du fjerner blot boilerplate. For de fleste produktionsapps vil jeg anbefale Refit kombineret med IHttpClientFactory.
Kan jeg bruge IHttpClientFactory i .NET MAUI, eller er det kun til ASP.NET Core?
IHttpClientFactory fungerer fuldt ud i .NET MAUI. Det er en del af Microsoft.Extensions.Http-pakken, som ikke er bundet til ASP.NET Core. Registrér det i MauiProgram.cs præcis som vist i denne guide. Der er en igangværende debat i fællesskabet om, hvorvidt det er strengt nødvendigt på mobil (da OS'et styrer sockets anderledes), men erfaringerne fra større projekter viser, at det forebygger problemer med hukommelse og CPU.
Hvordan håndterer jeg token-fornyelse (refresh tokens) i .NET MAUI?
Implementér logikken i din DelegatingHandler: fang 401-svar, brug dit refresh-token til at hente et nyt access-token, gem det i SecureStorage, og genforsøg det oprindelige kald automatisk. Brugeren skal aldrig selv opleve, at tokenet udløber — det hele skal ske bag kulisserne.
Hvad er forskellen på Microsoft.Extensions.Http.Polly og Microsoft.Extensions.Http.Resilience?
Microsoft.Extensions.Http.Polly er forældet (deprecated). Den nyere Microsoft.Extensions.Http.Resilience-pakke bygger på Polly v8 med nul-allokerings-design og bedre ydeevne. Brug altid Microsoft.Extensions.Http.Resilience til nye projekter — der er ingen grund til at starte med den gamle pakke.
Hvordan tester jeg mine REST API-kald i .NET MAUI?
Fordi du bruger interfaces (Refit) og dependency injection, kan du nemt mocke dine API-kald i unit tests. Brug et mocking-framework som NSubstitute eller Moq til at oprette en falsk IProduktApi-implementering, der returnerer foruddefinerede data. For integrationstest kan du bruge MockHttpMessageHandler fra RichardSzalay.MockHttp-pakken — den er rigtig god til at simulere specifikke HTTP-svar.