mcpField
M
maui-rest-api

maui-rest-api

github.com/davidortinauDevelopment17660 installsUpdated 16w ago
Repository

About maui-rest-api

A .NET MAUI guide to common pitfalls and sound practices when consuming REST APIs. It covers HTTP client lifetime, asynchronous calls, response handling, JSON, and mobile development networking details.

Key features

  1. 01Reuse HttpClient through dependency injection
  2. 02Use async/await instead of blocking calls
  3. 03Check response status before deserialization
  4. 04Handle network and JSON errors
  5. 05Configure local development networking

Use cases

  1. 01Prevent socket exhaustion from per-request clients
  2. 02Avoid UI-thread deadlocks
  3. 03Handle unsuccessful or malformed API responses
  4. 04Connect mobile emulators to local development servers

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/davidortinau/maui-skills --skill maui-rest-api

After installing, type/maui-rest-apiin Claude Code to confirm it loaded.

Bundled filesreferences/rest-api-reference.md

Details

From the project README

REST API Consumption — Gotchas & Best Practices

Common Mistakes

1. Creating HttpClient per request
// ❌ Creates socket exhaustion — each instance opens a new connection
public async Task<List<Item>> GetItemsAsync()
{
    using var client = new HttpClient();
    var response = await client.GetAsync("https://api.example.com/items");
    // ...
}

// ✅ Register once in DI, inject everywhere
builder.Services.AddSingleton(sp => new HttpClient
{
    BaseAddress = new Uri("https://api.example.com")
});
2. Blocking with .Result or .Wait()
// ❌ Deadlocks on the UI thread
var items = _apiService.GetItemsAsync().Result;

// ✅ Always use async/await
var items = await _apiService.GetItemsAsync();
3. Deserializing before checking status
// ❌ Tries to deserialize error HTML/JSON as your model
var content = await response.Content.ReadAsStringAsync();
var items = JsonSerializer.Deserialize<List<Item>>(content, _jsonOptions);

// ✅ Check status first
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var items = JsonSerializer.Deserialize<List<Item>>(content, _jsonOptions) ?? [];
4. Hardcoding BaseAddress in service methods
// ❌ Absolute URIs in every method — hard to change, easy to typo
await _httpClient.GetAsync("https://api.example.com/api/items");

// ✅ Set BaseAddress in DI, use relative URIs in methods
await _httpClient.GetAsync("api/items");
5. Missing error handling for network failures
// ❌ Crashes on network timeout, DNS failure, etc.
var items = await _apiService.GetItemsAsync();

// ✅ Catch both network and deserialization errors
try
{
    var items = await _apiService.GetItemsAsync();
}
catch (HttpRequestException ex) { /* network or HTTP error */ }
catch (JsonException ex) { /* malformed response */ }

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.

// ❌ On Android emulator, this hits the emulator, not your dev machine
new Uri("http://localhost:5000")

// ✅ Use the emulator's host loopback address
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.

// ❌ Properties stay null/default — no error thrown
JsonSerializer.Deserialize<Item>(content);

// ✅ Configure casing policy
private static readonly JsonSerializerOptions _jsonOptions = new()
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    PropertyNameCaseInsensitive = true
};

Decision Framework

ScenarioError handling approach
Failure is unexpected (auth'd endpoints)EnsureSuccessStatusCode() — throws HttpRequestException
Need to branch on status codesCheck IsSuccessStatusCode or response.StatusCode
Network may be unreliable (mobile)Wrap in try/catch for HttpRequestException
Response format may varyAlso catch JsonException

Checklist

  • HttpClient registered as singleton or via IHttpClientFactory — never created per-request
  • BaseAddress set in DI; service methods use relative URIs
  • JsonSerializerOptions with CamelCase policy applied consistently
  • IsSuccessStatusCode or EnsureSuccessStatusCode() checked before deserializing
  • try/catch for HttpRequestException and JsonException in ViewModel calls
  • All API calls use async/await — no .Result or .Wait()
  • Service interface pattern used so ViewModels depend on abstractions
  • Android clear-text config for local dev (10.0.2.2)
  • iOS NSAllowsLocalNetworking for local dev

Comments

Markdown supported