关于 maui-rest-api
介绍 .NET MAUI 消费 REST API 时的常见陷阱与实践,涵盖 HTTP 客户端生命周期、异步调用、响应处理、JSON 及移动端网络配置。
主要功能
- 01通过依赖注入复用 HttpClient
- 02使用 async/await 避免阻塞调用
- 03反序列化前检查响应状态
- 04处理网络与 JSON 错误
- 05配置本地开发网络
适用场景
- 01避免每次请求创建客户端导致套接字耗尽
- 02防止 UI 线程死锁
- 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:需要在
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