| name | maui-rest-api |
| description | Guidance for consuming REST APIs in .NET MAUI apps. Covers HttpClient setup with System.Text.Json, DI registration, service interface/implementation pattern, full CRUD operations (GET, POST, PUT, DELETE), error handling, platform-specific clear-text traffic configuration, and async/await best practices. USE FOR: "REST API", "HttpClient", "call API", "GET request", "POST request", "API service", "JSON deserialization", "CRUD operations", "clear-text traffic", "consume API MAUI". DO NOT USE FOR: Aspire service discovery (use maui-aspire), authentication token handling (use maui-authentication), or local database storage (use maui-sqlite-database).
|
REST API Consumption — Gotchas & Best Practices
Common Mistakes
1. Creating HttpClient per request
public async Task<List<Item>> GetItemsAsync()
{
using var client = new HttpClient();
var response = await client.GetAsync("https://api.example.com/items");
}
builder.Services.AddSingleton(sp => new HttpClient
{
BaseAddress = new Uri("https://api.example.com")
});
2. Blocking with .Result or .Wait()
var items = _apiService.GetItemsAsync().Result;
var items = await _apiService.GetItemsAsync();
3. Deserializing before checking status
var content = await response.Content.ReadAsStringAsync();
var items = JsonSerializer.Deserialize<List<Item>>(content, _jsonOptions);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var items = JsonSerializer.Deserialize<List<Item>>(content, _jsonOptions) ?? [];
4. Hardcoding BaseAddress in service methods
await _httpClient.GetAsync("https://api.example.com/api/items");
await _httpClient.GetAsync("api/items");
5. Missing error handling for network failures
var items = await _apiService.GetItemsAsync();
try
{
var items = await _apiService.GetItemsAsync();
}
catch (HttpRequestException ex) { }
catch (JsonException ex) { }
Platform Pitfalls
⚠️ Clear-text HTTP blocked on emulators/simulators
Local dev servers on http:// are blocked by default. Configure exceptions:
- Android: needs
network_security_config.xml with cleartextTrafficPermitted="true" for 10.0.2.2
- iOS/Mac Catalyst: needs
NSAllowsLocalNetworking in Info.plist
⚠️ Android emulator uses 10.0.2.2 for localhost
The Android emulator maps 10.0.2.2 to the host machine. localhost refers to the emulator itself.
new Uri("http://localhost:5000")
new Uri("http://10.0.2.2:5000")
iOS simulators use localhost directly.
⚠️ Inconsistent JSON casing
APIs typically use camelCase; C# properties are PascalCase. Without JsonSerializerOptions, deserialization silently returns default values.
JsonSerializer.Deserialize<Item>(content);
private static readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
Decision Framework
| Scenario | Error handling approach |
|---|
| Failure is unexpected (auth'd endpoints) | EnsureSuccessStatusCode() — throws HttpRequestException |
| Need to branch on status codes | Check IsSuccessStatusCode or response.StatusCode |
| Network may be unreliable (mobile) | Wrap in try/catch for HttpRequestException |
| Response format may vary | Also catch JsonException |
Checklist