mcpField
M
maui-rest-api

maui-rest-api

github.com/davidortinau开发17660 次安装16 周前更新
代码仓库

关于 maui-rest-api

介绍 .NET MAUI 消费 REST API 时的常见陷阱与实践,涵盖 HTTP 客户端生命周期、异步调用、响应处理、JSON 及移动端网络配置。

主要功能

  1. 01通过依赖注入复用 HttpClient
  2. 02使用 async/await 避免阻塞调用
  3. 03反序列化前检查响应状态
  4. 04处理网络与 JSON 错误
  5. 05配置本地开发网络

适用场景

  1. 01避免每次请求创建客户端导致套接字耗尽
  2. 02防止 UI 线程死锁
  3. 03处理失败或格式异常的 API 响应
  4. 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:需要在 network_security_config.xml 中为 10.0.2.2 设置 cleartextTrafficPermitted="true"
  • iOS/Mac Catalyst:需要在 Info.plist 中设置 NSAllowsLocalNetworking
⚠️ Android 模拟器使用 10.0.2.2 访问 localhost

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
网络可能不稳定(移动设备)使用 try/catch 捕获 HttpRequestException
响应格式可能变化也要捕获 JsonException

检查清单

  • 将 HttpClient 注册为单例或通过 IHttpClientFactory 获取——切勿为每个请求单独创建
  • 在 DI 中设置 BaseAddress;服务方法使用相对 URI
  • 始终应用带有 CamelCase 策略的 JsonSerializerOptions
  • 反序列化前检查 IsSuccessStatusCode 或调用 EnsureSuccessStatusCode()
  • 在 ViewModel 调用中使用 try/catch 捕获 HttpRequestException 和 JsonException
  • 所有 API 调用都使用 async/await——不使用 .Result 或 .Wait()
  • 使用服务接口模式,使 ViewModel 依赖抽象
  • 为本地开发配置 Android 明文通信(10.0.2.2)
  • 为本地开发配置 iOS NSAllowsLocalNetworking

评论

支持 Markdown