Xpandables.Rests
10.0.2
dotnet add package Xpandables.Rests --version 10.0.2
NuGet\Install-Package Xpandables.Rests -Version 10.0.2
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Xpandables.Rests" Version="10.0.2" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Xpandables.Rests" Version="10.0.2" />
<PackageReference Include="Xpandables.Rests" />
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Xpandables.Rests --version 10.0.2
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: Xpandables.Rests, 10.0.2"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Xpandables.Rests@10.0.2
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Xpandables.Rests&version=10.0.2
#tool nuget:?package=Xpandables.Rests&version=10.0.2
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
System.Rests
Type-safe, attribute-based REST client with automatic request composition, response handling, and interceptors.
Overview
System.Rests provides a type-safe, attribute-based HTTP client for building RESTful API clients. It uses attributes to define endpoints and request types, with automatic request composition, response handling, request/response interceptors, and resilience options.
Built for .NET 10 with full async support.
Features
Core Client
IRestClient— Core HTTP client interface with async supportRestClient— Default implementation with interceptor pipeline
Request/Response Building
IRestRequestBuilder— Build HTTP requests from contextIRestResponseBuilder— Build responses from HTTP messagesRestRequestBuilder— Default request builder with interceptorsRestResponseBuilder— Default response builder with interceptors
REST Attributes
RestGetAttribute— HTTP GET requestsRestPostAttribute— HTTP POST requestsRestPutAttribute— HTTP PUT requestsRestDeleteAttribute— HTTP DELETE requestsRestPatchAttribute— HTTP PATCH requestsIRestAttributeBuilder— Dynamic attribute building
Request Types
IRestRequest— Base request interfaceIRestString— JSON body requestsIRestQueryString— Query parameter requestsIRestPathString— URL path parameter requestsIRestFormUrlEncoded— Form data requestsIRestMultipart— File upload requestsIRestByteArray— Binary data requestsIRestStream— Stream data requestsIRestHeader— Custom header requestsIRestCookie— Cookie requestsIRestBasicAuthentication— Basic auth requestsIRestPatch— JSON Patch requestsIRestMime— MIME type support
Interceptors
IRestRequestInterceptor— Intercept requests before sendingIRestResponseInterceptor— Intercept responses after receivingOrder— Control interceptor execution order
Resilience Options
RestClientOptions— Configure timeout, retry, circuit breaker, loggingRestRetryOptions— Retry policy configurationRestCircuitBreakerOptions— Circuit breaker configurationRestLogLevel— Logging levels
Request Composers
IRestRequestComposer— Compose HTTP request messagesRestStringComposer— JSON body compositionRestQueryStringComposer— Query string compositionRestPathStringComposer— Path parameter compositionRestFormUrlEncodedComposer— Form data compositionRestMultipartComposer— Multipart compositionRestHeaderComposer— Header compositionRestCookieComposer— Cookie compositionRestBasicAuthComposer— Basic auth compositionRestByteArrayComposer— Binary compositionRestStreamComposer— Stream compositionRestPatchComposer— JSON Patch composition
Response Composers
IRestResponseComposer— Compose REST responsesRestResponseResultComposer— Typed result responsesRestResponseContentComposer— Content responsesRestResponseStreamComposer— Stream responsesRestResponseStreamPagedComposer— Paged stream responsesRestResponseNoContentComposer— No content responsesRestResponseFailureComposer— Error responses
Other Types
RestRequest— Request wrapperRestResponse— Response wrapper with typed resultRestRequestContext— Request building contextRestResponseContext— Response building contextRestSettings— Global settingsRestAttributeProvider— Attribute resolutionRestAuthorizationHandler— Authorization handling
Installation
dotnet add package Xpandables.Rests
Quick Start
Register Services
using Microsoft.Extensions.DependencyInjection;
// Option A: Full setup with resilience (retry + circuit breaker)
services.AddXRestAttributeProvider();
services.AddXRestRequestComposers();
services.AddXRestResponseComposers();
services.AddXRestClientWithResilience((sp, client) =>
{
client.BaseAddress = new Uri("https://api.example.com");
});
// Option B: Simple setup without resilience
services.AddXRestClient((sp, client) =>
{
client.BaseAddress = new Uri("https://api.example.com");
});
GET Request with Path Parameters
using System.Rests.Abstractions;
[RestGet("/api/users/{id}")]
public sealed record GetUserRequest(Guid Id)
: IRestRequestResult<UserDto>, IRestPathString
{
public IDictionary<string, string> GetPathString() =>
new Dictionary<string, string> { ["id"] = Id.ToString() };
}
// Usage
public class UserService(IRestClient restClient)
{
public async Task<UserDto?> GetUserAsync(Guid id, CancellationToken ct)
{
using RestResponse response = await restClient.SendAsync(
new GetUserRequest(id), ct);
if (response.IsSuccess)
return response.ToRestResponse<UserDto>().Result;
Console.WriteLine($"Failed: {response.StatusCode} — {response.ReasonPhrase}");
return null;
}
}
GET Request with Query String
[RestGet("/api/products")]
public sealed record SearchProductsRequest(string? Category, int Page, int PageSize)
: IRestRequestResult<ProductListDto>, IRestQueryString
{
public IDictionary<string, string?>? GetQueryString() =>
new Dictionary<string, string?>
{
["category"] = Category,
["page"] = Page.ToString(),
["pageSize"] = PageSize.ToString()
};
}
POST Request with JSON Body
[RestPost("/api/orders")]
public sealed record CreateOrderRequest(string CustomerName, decimal Total)
: IRestRequestResult<OrderDto>, IRestString;
// Usage
using RestResponse response = await restClient.SendAsync(
new CreateOrderRequest("Alice", 149.99m), ct);
PUT Request with Path + JSON Body
[RestPut("/api/orders/{id}")]
public sealed record UpdateOrderRequest(Guid Id, string CustomerName, decimal Total)
: IRestRequestResult<OrderDto>, IRestPathString, IRestString
{
public IDictionary<string, string> GetPathString() =>
new Dictionary<string, string> { ["id"] = Id.ToString() };
}
DELETE Request
[RestDelete("/api/orders/{id}")]
public sealed record DeleteOrderRequest(Guid Id)
: IRestRequest, IRestPathString
{
public IDictionary<string, string> GetPathString() =>
new Dictionary<string, string> { ["id"] = Id.ToString() };
}
File Upload (Multipart)
[RestPost("/api/documents")]
public sealed record UploadDocumentRequest(Stream FileStream, string FileName)
: IRestRequestResult<DocumentDto>, IRestMultipart
{
public MultipartFormDataContent GetMultipartContent()
{
var content = new MultipartFormDataContent();
content.Add(new StreamContent(FileStream), "file", FileName);
return content;
}
}
Streaming Response
// Request that returns an async stream of items
[RestGet("/api/events/stream")]
public sealed record GetEventsStreamRequest
: IRestRequestStream<EventDto>, IRestQueryString
{
public IDictionary<string, string?>? GetQueryString() => null;
}
Paged Streaming Response
// Request that returns IAsyncPagedEnumerable
[RestGet("/api/products/paged")]
public sealed record GetProductsPagedRequest(int Page, int PageSize)
: IRestRequestStreamPaged<ProductDto>, IRestQueryString
{
public IDictionary<string, string?>? GetQueryString() =>
new Dictionary<string, string?>
{
["page"] = Page.ToString(),
["pageSize"] = PageSize.ToString()
};
}
Dynamic Attribute Building (IRestAttributeBuilder)
// Build the RestAttribute at runtime instead of using a static attribute
public sealed record DynamicGetUserRequest(Guid Id)
: IRestRequestResult<UserDto>, IRestPathString, IRestAttributeBuilder
{
public IDictionary<string, string> GetPathString() =>
new Dictionary<string, string> { ["id"] = Id.ToString() };
public RestAttribute Build(IServiceProvider sp) =>
new RestGetAttribute($"/api/v2/users/{{id}}")
{
IsSecured = true,
Accept = "application/json"
};
}
Custom Headers
[RestGet("/api/data")]
public sealed record GetDataRequest(string ApiKey)
: IRestRequestResult<DataDto>, IRestHeader
{
public ElementCollection GetHeaders() =>
[new("X-Api-Key", [ApiKey])];
}
Basic Authentication
using System.Net.Http.Headers;
[RestGet("/api/secure/data")]
public sealed record GetSecureDataRequest(string Username, string Password)
: IRestRequestResult<SecureDataDto>, IRestBasicAuthentication
{
public AuthenticationHeaderValue GetAuthenticationHeaderValue()
{
string credentials = Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes($"{Username}:{Password}"));
return new AuthenticationHeaderValue("Basic", credentials);
}
}
Request Interceptor
public sealed class CorrelationIdInterceptor : IRestRequestInterceptor
{
public int Order => 0;
public ValueTask InterceptAsync(
RestRequestContext context, CancellationToken ct)
{
context.HttpRequestMessage.Headers.Add(
"X-Correlation-Id", Guid.CreateVersion7().ToString("N"));
return ValueTask.CompletedTask;
}
}
// Register
services.AddXRestRequestInterceptor<CorrelationIdInterceptor>();
Response Interceptor
public sealed class MetricsResponseInterceptor : IRestResponseInterceptor
{
public int Order => 0;
public ValueTask<RestResponse> InterceptAsync(
RestResponseContext context,
RestResponse response,
CancellationToken ct)
{
Console.WriteLine(
$"[{response.StatusCode}] {context.HttpResponseMessage?.RequestMessage?.RequestUri}");
return ValueTask.FromResult(response);
}
}
// Register
services.AddXRestResponseInterceptor<MetricsResponseInterceptor>();
Configure Resilience
services.ConfigureXRestClientOptions(options =>
{
options.Timeout = TimeSpan.FromSeconds(30);
options.EnableLogging = true;
options.LogLevel = RestLogLevel.Information;
options.Retry = new RestRetryOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(1),
MaxDelay = TimeSpan.FromSeconds(30),
UseExponentialBackoff = true,
JitterFactor = 0.2
};
options.CircuitBreaker = new RestCircuitBreakerOptions
{
FailureThreshold = 5,
BreakDuration = TimeSpan.FromSeconds(30),
SamplingDuration = TimeSpan.FromSeconds(30),
MinimumThroughput = 10
};
});
Form URL Encoded
[RestPost("/api/auth/token")]
public sealed record TokenRequest(string ClientId, string ClientSecret)
: IRestRequestResult<TokenResponse>, IRestFormUrlEncoded
{
public FormUrlEncodedContent GetFormUrlEncodedContent() =>
new(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = ClientId,
["client_secret"] = ClientSecret
});
}
Core Types
| Type | Description |
|---|---|
IRestClient |
HTTP client interface |
IRestRequest |
Base request interface |
RestAttribute |
Endpoint attribute |
IRestRequestInterceptor |
Request interceptor |
IRestResponseInterceptor |
Response interceptor |
RestResponse |
Response wrapper |
RestClientOptions |
Resilience configuration |
✅ Best Practices
- Use appropriate request interfaces —
IRestStringfor JSON,IRestQueryStringfor GET params - Combine interfaces — A request can implement multiple interfaces (
IRestPathString+IRestString) - Use records — Immutable request types work best
- Dispose responses — Always use
usingwithRestResponse - Handle errors — Check
IsSuccessbefore accessing results - Configure timeouts — Set appropriate timeouts for your API
📚 Related Packages
- Xpandables.Results — Result types for response handling
- Xpandables.AspNetCore — ASP.NET Core integration
📄 License
Apache License 2.0 — Copyright © Kamersoft 2025
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- Microsoft.Extensions.Configuration (>= 10.0.5)
- Microsoft.Extensions.Configuration.EnvironmentVariables (>= 10.0.5)
- Microsoft.Extensions.Configuration.Json (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection (>= 10.0.5)
- Microsoft.Extensions.Http (>= 10.0.5)
- Microsoft.Extensions.Http.Resilience (>= 10.4.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options (>= 10.0.5)
- Microsoft.Extensions.Primitives (>= 10.0.5)
- Xpandables.AsyncPaged.Json (>= 10.0.2)
- Xpandables.Primitives (>= 10.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.