maui-rest-api

📁 davidortinau/maui-skills 📅 11 days ago
9
总安装量
8
周安装量
#31583
全站排名
安装命令
npx skills add https://github.com/davidortinau/maui-skills --skill maui-rest-api

Agent 安装分布

opencode 8
gemini-cli 8
github-copilot 8
codex 8
kimi-cli 8
amp 8

Skill 文档

Consuming REST APIs in .NET MAUI

HttpClient & JSON setup

Always configure a shared JsonSerializerOptions with camel-case naming:

private static readonly JsonSerializerOptions _jsonOptions = new()
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    PropertyNameCaseInsensitive = true
};

DI registration

Register HttpClient as a singleton or use IHttpClientFactory. Set BaseAddress once:

// 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:

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:

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:

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

GET (list)

    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)

    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)

    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)

    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

    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:

try
{
    var items = await _apiService.GetItemsAsync();
}
catch (HttpRequestException ex)
{
    // Network error or non-success status code
}
catch (JsonException ex)
{
    // Deserialization failure
}

Common HTTP response codes

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

Platform-specific: local development with HTTP clear-text

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 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:

<application android:networkSecurityConfig="@xml/network_security_config" ... />

iOS / Mac Catalyst — add an NSAppTransportSecurity exception in Info.plist:

<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.

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.