maui-rest-api

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Consuming REST APIs in .NET MAUI

在.NET MAUI中调用REST API

HttpClient & JSON setup

HttpClient与JSON配置

Always configure a shared
JsonSerializerOptions
with camel-case naming:
csharp
private static readonly JsonSerializerOptions _jsonOptions = new()
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    PropertyNameCaseInsensitive = true
};
始终配置一个使用驼峰命名的共享
JsonSerializerOptions
csharp
private static readonly JsonSerializerOptions _jsonOptions = new()
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    PropertyNameCaseInsensitive = true
};

DI registration

DI注册

Register
HttpClient
as a singleton or use
IHttpClientFactory
. Set
BaseAddress
once:
csharp
// 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");
});
HttpClient
注册为单例或使用
IHttpClientFactory
。一次性设置
BaseAddress
csharp
// 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
HttpClient
:
csharp
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);
}
实现该接口,并注入
HttpClient
csharp
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
IsSuccessStatusCode
or call
EnsureSuccessStatusCode()
depending on the scenario:
  • Use
    EnsureSuccessStatusCode()
    when failure is unexpected (throws
    HttpRequestException
    ).
  • Use
    IsSuccessStatusCode
    when you need to branch on specific status codes.
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
}
根据场景选择检查
IsSuccessStatusCode
或调用
EnsureSuccessStatusCode()
  • 当失败属于意外情况时,使用
    EnsureSuccessStatusCode()
    (会抛出
    HttpRequestException
    )。
  • 当需要根据特定状态码分支处理时,使用
    IsSuccessStatusCode
将调用包裹在try/catch块中处理网络级别的失败:
csharp
try
{
    var items = await _apiService.GetItemsAsync();
}
catch (HttpRequestException ex)
{
    // 网络错误或非成功状态码
}
catch (JsonException ex)
{
    // 反序列化失败
}

Common HTTP response codes

常见HTTP响应码

CodeMeaningTypical use
200OKSuccessful GET or PUT
201CreatedSuccessful POST (resource created)
204No ContentSuccessful DELETE or PUT (no body)
400Bad RequestValidation error in request body
404Not FoundResource does not exist
409ConflictDuplicate 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.xml
:
xml
<?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.xml
:
xml
<application android:networkSecurityConfig="@xml/network_security_config" ... />
iOS / Mac Catalyst — add an
NSAppTransportSecurity
exception in
Info.plist
:
xml
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsLocalNetworking</key>
  <true/>
</dict>
Note: Android emulators reach the host machine at
10.0.2.2
. iOS simulators use
localhost
directly.
模拟器默认会阻止明文HTTP流量。当通过
http://
访问本地开发服务器时:
Android — 在
Platforms/Android/Resources/xml/network_security_config.xml
中添加网络安全配置:
xml
<?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.xml
中引用该配置:
xml
<application android:networkSecurityConfig="@xml/network_security_config" ... />
iOS / Mac Catalyst — 在
Info.plist
中添加
NSAppTransportSecurity
例外:
xml
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsLocalNetworking</key>
  <true/>
</dict>
注意: Android模拟器通过
10.0.2.2
访问主机,iOS模拟器直接使用
localhost

Rules

规则

  • Always use
    async/await
    ; never block with
    .Result
    or
    .Wait()
    .
  • Register
    HttpClient
    once (singleton or factory); do not create per-request instances.
  • Set
    BaseAddress
    in DI; use relative URIs in service methods.
  • Apply
    JsonSerializerOptions
    consistently with
    CamelCase
    naming policy.
  • Check
    IsSuccessStatusCode
    before deserializing response bodies.
  • Wrap API calls in try/catch for
    HttpRequestException
    and
    JsonException
    .
  • Use the service interface pattern so view models depend on abstractions.
  • 始终使用
    async/await
    ;绝不要用
    .Result
    .Wait()
    阻塞线程。
  • 只注册一次
    HttpClient
    (单例或工厂模式);不要为每个请求创建实例。
  • 在DI中设置
    BaseAddress
    ;在服务方法中使用相对URI。
  • 统一应用带有驼峰命名策略的
    JsonSerializerOptions
  • 反序列化响应体前检查
    IsSuccessStatusCode
  • 将API调用包裹在try/catch块中处理
    HttpRequestException
    JsonException
  • 使用服务接口模式,让视图模型依赖抽象而非具体实现。