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" />
                    
Directory.Packages.props
<PackageReference Include="Xpandables.Rests" />
                    
Project file
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
                    
#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
                    
Install as a Cake Addin
#tool nuget:?package=Xpandables.Rests&version=10.0.2
                    
Install as a Cake Tool

System.Rests

NuGet .NET License

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 support
  • RestClient — Default implementation with interceptor pipeline

Request/Response Building

  • IRestRequestBuilder — Build HTTP requests from context
  • IRestResponseBuilder — Build responses from HTTP messages
  • RestRequestBuilder — Default request builder with interceptors
  • RestResponseBuilder — Default response builder with interceptors

REST Attributes

  • RestGetAttribute — HTTP GET requests
  • RestPostAttribute — HTTP POST requests
  • RestPutAttribute — HTTP PUT requests
  • RestDeleteAttribute — HTTP DELETE requests
  • RestPatchAttribute — HTTP PATCH requests
  • IRestAttributeBuilder — Dynamic attribute building

Request Types

  • IRestRequest — Base request interface
  • IRestString — JSON body requests
  • IRestQueryString — Query parameter requests
  • IRestPathString — URL path parameter requests
  • IRestFormUrlEncoded — Form data requests
  • IRestMultipart — File upload requests
  • IRestByteArray — Binary data requests
  • IRestStream — Stream data requests
  • IRestHeader — Custom header requests
  • IRestCookie — Cookie requests
  • IRestBasicAuthentication — Basic auth requests
  • IRestPatch — JSON Patch requests
  • IRestMime — MIME type support

Interceptors

  • IRestRequestInterceptor — Intercept requests before sending
  • IRestResponseInterceptor — Intercept responses after receiving
  • Order — Control interceptor execution order

Resilience Options

  • RestClientOptions — Configure timeout, retry, circuit breaker, logging
  • RestRetryOptions — Retry policy configuration
  • RestCircuitBreakerOptions — Circuit breaker configuration
  • RestLogLevel — Logging levels

Request Composers

  • IRestRequestComposer — Compose HTTP request messages
  • RestStringComposer — JSON body composition
  • RestQueryStringComposer — Query string composition
  • RestPathStringComposer — Path parameter composition
  • RestFormUrlEncodedComposer — Form data composition
  • RestMultipartComposer — Multipart composition
  • RestHeaderComposer — Header composition
  • RestCookieComposer — Cookie composition
  • RestBasicAuthComposer — Basic auth composition
  • RestByteArrayComposer — Binary composition
  • RestStreamComposer — Stream composition
  • RestPatchComposer — JSON Patch composition

Response Composers

  • IRestResponseComposer — Compose REST responses
  • RestResponseResultComposer — Typed result responses
  • RestResponseContentComposer — Content responses
  • RestResponseStreamComposer — Stream responses
  • RestResponseStreamPagedComposer — Paged stream responses
  • RestResponseNoContentComposer — No content responses
  • RestResponseFailureComposer — Error responses

Other Types

  • RestRequest — Request wrapper
  • RestResponse — Response wrapper with typed result
  • RestRequestContext — Request building context
  • RestResponseContext — Response building context
  • RestSettings — Global settings
  • RestAttributeProvider — Attribute resolution
  • RestAuthorizationHandler — 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

  1. Use appropriate request interfacesIRestString for JSON, IRestQueryString for GET params
  2. Combine interfaces — A request can implement multiple interfaces (IRestPathString + IRestString)
  3. Use records — Immutable request types work best
  4. Dispose responses — Always use using with RestResponse
  5. Handle errors — Check IsSuccess before accessing results
  6. Configure timeouts — Set appropriate timeouts for your API

  • Xpandables.Results — Result types for response handling
  • Xpandables.AspNetCore — ASP.NET Core integration

📄 License

Apache License 2.0 — Copyright © Kamersoft 2025

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

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.2 69 3/15/2026
10.0.1 89 2/20/2026
10.0.0 102 1/9/2026