maui-rest-api について
.NET MAUI で REST API を利用する際の注意点と実践方法をまとめたガイドです。HttpClient の管理、非同期処理、レスポンス、JSON、モバイルのネットワーク設定を扱います。
主な機能
- 01依存性注入による HttpClient の再利用
- 02ブロッキングを避けた async/await
- 03デシリアライズ前のステータス確認
- 04ネットワークと JSON のエラー処理
- 05ローカル開発用ネットワーク設定
活用シーン
- 01リクエストごとの生成によるソケット枯渇を防止
- 02UI スレッドのデッドロックを回避
- 03失敗または不正形式の API 応答を処理
- 04モバイルエミュレーターから開発サーバーへ接続
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ davidortinau/ maui-skills --skill maui-rest-apiインストール後、Claude Code で/maui-rest-apiを入力して読み込まれたか確認します。
同梱ファイルreferences/rest-api-reference.md
詳細
REST API の利用 — 注意点とベストプラクティス
よくある間違い
1. リクエストごとに HttpClient を作成する
// ❌ 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. .Result または .Wait() で処理をブロックする
// ❌ Deadlocks on the UI thread
var items = _apiService.GetItemsAsync().Result;
// ✅ Always use async/await
var items = await _apiService.GetItemsAsync();
3. ステータスを確認する前にデシリアライズする
// ❌ 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. サービスメソッドに BaseAddress をハードコードする
// ❌ 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. ネットワーク障害時のエラー処理がない
// ❌ 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 */ }
プラットフォーム固有の落とし穴
⚠️ エミュレーター/シミュレーターでは平文 HTTP がブロックされます
http:// を使うローカル開発サーバーは、デフォルトでブロックされます。例外を設定してください。
- Android:
10.0.2.2に対してcleartextTrafficPermitted="true"を指定したnetwork_security_config.xmlが必要です - iOS/Mac Catalyst:
Info.plistにNSAllowsLocalNetworkingが必要です
⚠️ Android エミュレーターでは localhost に 10.0.2.2 を使用します
Android エミュレーターでは、10.0.2.2 がホストマシンに割り当てられます。localhost はエミュレーター自体を指します。
// ❌ 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 シミュレーターでは localhost をそのまま使用できます。
⚠️ JSON の大文字小文字が一致しない
API では通常 camelCase が使われますが、C# のプロパティは PascalCase です。JsonSerializerOptions を指定しないと、デシリアライズ時にエラーが発生せず、既定値が返されます。
// ❌ 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
};
判断の指針
| シナリオ | エラー処理の方法 |
|---|---|
| 予期しない失敗(認証が必要なエンドポイント) | EnsureSuccessStatusCode() — HttpRequestException をスローします |
| ステータスコードに応じた分岐が必要 | IsSuccessStatusCode または response.StatusCode を確認します |
| ネットワークが不安定な可能性がある(モバイル) | HttpRequestException を try/catch で囲みます |
| レスポンス形式が変わる可能性がある | JsonException も捕捉します |
チェックリスト
-
HttpClientはシングルトンまたはIHttpClientFactoryを通じて登録する — リクエストごとに作成しない -
BaseAddressは DI で設定し、サービスメソッドでは相対 URI を使用する -
CamelCaseポリシーを指定したJsonSerializerOptionsを一貫して適用する - デシリアライズの前に
IsSuccessStatusCodeまたはEnsureSuccessStatusCode()を確認する - ViewModel の呼び出しでは
HttpRequestExceptionとJsonExceptionをtry/catchで捕捉する - すべての API 呼び出しで
async/awaitを使う —.Resultや.Wait()は使わない - サービスインターフェイスのパターンを使い、ViewModel が抽象化に依存するようにする
- ローカル開発用の Android 平文通信設定(
10.0.2.2) - ローカル開発用の iOS
NSAllowsLocalNetworking
コメント
Markdown 対応