All skills
davidortinau avatar

/maui-rest-api

@a0cf477

Guidance for consuming REST APIs in .NET MAUI apps. Covers HttpClient setup with System.Text.Json, DI registration, service interface/implementation pattern, full CRUD operations (GET, POST, PUT, DELETE), error handling, platform-specific clear-text traffic configuration, and async/await best practices. USE FOR: "REST API", "HttpClient", "call API", "GET request", "POST request", "API service", "JSON deserialization", "CRUD operations", "clear-text traffic", "consume API MAUI". DO NOT USE FOR: Aspire service discovery (use maui-aspire), authentication token handling (use maui-authentication), or local database storage (use maui-sqlite-database).

Use this Skill: https://skilld.dev/gh/davidortinau/maui-skills/maui-rest-api

This session only. Nothing lands on disk.

referencesrest-api-reference.md

≈1.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

REST API Reference

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;
    }
}

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.

Source: SKILL.md on GitHub

1 warning15d4 checks · Risk SAFE
  • Gen Agent Trust Hub15d

    This skill provides standard architectural guidance and code samples for consuming REST APIs in .NET MAUI applications. No security risks were identified.

  • Socket15d

    No alerts

  • Snyk15d

    Risk: LOW · No issues

  • Runlayer7mo

    1/1 file flagged

Signed by skilld at a0cf477. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 6 months ago

README badge

README badge for davidortinau/maui-skills/maui-rest-api