HttpClient og Refit i .NET MAUI: Type-Safe REST API Integration (2026)

Sådan opsætter du type-safe REST API-integration i .NET MAUI med IHttpClientFactory, Refit og Polly. Fra MauiProgram til authenticated calls, med produktionsklare kodeeksempler jeg selv har shippet.

HttpClient og Refit i .NET MAUI (2026)

Opdateret: 15. juli 2026

Type-safe REST API-integration i .NET MAUI opnås bedst ved at kombinere IHttpClientFactory til livscyklus-styring, Refit til deklarative klient-interfaces og Polly til retry og circuit breaker. Kombinationen fjerner boilerplate, isolerer HTTP-detaljer bag et interface og gør din netværkskode testbar. I denne guide viser jeg dig hele opsætningen fra MauiProgram.cs til authenticated calls, med kode jeg selv har shippet i tre produktions-apps.

  • Brug IHttpClientFactory fra Microsoft.Extensions.Http, aldrig new HttpClient() direkte i view models.
  • Refit genererer type-safe klient-implementeringer fra et interface, hvilket eliminerer manuel JSON- og URL-håndtering.
  • Polly tilføjer retry, timeout og circuit breaker via Microsoft.Extensions.Http.Resilience uden at ændre din API-kode.
  • DelegatingHandlers er det rigtige sted at tilføje Bearer-tokens, logging og korrelations-ID'er (ikke i hver enkelt kald).
  • iOS kræver eksplicit NSAppTransportSecurity-konfiguration for lokale HTTP-endpoints under udvikling.
  • Kombinér IConnectivity med en outbox-tabel i SQLite for at understøtte kortvarige netværksafbrydelser.

Hvorfor du aldrig bør bruge new HttpClient() i MAUI

Det klassiske fejltrin i mobile .NET-apps er at instantiere new HttpClient() pr. kald. Klassen implementerer IDisposable, så udviklere pakker det pænt i using, og udmatter derefter socket-poolen efter få tusinde forespørgsler. På Android manifesterer det sig som SocketException under load. På iOS ses det som mystiske timeouts. Jeg fangede selv den her bug i en produktions-app, hvor QA aldrig så den, fordi den kun optrådte efter 30+ minutters brug. Ikke sjovt at debugge fra release notes.

IHttpClientFactory løser tre problemer på én gang: den genbruger HttpMessageHandler-instanser (som er de dyre objekter), den roterer handlers hvert andet minut for at respektere DNS-ændringer, og den lader dig konfigurere navngivne eller typede klienter centralt. I .NET MAUI er factory tilgængelig via Microsoft.Extensions.Http-pakken og integrerer direkte med den indbyggede DI-container. Se Microsofts officielle vejledning om IHttpClientFactory for baggrund.

En anden fordel: factory'en samarbejder med Polly, så retry- og circuit-breaker-politikker anvendes automatisk på hver forespørgsel, uden at forurene din forretningslogik. Vi kobler det sammen i næste sektion.

Opsætning af IHttpClientFactory i MauiProgram

Så, lad os starte med at tilføje pakkerne. I .NET 9 og nyere er Microsoft.Extensions.Http allerede med som transitiv afhængighed, men du skal eksplicit tilføje resilience- og Refit-pakkerne:

<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="8.0.0" />

Konfigurér derefter en navngiven klient i MauiProgram.cs. Personligt foretrækker jeg navngivne klienter over typede, fordi de er nemmere at ombryde med Refit senere:

public static MauiApp CreateMauiApp()
{
    var builder = MauiApp.CreateBuilder();
    builder
        .UseMauiApp<App>()
        .ConfigureFonts(fonts =>
        {
            fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
        });

    // Base HttpClient konfiguration
    builder.Services.AddHttpClient("MobileApi", client =>
    {
        client.BaseAddress = new Uri("https://api.example.com/v1/");
        client.Timeout = TimeSpan.FromSeconds(30);
        client.DefaultRequestHeaders.Add("User-Agent", "MobileTechLead/1.0");
    });

    return builder.Build();
}

Bemærk at BaseAddress ender med en skråstreg. Uden den vil relative URI'er i Refit blive tolket forkert (og du får en time i debuggeren gratis). Timeout på 30 sekunder er min standard for mobile netværk; hvis dit API kører 3G-scenarier, kan du overveje 60 sekunder, men vær opmærksom på, at brugere opgiver længe før det.

Refit: type-safe klienter uden boilerplate

Refit lader dig deklarere API-endpoints som et interface med attributter, og genererer implementeringen ved kompilering. Det fjerner manuel URL-formatering, JSON-serialisering og fejlkontrol fra din view model-kode. Definér først din model og dit interface:

public record Product(int Id, string Name, decimal Price, string ImageUrl);

public record ProductPage(IReadOnlyList<Product> Items, int Total, int Page);

public interface IProductApi
{
    [Get("/products")]
    Task<ProductPage> GetProductsAsync(
        [Query] int page = 1,
        [Query] int pageSize = 20,
        CancellationToken ct = default);

