ServiceStack

Guide to using ServiceStack for REST service consumption in .NET applications

ServiceStack Repo stars is a versatile and feature-rich framework that includes powerful HTTP client libraries for consuming REST APIs. While ServiceStack is primarily known as a complete web service framework, its client libraries can be used independently with any REST API.

ServiceStack’s HTTP client offerings come in two main variants:

  1. ServiceStack.Client - Strongly-typed client with built-in serialization and comprehensive features
  2. ServiceStack.HttpClient - Built on top of HttpClient with the same API as ServiceStack.Client
  3. HTTP Utils - Lightweight string-based fluent API for simple HTTP requests
InfoLink
LicenseFree for OSS / Commercial for Business
DownloadsNuget
Latest VersionNuGet
IssuesGitHub issues
ContributorsGitHub contributors

Key Features

  • Multiple serialization formats - JSON, XML, CSV, MessagePack, Protocol Buffers
  • Request/Response filters - Intercept and modify requests and responses
  • Automatic error handling - Strongly-typed WebServiceException
  • Authentication support - Bearer tokens, API keys, Basic Auth, JWT, OAuth
  • Timeout and retry policies - Configurable with automatic retries
  • Streaming support - Efficient handling of large responses
  • Caching - Built-in response caching mechanisms
  • High performance - Optimized serialization with ServiceStack.Text

Quick Start

Install the package:

dotnet add package ServiceStack.HttpClient

DTO definitions with explicit types:

/// <summary>
/// Represents a task returned from the API.
/// </summary>
public sealed class TaskDto
{
    public int Id { get; set; }
    public required string Title { get; set; }
    public bool Completed { get; set; }
    public string? Description { get; set; }
    public DateTime CreatedAt { get; set; }
}

/// <summary>
/// Request model for creating a new task.
/// </summary>
public sealed class CreateTaskRequest
{
    public required string Title { get; set; }
    public string? Description { get; set; }
    public string Priority { get; set; } = "normal";
}

/// <summary>
/// Request model for updating an existing task.
/// </summary>
public sealed class UpdateTaskRequest
{
    public string? Title { get; set; }
    public string? Description { get; set; }
    public bool? Completed { get; set; }
}

JsonHttpClient Usage

ServiceStack’s JsonHttpClient provides a strongly-typed approach with built-in serialization:

using ServiceStack;

// Create client with base URL
JsonHttpClient client = new("https://api.example.com");

// GET - Retrieve single task
TaskDto? task = await client.GetAsync<TaskDto>("tasks/123");

// GET - Retrieve all tasks
List<TaskDto> tasks = await client.GetAsync<List<TaskDto>>("tasks");

// POST - Create new task
CreateTaskRequest newTask = new() { Title = "Buy flowers", Priority = "high" };
TaskDto createdTask = await client.PostAsync<TaskDto>("tasks", newTask);

// PUT - Update existing task
UpdateTaskRequest updateRequest = new() { Title = "Buy roses", Completed = true };
TaskDto updatedTask = await client.PutAsync<TaskDto>("tasks/123", updateRequest);

// DELETE - Remove task
await client.DeleteAsync<EmptyResponse>("tasks/123");

Authentication

using ServiceStack;

JsonHttpClient client = new("https://api.example.com");

// Basic authentication
client.SetCredentials("username", "password");

// Bearer token (OAuth/JWT)
client.BearerToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";

// API key in header
client.AddHeader("X-API-Key", "your-api-key");

// Session-based authentication
client.SetSessionId("session-id-from-auth");

// Refresh token support
client.RefreshToken = "refresh-token";
client.RefreshTokenUri = "https://api.example.com/auth/refresh";

Request and Response Filters

using ServiceStack;

JsonHttpClient client = new("https://api.example.com");

// Global request filter - runs before every request
client.RequestFilter = (HttpRequestMessage request) =>
{
    request.Headers.Add("X-Request-Id", Guid.NewGuid().ToString());
    request.Headers.Add("X-Client-Version", "1.0.0");
    Console.WriteLine($"Requesting: {request.RequestUri}");
};

// Global response filter - runs after every response
client.ResponseFilter = (HttpResponseMessage response) =>
{
    Console.WriteLine($"Response: {response.StatusCode}");

    if (response.Headers.TryGetValues("X-RateLimit-Remaining", out IEnumerable<string>? values))
    {
        Console.WriteLine($"Rate limit remaining: {values.First()}");
    }
};

