maui-rest-api
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseConsuming REST APIs in .NET MAUI
在.NET MAUI中调用REST API
HttpClient & JSON setup
HttpClient与JSON配置
Always configure a shared with camel-case naming:
JsonSerializerOptionscsharp
private static readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};始终配置一个使用驼峰命名的共享:
JsonSerializerOptionscsharp
private static readonly JsonSerializerOptions _jsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};DI registration
DI注册
Register as a singleton or use . Set once:
HttpClientIHttpClientFactoryBaseAddresscsharp
// 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:
csharp
builder.Services.AddHttpClient<IMyApiService, MyApiService>(client =>
{
client.BaseAddress = new Uri("https://api.example.com");
});将注册为单例或使用。一次性设置:
HttpClientIHttpClientFactoryBaseAddresscsharp
// MauiProgram.cs
builder.Services.AddSingleton(sp => new HttpClient
{
BaseAddress = new Uri("https://api.example.com")
});
builder.Services.AddSingleton<IMyApiService, MyApiService>();如需更多控制,可使用工厂模式:
csharp
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:
csharp
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 :
HttpClientcsharp
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;
}为每个API资源定义清晰的接口:
csharp
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);
}实现该接口,并注入:
HttpClientcsharp
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
CRUD操作
GET (list)
GET(列表)
csharp
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) ?? [];
}csharp
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)
GET(单个资源)
csharp
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);
}csharp
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)
POST(创建)
csharp
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);
}csharp
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)
PUT(更新)
csharp
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;
}csharp
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
DELETE(删除)
csharp
public async Task<bool> DeleteItemAsync(int id)
{
var response = await _httpClient.DeleteAsync($"api/items/{id}");
return response.IsSuccessStatusCode;
}
}csharp
public async Task<bool> DeleteItemAsync(int id)
{
var response = await _httpClient.DeleteAsync($"api/items/{id}");
return response.IsSuccessStatusCode;
}
}Error handling
错误处理
Check or call depending on the scenario:
IsSuccessStatusCodeEnsureSuccessStatusCode()- Use when failure is unexpected (throws
EnsureSuccessStatusCode()).HttpRequestException - Use when you need to branch on specific status codes.
IsSuccessStatusCode
Wrap calls in try/catch for network-level failures:
csharp
try
{
var items = await _apiService.GetItemsAsync();
}
catch (HttpRequestException ex)
{
// Network error or non-success status code
}
catch (JsonException ex)
{
// Deserialization failure
}根据场景选择检查或调用:
IsSuccessStatusCodeEnsureSuccessStatusCode()- 当失败属于意外情况时,使用(会抛出
EnsureSuccessStatusCode())。HttpRequestException - 当需要根据特定状态码分支处理时,使用。
IsSuccessStatusCode
将调用包裹在try/catch块中处理网络级别的失败:
csharp
try
{
var items = await _apiService.GetItemsAsync();
}
catch (HttpRequestException ex)
{
// 网络错误或非成功状态码
}
catch (JsonException ex)
{
// 反序列化失败
}Common HTTP response codes
常见HTTP响应码
| 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 |
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 200 | 成功 | 成功的GET或PUT请求 |
| 201 | 已创建 | 成功的POST请求(资源已创建) |
| 204 | 无内容 | 成功的DELETE或PUT请求(无响应体) |
| 400 | 错误请求 | 请求体验证失败 |
| 404 | 未找到 | 资源不存在 |
| 409 | 冲突 | 重复资源或状态冲突 |
Platform-specific: local development with HTTP clear-text
平台特定配置:本地开发使用HTTP明文流量
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.xmlxml
<?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.xmlxml
<application android:networkSecurityConfig="@xml/network_security_config" ... />iOS / Mac Catalyst — add an exception in :
NSAppTransportSecurityInfo.plistxml
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>Note: Android emulators reach the host machine at. iOS simulators use10.0.2.2directly.localhost
模拟器默认会阻止明文HTTP流量。当通过访问本地开发服务器时:
http://Android — 在中添加网络安全配置:
Platforms/Android/Resources/xml/network_security_config.xmlxml
<?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>在中引用该配置:
AndroidManifest.xmlxml
<application android:networkSecurityConfig="@xml/network_security_config" ... />iOS / Mac Catalyst — 在中添加例外:
Info.plistNSAppTransportSecurityxml
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>注意: Android模拟器通过访问主机,iOS模拟器直接使用10.0.2.2。localhost
Rules
规则
- Always use ; never block with
async/awaitor.Result..Wait() - Register once (singleton or factory); do not create per-request instances.
HttpClient - Set in DI; use relative URIs in service methods.
BaseAddress - Apply consistently with
JsonSerializerOptionsnaming policy.CamelCase - Check before deserializing response bodies.
IsSuccessStatusCode - Wrap API calls in try/catch for and
HttpRequestException.JsonException - Use the service interface pattern so view models depend on abstractions.
- 始终使用;绝不要用
async/await或.Result阻塞线程。.Wait() - 只注册一次(单例或工厂模式);不要为每个请求创建实例。
HttpClient - 在DI中设置;在服务方法中使用相对URI。
BaseAddress - 统一应用带有驼峰命名策略的。
JsonSerializerOptions - 反序列化响应体前检查。
IsSuccessStatusCode - 将API调用包裹在try/catch块中处理和
HttpRequestException。JsonException - 使用服务接口模式,让视图模型依赖抽象而非具体实现。