    [Get("/products/{id}")]
    Task<Product> GetProductAsync(int id, CancellationToken ct = default);

    [Post("/products")]
    Task<Product> CreateProductAsync(
        [Body] Product product,
        CancellationToken ct = default);

    [Multipart]
    [Post("/products/{id}/image")]
    Task UploadImageAsync(
        int id,
        [AliasAs("file")] StreamPart image,
        CancellationToken ct = default);
}

Registrér Refit-klienten sammen med din navngivne HttpClient:

builder.Services
    .AddRefitClient<IProductApi>()
    .ConfigureHttpClient(c =>
    {
        c.BaseAddress = new Uri("https://api.example.com/v1/");
        c.Timeout = TimeSpan.FromSeconds(30);
    });

Din view model modtager nu IProductApi via constructor injection, helt fri for HTTP-detaljer, hvilket gør den trivielt testbar. Se Refit-projektets officielle dokumentation for fulde attribut-oversigter, herunder [Header], [QueryUriFormat] og typede fejl-modeller via ApiException. Hvis du har brug for at genopfriske dependency injection-mønstre først, dækker vores guide til dependency injection i .NET MAUI alle service-livscyklusser i detaljer.

Resilience med Polly og retry-strategier

Mobile netværk fejler konstant: en tunnel, en overfyldt celletårn, en Wi-Fi-overgang. En stabil API-klient antager fejl som normen og reagerer med eksponentiel back-off. Microsoft.Extensions.Http.Resilience pakker Polly v8 med sensible standarder, som du kan tilføje til enhver HttpClient med én linje:

using Microsoft.Extensions.Http.Resilience;

builder.Services
    .AddRefitClient<IProductApi>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com/v1/"))
    .AddStandardResilienceHandler(options =>
    {
        options.Retry.MaxRetryAttempts = 3;
        options.Retry.BackoffType = DelayBackoffType.Exponential;
        options.Retry.UseJitter = true;
        options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(10);
        options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(45);
        options.CircuitBreaker.FailureRatio = 0.5;
    });

Standardhandler'en kombinerer fem strategier: timeout pr. forsøg, samlet timeout, retry med jitter, circuit breaker og rate limiter. Jitter er kritisk i mobile scenarier. Uden den vil hundredvis af klienter forsøge igen samtidig efter en netværksafbrydelse og forværre problemet.

Til produktion anbefaler jeg at logge circuit-breaker-transitioner via ILogger. Det er dit tidligste signal om et nedstrøms API-outage. Polly's telemetri integrerer med Microsoft.Extensions.Diagnostics, så du får dette gratis, hvis du allerede har opsat structured logging.

DelegatingHandler til authentication og logging

Bearer-tokens hører ikke hjemme i hvert enkelt Refit-kald. Brug en DelegatingHandler, som opsnapper alle forespørgsler for din klient og tilføjer headers centralt. Dette er også det rette sted til at håndtere token-fornyelse på 401-responses:

public sealed class AuthHeaderHandler : DelegatingHandler
{
    private readonly ITokenProvider _tokens;

    public AuthHeaderHandler(ITokenProvider tokens)
    {
        _tokens = tokens;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken ct)
    {
        var token = await _tokens.GetAccessTokenAsync(ct);
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);

        var response = await base.SendAsync(request, ct);

        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            token = await _tokens.RefreshAsync(ct);
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
            response.Dispose();
            response = await base.SendAsync(request, ct);
        }

        return response;
    }
}

Registrér handler'en og kæd den til din klient:

builder.Services.AddSingleton<ITokenProvider, SecureTokenProvider>();
builder.Services.AddTransient<AuthHeaderHandler>();

builder.Services
    .AddRefitClient<IProductApi>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.example.com/v1/"))
    .AddHttpMessageHandler<AuthHeaderHandler>()
    .AddStandardResilienceHandler();

Tokens bør gemmes i SecureStorage, aldrig i Preferences eller ren tekst. Vores guide til sikkerhed i .NET MAUI dækker dette i detaljer, inklusive biometrisk beskyttelse af refresh tokens.

iOS- og Android-platformkonfiguration

Både iOS og Android har platform-specifikke krav til HTTP-trafik i 2026. På iOS blokerer App Transport Security (ATS) al HTTP-trafik uden TLS 1.2+. Til produktion er det præcis hvad du vil have, men til lokal udvikling mod http://localhost:5000 skal du tilføje en undtagelse i Platforms/iOS/Info.plist:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

På Android skal du eksplicit tillade cleartext-trafik pr. domæne via en network security config. Opret 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 includeSubdomains="true">localhost</domain>
    </domain-config>
</network-security-config>

Reference filen fra AndroidManifest.xml via <application android:networkSecurityConfig="@xml/network_security_config">. Android-emulatoren bruger 10.0.2.2 som alias for host-maskinens localhost, en detalje der har kostet mig flere timer over årene. Husk det, seriøst.