Error Handling

using ServiceStack;

JsonHttpClient client = new("https://api.example.com");

try
{
    TaskDto? task = await client.GetAsync<TaskDto>("tasks/999");
}
catch (WebServiceException ex)
{
    // Strongly-typed exception with rich details
    int statusCode = ex.StatusCode;
    string? statusDescription = ex.StatusDescription;
    string? errorCode = ex.ErrorCode;
    string? errorMessage = ex.ErrorMessage;

    Console.WriteLine($"HTTP {statusCode}: {statusDescription}");
    Console.WriteLine($"Error: {errorCode} - {errorMessage}");

    // Access response DTO if available
    ResponseStatus? responseStatus = ex.ResponseStatus;
    if (responseStatus?.Errors is not null)
    {
        foreach (ResponseError error in responseStatus.Errors)
        {
            Console.WriteLine($"Field: {error.FieldName}, Error: {error.Message}");
        }
    }
}

Timeout and Retry Configuration

using ServiceStack;

JsonHttpClient client = new("https://api.example.com")
{
    Timeout = TimeSpan.FromSeconds(30)
};

// Configure retry behavior
client.ExceptionFilter = (HttpResponseMessage response, string operationName) =>
{
    // Custom logic to determine if request should be retried
    if (response.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable)
    {
        // Throw to trigger retry
        throw new WebServiceException("Service temporarily unavailable");
    }
};

HTTP Utils (Lightweight Alternative)

ServiceStack’s HTTP Utils provide a lightweight, string-based fluent API for simple requests:

using ServiceStack;

// GET with automatic deserialization
string taskJson = await "https://api.example.com/tasks/123".GetJsonFromUrlAsync();
TaskDto? task = taskJson.FromJson<TaskDto>();

// GET all tasks
string tasksJson = await "https://api.example.com/tasks".GetJsonFromUrlAsync();
List<TaskDto> tasks = tasksJson.FromJson<List<TaskDto>>();

// POST with JSON body
CreateTaskRequest newTask = new() { Title = "Buy flowers" };
string responseJson = await "https://api.example.com/tasks"
    .PostJsonToUrlAsync(newTask.ToJson());
TaskDto createdTask = responseJson.FromJson<TaskDto>();

// PUT
UpdateTaskRequest updateRequest = new() { Title = "Buy roses", Completed = true };
await "https://api.example.com/tasks/123".PutJsonToUrlAsync(updateRequest.ToJson());

// DELETE
await "https://api.example.com/tasks/123".DeleteFromUrlAsync();

With Headers and Configuration

using ServiceStack;

// Headers and authentication
TaskDto? task = await "https://api.example.com/tasks/123"
    .GetJsonFromUrlAsync(
        requestFilter: request =>
        {
            request.AddHeader("Authorization", "Bearer token");
            request.AddHeader("Accept-Language", "en-US");
            request.Timeout = TimeSpan.FromSeconds(30);
        });

// Response filter for status inspection
await "https://api.example.com/tasks"
    .GetJsonFromUrlAsync(
        responseFilter: response =>
        {
            Console.WriteLine($"Status: {response.StatusCode}");
        });

ServiceStack.Text (High-Performance Serialization)

The ServiceStack.Text library powers ServiceStack’s HTTP clients and can be used independently:

using ServiceStack.Text;

// JSON serialization
TaskDto task = new() { Id = 1, Title = "Test", Completed = false };
string json = task.ToJson();

// JSON deserialization
TaskDto deserialized = json.FromJson<TaskDto>();

// Pretty print
string prettyJson = task.ToJson().IndentJson();

// Type conversion utilities
int number = "42".To<int>();
DateTime date = "2025-01-15".To<DateTime>();
bool flag = "true".To<bool>();

// Dynamic object support
Dictionary<string, object> dynamic = json.FromJson<Dictionary<string, object>>();

Typed API Client Pattern

using ServiceStack;

/// <summary>
/// Strongly-typed API client for the Tasks API.
/// </summary>
public sealed class TasksApiClient : IDisposable
{
    private readonly JsonHttpClient _client;

