REST API Reference
HttpClient & JSON Setup
Always configure a shared JsonSerializerOptions with camel-case naming:
private static readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};DI Registration
Register HttpClient as a singleton or use IHttpClientFactory. Set BaseAddress once:
// MauiProgram.cs
builder.Services.AddSingleton(sp => new HttpClient
{
BaseAddress = new Uri("https://api.example.com")
});
builder.Services.AddSingleton<IMyApiService, MyApiService>();For more control, use the factory pattern:
builder.Services.AddHttpClient<IMyApiService, MyApiService>(client =>
{
client.BaseAddress = new Uri("https://api.example.com");
});Service Interface + Implementation
Define a clean interface for each API resource:
public interface IMyApiService
{
Task<List<Item>> GetItemsAsync();
Task<Item?> GetItemAsync(int id);
Task<Item?> CreateItemAsync(Item item);
Task<bool> UpdateItemAsync(Item item);
Task<bool> DeleteItemAsync(int id);
}Implement the interface, injecting HttpClient:
public class MyApiService : IMyApiService
{
private readonly HttpClient _httpClient;
private static readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
public MyApiService(HttpClient httpClient)
{
_httpClient = httpClient;
}CRUD Operations
GET (list)
public async Task<List<Item>> GetItemsAsync()
{
var response = await _httpClient.GetAsync("api/items");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<List<Item>>(content, _jsonOptions) ?? [];
}GET (single)
public async Task<Item?> GetItemAsync(int id)
{
var response = await _httpClient.GetAsync($"api/items/{id}");
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
return null;
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<Item>(content, _jsonOptions);
}POST (create)
public async Task<Item?> CreateItemAsync(Item item)
{
var json = JsonSerializer.Serialize(item, _jsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync("api/items", content);
if (!response.IsSuccessStatusCode)
return null;
var responseBody = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<Item>(responseBody, _jsonOptions);
}PUT (update)
public async Task<bool> UpdateItemAsync(Item item)
{
var json = JsonSerializer.Serialize(item, _jsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _httpClient.PutAsync($"api/items/{item.Id}", content);
return response.IsSuccessStatusCode;
}DELETE
public async Task<bool> DeleteItemAsync(int id)
{
var response = await _httpClient.DeleteAsync($"api/items/{id}");
return response.IsSuccessStatusCode;
}
}Common HTTP Response Codes
| Code | Meaning | Typical use |
|---|---|---|
| 200 | OK | Successful GET or PUT |
| 201 | Created | Successful POST (resource created) |
| 204 | No Content | Successful DELETE or PUT (no body) |
| 400 | Bad Request | Validation error in request body |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Duplicate or state conflict |
Platform-Specific: Local Development with HTTP Clear-Text
Emulators and simulators block clear-text HTTP by default. When targeting a local dev server over http://:
Android — add a network security config in 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>Reference it in AndroidManifest.xml:
<application android:networkSecurityConfig="@xml/network_security_config" ... />iOS / Mac Catalyst — add an NSAppTransportSecurity exception in Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>Note: Android emulators reach the host machine at
10.0.2.2. iOS simulators uselocalhostdirectly.