På Android bør du også opgradere til SocketsHttpHandler (default fra .NET 8) i stedet for den ældre Xamarin-handler. Det giver HTTP/2, connection pooling og bedre timeout-håndtering. .NET MAUI bruger den korrekte handler by default siden 8.0, men hvis du migrerer fra Xamarin.Forms, bør du eksplicit sætte <UseNativeHttpHandler>false</UseNativeHttpHandler> i din .csproj.

Håndtering af offline-tilstand og connectivity

En mobile-first API-klient skal reagere på ændringer i netværkstilstand. IConnectivity fra Microsoft.Maui.Essentials giver både en synkron tilstand og en event:

public sealed class ProductService
{
    private readonly IProductApi _api;
    private readonly IConnectivity _connectivity;
    private readonly ILocalProductStore _store;

    public ProductService(
        IProductApi api,
        IConnectivity connectivity,
        ILocalProductStore store)
    {
        _api = api;
        _connectivity = connectivity;
        _store = store;
    }

    public async Task<IReadOnlyList<Product>> GetProductsAsync(CancellationToken ct)
    {
        if (_connectivity.NetworkAccess != NetworkAccess.Internet)
        {
            return await _store.GetAllAsync(ct);
        }

        try
        {
            var page = await _api.GetProductsAsync(ct: ct);
            await _store.UpsertRangeAsync(page.Items, ct);
            return page.Items;
        }
        catch (ApiException) when (await _store.HasCacheAsync(ct))
        {
            return await _store.GetAllAsync(ct);
        }
    }
}

Dette mønster (stale-while-revalidate) er min standard. Vis cached data hurtigt, opdatér i baggrunden, og fald tilbage til cache ved API-fejl. Til skrive-operationer bruger jeg en outbox-tabel i SQLite, hvor pending mutations opbevares indtil netværket vender tilbage. Vores guide til SQLite og EF Core i .NET MAUI viser hvordan du opsætter den lokale store.

Test af dine API-klienter

Fordi din view model afhænger af IProductApi (interface) og ikke HttpClient (concrete), er unit tests trivielle:

[Fact]
public async Task LoadProducts_populates_collection()
{
    var api = Substitute.For<IProductApi>();
    api.GetProductsAsync(Arg.Any<int>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
       .Returns(new ProductPage(new[] { new Product(1, "Test", 99m, "img.jpg") }, 1, 1));

    var vm = new ProductListViewModel(api);
    await vm.LoadCommand.ExecuteAsync(null);

    Assert.Single(vm.Products);
}

Til integration tests mod en fake HTTP-server bruger jeg WireMock.Net, som lader dig scripte responses inklusive fejl-scenarier. Det er langt mere realistisk end at mocke Refit-interfacet, fordi du også får dine DelegatingHandlers og Polly-strategier med i testen. Vores komplette teststrategi for .NET MAUI dækker både unit-, integration- og UI-tests. Se også Microsofts vejledning om test af IHttpClientFactory.

Ofte stillede spørgsmål

Hvad er forskellen mellem HttpClient og Refit i .NET MAUI?

HttpClient er den lav-niveau .NET-klasse til at sende HTTP-forespørgsler manuelt. Refit er et bibliotek der genererer en type-safe klient-implementering fra et interface, så du deklarerer endpoints med attributter i stedet for at bygge HttpRequestMessage-objekter selv. Refit bruger stadig HttpClient internt, så det er egentlig et abstraktionslag ovenpå.

Kan man bruge IHttpClientFactory i .NET MAUI?

Ja, IHttpClientFactory er fuldt understøttet i .NET MAUI via Microsoft.Extensions.Http-pakken. Registrér dine klienter i MauiProgram.cs med builder.Services.AddHttpClient(), og inject enten IHttpClientFactory eller en typed client i dine services.

Hvordan tilføjer man authentication headers til alle API-kald?

Opret en klasse der arver fra DelegatingHandler, overskriv SendAsync og tilføj request.Headers.Authorization. Registrér den derefter på din HttpClient med .AddHttpMessageHandler<YourHandler>(). Dette centraliserer auth-logik og lader dig håndtere 401-responses med token-fornyelse ét sted.

Hvordan implementerer man retry-logik med Polly i .NET MAUI?

Installér Microsoft.Extensions.Http.Resilience, og tilføj .AddStandardResilienceHandler() til din klient-registrering. Standardhandler'en giver retry med eksponentiel back-off og jitter, timeouts og circuit breaker, og alt kan tilpasses via en options-callback uden at ændre din API-kode.

Skal man bruge System.Text.Json eller Newtonsoft.Json med Refit?

Brug System.Text.Json. Det er default i Refit 8, betydeligt hurtigere, og fylder mindre i din app. Newtonsoft.Json er kun nødvendigt hvis du bruger avancerede features som custom JsonConverter-hierarkier eller [JsonExtensionData], som System.Text.Json først for nyligt har fået fuld paritet på.

Marcus Chen
Om Forfatteren Marcus Chen

Senior mobile architect with a decade of cross-platform experience. Spent the last five years going deep on .NET MAUI in production.