Consuming REST APIs in .NET MAUI
Most real-world MAUI apps are thin clients over a backend: they fetch data from a REST API, render it, and push changes back. .NET MAUI reuses the same HTTP stack as ASP.NET Core, so the patterns you already know from server-side C# — HttpClient, System.Text.Json, async/await — carry over directly, with a few mobile-specific wrinkles around connectivity, certificates, and platform networking permissions.
Cricket analogy: Just as a cricket team relies on a scouting report from the analytics department before walking out to bat, a MAUI screen relies on data pulled from a REST API before it can render anything meaningful to the user.
HttpClient and IHttpClientFactory
Creating a raw new HttpClient() per request is a well-known anti-pattern because each instance holds its own connection pool, and disposing them rapidly can exhaust available sockets under load — a problem that is just as real on a phone juggling background sync as on a server. MAUI apps should instead register clients through IHttpClientFactory, wired up in MauiProgram.cs via builder.Services.AddHttpClient("Api", c => c.BaseAddress = new Uri("https://api.example.com/")), and then inject IHttpClientFactory (or a named/typed client) into view models via the DI container that MAUI configures for you.
Cricket analogy: It's like a franchise not letting every branch buy its own delivery van outright — instead a central fleet-management service issues vehicles on demand and reclaims them, which is exactly what IHttpClientFactory does with connections.
// MauiProgram.cs
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts => fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"));
builder.Services.AddHttpClient("Api", client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
client.Timeout = TimeSpan.FromSeconds(15);
});
builder.Services.AddSingleton<IProductService, ProductService>();
builder.Services.AddTransient<ProductsViewModel>();
return builder.Build();
}
}Serializing and Deserializing JSON
System.Text.Json is the default serializer in MAUI, exposed through JsonSerializer.Deserialize<T> and its async counterpart ReadFromJsonAsync<T> on HttpContent. Model classes should mirror the API's casing (JsonPropertyName attributes when the API uses snake_case), and for apps that publish with Native AOT or full trimming you should switch to a source-generated JsonSerializerContext so reflection-based serialization doesn't get trimmed away or slow the first request.
Cricket analogy: It's like translating a scorecard from one cricket board's format into your own broadcaster's on-screen graphics template before you can display it — JSON deserialization maps the API's shape onto your app's model.
public record Product(
[property: JsonPropertyName("product_id")] int Id,
[property: JsonPropertyName("product_name")] string Name,
decimal Price);
public class ProductService : IProductService
{
private readonly HttpClient _http;
public ProductService(IHttpClientFactory factory)
=> _http = factory.CreateClient("Api");
public async Task<List<Product>> GetProductsAsync(CancellationToken ct = default)
{
var products = await _http.GetFromJsonAsync<List<Product>>("api/products", ct);
return products ?? new List<Product>();
}
}Calling APIs from a ViewModel
View models typically expose an [RelayCommand] (from the CommunityToolkit.Mvvm source generator) that wraps the API call, toggles an IsBusy boolean the XAML binds an ActivityIndicator to, and catches exceptions so a failed call surfaces as a friendly message instead of crashing the app. Because MAUI runs everything on a single UI thread per platform, long-running network calls must always be awaited rather than blocked on, or the UI will freeze mid-scroll.
Cricket analogy: It's like a captain who won't walk out to review a DRS decision themselves — they delegate to the on-field umpire and just wait for the signal, exactly as the UI thread delegates the network call and awaits the result instead of blocking.
On Android, network access requires the android.permission.INTERNET permission in the platform manifest — it's included by default in new MAUI projects, but double-check Platforms/Android/AndroidManifest.xml if HTTP calls silently fail only on device.
Both Android (API 28+) and iOS block plain HTTP by default — Android via Network Security Config and iOS via App Transport Security. Calling a non-HTTPS endpoint will throw at runtime unless you explicitly whitelist the domain for development, which you should never ship to production.
- Register HTTP clients through IHttpClientFactory in MauiProgram.cs rather than instantiating HttpClient directly, to avoid socket exhaustion.
- Use named or typed clients (AddHttpClient("Api", ...)) and inject IHttpClientFactory into services via MAUI's built-in DI container.
- System.Text.Json with JsonPropertyName attributes maps API field names onto your C# model properties.
- Wrap API calls in async RelayCommands with an IsBusy flag so the UI thread never blocks and stays responsive.
- Android requires the INTERNET permission and both platforms block plain HTTP by default (ATS on iOS, Network Security Config on Android).
- Always await network calls; never use .Result or .Wait() on a Task in a MAUI UI-thread context, as it can deadlock.
- Consider Connectivity.Current.NetworkAccess to check connectivity before firing a request on mobile, where networks are far less reliable than on desktop or server.
Practice what you learned
1. Why should a MAUI app avoid instantiating `new HttpClient()` for every request?
2. Where should you register a named HttpClient for a MAUI app?
3. What happens by default if a MAUI app on iOS or Android tries to call a plain HTTP (non-HTTPS) endpoint?
4. What is the purpose of the IsBusy pattern combined with [RelayCommand] in a MAUI view model?
Was this page helpful?
You May Also Like
Dependency Injection
Understand how .NET MAUI's built-in dependency injection container wires up services, view models, and pages via MauiProgram.cs.
Local Storage with SQLite
Persist structured data on-device in a MAUI app using sqlite-net-pcl, async CRUD operations, and simple schema migrations.
MAUI Blazor Hybrid
Learn how BlazorWebView embeds Razor components inside a native MAUI app, sharing UI code between web and native targets.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics