ServiceStack
7 minute read
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:
- ServiceStack.Client - Strongly-typed client with built-in serialization and comprehensive features
- ServiceStack.HttpClient - Built on top of
HttpClientwith the same API as ServiceStack.Client - HTTP Utils - Lightweight string-based fluent API for simple HTTP requests
| Info | Link |
|---|---|
| License | Free for OSS / Commercial for Business |
| Downloads | |
| Latest Version | |
| Issues | |
| 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);
});
- Comprehensive features - Multiple serialization formats, caching, retries
- High performance - Optimized
ServiceStack.Textserializer - Strongly-typed exceptions -
WebServiceExceptionwith rich error details - Flexible authentication - Bearer, API keys, sessions, OAuth
- Request/Response filters - Extensible pipeline
- Well-documented - Extensive official documentation
- 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:
- Applications already using other ServiceStack components
- Projects requiring multiple serialization formats (JSON, XML, CSV, etc.)
- Complex authentication requirements
- High-performance API consumption with optimized serialization
- Teams familiar with the ServiceStack ecosystem
Full Sample
See the full sample on GitHub: https://github.com/BenjaminAbt/dotnet.rest-samples