    /// <summary>
    /// Initializes a new instance of the <see cref="TasksApiClient"/> class.
    /// </summary>
    /// <param name="baseUrl">The base URL of the API.</param>
    /// <param name="bearerToken">Optional bearer token for authentication.</param>
    public TasksApiClient(string baseUrl, string? bearerToken = null)
    {
        _client = new JsonHttpClient(baseUrl)
        {
            Timeout = TimeSpan.FromSeconds(30)
        };

        if (!string.IsNullOrEmpty(bearerToken))
        {
            _client.BearerToken = bearerToken;
        }

        _client.AddHeader("User-Agent", "TasksApiClient/1.0");
    }

    /// <summary>
    /// Retrieves a task by its unique identifier.
    /// </summary>
    public async Task<TaskDto?> GetTaskByIdAsync(int id)
    {
        try
        {
            TaskDto task = await _client.GetAsync<TaskDto>($"tasks/{id}");
            return task;
        }
        catch (WebServiceException ex) when (ex.StatusCode == 404)
        {
            return null;
        }
    }

    /// <summary>
    /// Retrieves all tasks.
    /// </summary>
    public async Task<List<TaskDto>> GetAllTasksAsync()
    {
        List<TaskDto> tasks = await _client.GetAsync<List<TaskDto>>("tasks");
        return tasks;
    }

    /// <summary>
    /// Creates a new task.
    /// </summary>
    public async Task<TaskDto> CreateTaskAsync(CreateTaskRequest request)
    {
        TaskDto createdTask = await _client.PostAsync<TaskDto>("tasks", request);
        return createdTask;
    }

    /// <summary>
    /// Updates an existing task.
    /// </summary>
    public async Task<TaskDto> UpdateTaskAsync(int id, UpdateTaskRequest request)
    {
        TaskDto updatedTask = await _client.PutAsync<TaskDto>($"tasks/{id}", request);
        return updatedTask;
    }

    /// <summary>
    /// Deletes a task by its identifier.
    /// </summary>
    public async Task DeleteTaskAsync(int id)
    {
        await _client.DeleteAsync<EmptyResponse>($"tasks/{id}");
    }

    /// <inheritdoc />
    public void Dispose()
    {
        _client.Dispose();
    }
}

Dependency Injection

using Microsoft.Extensions.DependencyInjection;
using ServiceStack;

IServiceCollection services = new ServiceCollection();

// Register as singleton
services.AddSingleton<IServiceClient>(sp =>
{
    IConfiguration configuration = sp.GetRequiredService<IConfiguration>();
    string baseUrl = configuration["TasksApi:BaseUrl"]
        ?? throw new InvalidOperationException("TasksApi:BaseUrl not configured");
    string? apiKey = configuration["TasksApi:ApiKey"];

    JsonHttpClient client = new(baseUrl);

    if (!string.IsNullOrEmpty(apiKey))
    {
        client.AddHeader("X-API-Key", apiKey);
    }

    return client;
});

// Register typed client
services.AddSingleton<TasksApiClient>(sp =>
{
    IConfiguration configuration = sp.GetRequiredService<IConfiguration>();
    string baseUrl = configuration["TasksApi:BaseUrl"]!;
    string? bearerToken = configuration["TasksApi:BearerToken"];

    return new TasksApiClient(baseUrl, bearerToken);
});
✅ Pros

  • Comprehensive features - Multiple serialization formats, caching, retries
  • High performance - Optimized ServiceStack.Text serializer
  • Strongly-typed exceptions - WebServiceException with rich error details
  • Flexible authentication - Bearer, API keys, sessions, OAuth
  • Request/Response filters - Extensible pipeline
  • Well-documented - Extensive official documentation

⚠️ Considerations

  • Commercial licensing - Free quotas, then paid for business use
  • Learning curve - Larger framework with more concepts
  • Framework size - Part of larger ecosystem, may be overkill for simple use
  • Some features require additional ServiceStack packages

When to Choose ServiceStack

ServiceStack HTTP clients are particularly well-suited for:

  1. Applications already using other ServiceStack components
  2. Projects requiring multiple serialization formats (JSON, XML, CSV, etc.)
  3. Complex authentication requirements
  4. High-performance API consumption with optimized serialization
  5. Teams familiar with the ServiceStack ecosystem

Full Sample

See the full sample on GitHub: https://github.com/BenjaminAbt/dotnet.rest-samples

Further Reading