RESTween 1.7.10
dotnet add package RESTween --version 1.7.10
NuGet\Install-Package RESTween -Version 1.7.10
<PackageReference Include="RESTween" Version="1.7.10" />
<PackageVersion Include="RESTween" Version="1.7.10" />
<PackageReference Include="RESTween" />
paket add RESTween --version 1.7.10
#r "nuget: RESTween, 1.7.10"
#:package RESTween@1.7.10
#addin nuget:?package=RESTween&version=1.7.10
#tool nuget:?package=RESTween&version=1.7.10
RESTween
RESTween is a lightweight REST API client package for .NET. It turns a C# interface into a runtime HTTP client by using Castle DynamicProxy, HttpClient, and RESTween attributes.
The package is designed for teams that want strongly typed API contracts without hand-writing request URLs, query strings, headers, JSON bodies, multipart forms, and response handling for every endpoint.
What This Package Provides
- Runtime proxy clients for API interfaces.
- Attribute-based endpoint contracts with
[Get],[Post],[Put], and[Delete]. - Route, query, header, body, and multipart parameter binding.
- Method-level headers through
[Headers]. - JSON request body serialization with
System.Text.Json. - Multipart upload support for
Stream,byte[],FileInfo, simple values, and complex JSON parts. - Client-only metadata attributes:
[Headers],[Multipart],[Cache], and[RateLimit]. - Custom request handling through
IRequestHandler. - Extensible request-building pipeline through DI.
- Shared contracts from
RESTween.Core, so the same interface can be reused by client and server packages.
Install
dotnet add package RESTween
RESTween depends on RESTween.Core, so the shared attributes are installed automatically.
The client package also owns client-only attributes such as [Headers], [Multipart], [Cache], and [RateLimit]. They use the same RESTween.Attributes namespace as the shared contract attributes.
Basic Usage
Define an API interface:
using RESTween.Attributes;
public interface IUserApi
{
[Get("/users/{id}")]
Task<UserDto> GetUserAsync([Route] int id);
[Get("/users")]
Task<IReadOnlyList<UserDto>> SearchUsersAsync([Query] UserSearchQuery query);
[Post("/users")]
Task<UserDto> CreateUserAsync([Body] CreateUserDto dto);
[Put("/users/{id}")]
Task<UserDto> UpdateUserAsync([Route] int id, [Body] UpdateUserDto dto);
[Delete("/users/{id}")]
Task DeleteUserAsync([Route] int id);
}
Register the client:
using RESTween;
services.AddApiClient<IUserApi>(new Uri("https://api.example.com"));
Inject and use the interface:
public sealed class UserService
{
private readonly IUserApi _users;
public UserService(IUserApi users)
{
_users = users;
}
public Task<UserDto> GetUserAsync(int id)
{
return _users.GetUserAsync(id);
}
}
RESTween creates an implementation of IUserApi at runtime. Calling GetUserAsync(42) builds and sends:
GET https://api.example.com/users/42
Supported Interface Methods
RESTween client proxies support asynchronous methods:
Task DoWorkAsync();
Task<T> GetValueAsync();
Synchronous interface methods are not supported by the client proxy and will throw NotSupportedException.
Request-Building Rules
RESTween builds requests through a pipeline. The default priority is:
- Read method metadata from
[Get],[Post],[Put],[Delete],[Multipart], and[Headers]. - Apply method-level headers from
[Headers("Name: Value")]. - Bind each parameter with registered binders.
- Replace route placeholders.
- Build the query string.
- Create the final
HttpRequestMessage. - Attach JSON body, multipart content, and headers.
Explicit parameter attributes always win:
[Route] -> route placeholder value
[Query] -> query string value
[Header] -> request header value
[Body] -> JSON request body
If a parameter has no RESTween binding attribute, the default pipeline uses these conventions:
- If the parameter name appears in the URL template as
{name}, it becomes a route value. - If it is a simple type, enum, string,
Guid,DateTime, or a supported collection, it becomes a query value. - If it is a complex object on
GETorDELETE, its public properties become query values. - If it is a complex object on
POSTorPUT, it becomes the JSON body. GETandDELETErequests cannot contain a body.- A request can have only one body parameter.
- Null query values are skipped.
- Null route values throw a
RestweenRequestBuildException. - Duplicate query keys throw unless
[Query(collectionFormat: CollectionFormat.Multi)]is used.
Route Parameters
Route values replace placeholders in the URL template:
[Get("/users/{id}/orders/{orderId}")]
Task<OrderDto> GetOrderAsync([Route] int id, [Route] Guid orderId);
The [Route] name can be inferred from the parameter name or set explicitly:
[Get("/users/{userId}")]
Task<UserDto> GetUserAsync([Route("userId")] int id);
Route values are URL-encoded.
Query Parameters
Scalar query parameters:
[Get("/users")]
Task<IReadOnlyList<UserDto>> GetUsersAsync([Query("active")] bool isActive);
Complex query DTOs:
public sealed class UserSearchQuery
{
public string? Term { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
}
[Get("/users")]
Task<IReadOnlyList<UserDto>> SearchAsync([Query] UserSearchQuery query);
[JsonPropertyName] is respected when complex query DTO properties are expanded:
public sealed class UserSearchQuery
{
[JsonPropertyName("page_size")]
public int PageSize { get; set; }
}
Collections are supported. With CollectionFormat.Multi, RESTween emits repeated name[] keys:
[Get("/users")]
Task<IReadOnlyList<UserDto>> GetUsersAsync(
[Query("role", CollectionFormat.Multi)] string[] roles);
Example output:
/users?role[]=admin&role[]=manager
Headers
Use [Headers] for static method-level headers:
[Headers("X-Client: mobile", "Accept-Language: en-US")]
[Get("/profile")]
Task<ProfileDto> GetProfileAsync();
Use [Header] for dynamic parameter-level headers:
[Get("/profile")]
Task<ProfileDto> GetProfileAsync([Header("Authorization")] string bearerToken);
Header values are formatted using the configured IRestweenValueFormatter.
Body Requests
Use [Body] for JSON bodies:
[Post("/users")]
Task<UserDto> CreateUserAsync([Body] CreateUserDto dto);
For POST and PUT, complex parameters without explicit attributes are also treated as body parameters:
[Put("/users/{id}")]
Task<UserDto> UpdateUserAsync([Route] int id, UpdateUserDto dto);
Only one body parameter is allowed.
Multipart Requests
Add [Multipart] to build multipart/form-data requests:
[Multipart]
[Post("/files")]
Task<FileResultDto> UploadAsync(
[Header("X-Trace-Id")] string traceId,
Stream file,
string description,
UploadMetadata metadata);
The default multipart binder supports:
Streamas a file part.byte[]as a file part.FileInfoas a file part with the original file name.- Simple values as string parts.
- Complex objects as JSON parts.
Value Formatting
Route, query, and header values are formatted consistently:
boolbecomestrueorfalse.- Enums use
[EnumMember(Value = "...")]when available. DateTimeroute/query values use invariant ISO-style formatting.DateTimeheader values use RFC1123 formatting.- Numbers and other
IFormattablevalues use invariant culture.
Custom Request Handling
IRequestHandler controls how requests are sent and how responses are processed:
using RESTween.Handlers;
public sealed class AuthenticatedRequestHandler : IRequestHandler
{
private readonly ITokenProvider _tokens;
public AuthenticatedRequestHandler(ITokenProvider tokens)
{
_tokens = tokens;
}
public async Task<T> HandleRequestAsync<T>(RequestContext context, HttpClient httpClient)
{
var token = await _tokens.GetTokenAsync();
context.Request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.SendAsync(context.Request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>() ?? default!;
}
public async Task HandleRequestAsync(RequestContext context, HttpClient httpClient)
{
var token = await _tokens.GetTokenAsync();
context.Request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.SendAsync(context.Request);
response.EnsureSuccessStatusCode();
}
}
Register the handler before the API client:
services.AddScoped<IRequestHandler, AuthenticatedRequestHandler>();
services.AddApiClient<IUserApi>(new Uri("https://api.example.com"));
The request handler receives a RequestContext, which contains:
Request: the generatedHttpRequestMessage.Attributes: the attributes found on the API method, useful for custom behavior such as caching, rate limits, retries, or logging.
Client-only metadata attributes can be read from RequestContext:
var cache = context.GetAttribute<CacheAttribute>();
var rateLimit = context.GetAttribute<RateLimitAttribute>();
Extending Request Building
The client request builder is split into public services under RESTween.Building:
IRestweenRequestBuilder: builds the finalHttpRequestMessage.IRestweenParameterBinder: binds one method parameter into route, query, header, body, or multipart state.IRestweenContentSerializer: serializes JSON bodies and multipart JSON parts.IRestweenValueFormatter: formats route, query, and header values.
AddApiClient<T> automatically registers default implementations using TryAdd, so user registrations can replace the serializer, formatter, or request builder.
Example custom serializer:
public sealed class MyJsonSerializer : IRestweenContentSerializer
{
private readonly JsonSerializerOptions _options = new(JsonSerializerDefaults.Web);
public HttpContent SerializeJsonContent(object value)
{
return new StringContent(
JsonSerializer.Serialize(value, _options),
Encoding.UTF8,
"application/json");
}
public HttpContent SerializeMultipartJsonContent(object value)
{
return SerializeJsonContent(value);
}
}
Register it:
services.AddSingleton<IRestweenContentSerializer, MyJsonSerializer>();
services.AddApiClient<IUserApi>(new Uri("https://api.example.com"));
Example custom binder:
public sealed class TenantParameterBinder : IRestweenParameterBinder
{
public bool TryBind(RestweenParameterContext context)
{
if (context.Parameter.Name != "tenantId")
{
return false;
}
if (context.Value != null)
{
context.State.AddHeader("X-Tenant-Id", context.Value.ToString()!);
}
return true;
}
}
Register additional binders with AddSingleton<IRestweenParameterBinder, TenantParameterBinder>().
Factory Usage Without DI
You can create a client manually:
var httpClient = new HttpClient
{
BaseAddress = new Uri("https://api.example.com")
};
var handler = new DefaultRequestHandler();
var api = ApiClientFactory.CreateClient<IUserApi>(httpClient, handler);
You can also pass a custom IRestweenRequestBuilder:
var api = ApiClientFactory.CreateClient<IUserApi>(
httpClient,
handler,
customRequestBuilder);
Related Packages
RESTween.Core: shared attributes and API contract primitives.RESTween.Server: source generator that creates ASP.NET Core controllers from RESTween interfaces.
Use RESTween when your application needs to call HTTP APIs through strongly typed interfaces.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 is compatible. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net5.0
- Castle.Core.AsyncInterceptor (>= 2.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.1)
- RESTween.Core (>= 1.7.10)
-
net6.0
- Castle.Core.AsyncInterceptor (>= 2.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.1)
- RESTween.Core (>= 1.7.10)
-
net7.0
- Castle.Core.AsyncInterceptor (>= 2.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.1)
- RESTween.Core (>= 1.7.10)
-
net8.0
- Castle.Core.AsyncInterceptor (>= 2.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.1)
- RESTween.Core (>= 1.7.10)
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 |
|---|---|---|
| 1.7.10 | 105 | 5/7/2026 |
| 1.7.9 | 88 | 5/7/2026 |
| 1.6.8 | 88 | 5/7/2026 |
| 1.6.7 | 91 | 5/6/2026 |
| 1.6.6 | 89 | 5/6/2026 |
| 1.6.5 | 94 | 5/3/2026 |
| 1.6.4 | 107 | 5/3/2026 |
| 1.6.3 | 99 | 4/30/2026 |
| 1.6.2 | 94 | 4/30/2026 |
| 1.6.1 | 89 | 4/30/2026 |
| 1.5.2 | 157 | 4/7/2026 |
| 1.5.1 | 233 | 3/11/2026 |
| 1.5.0 | 379 | 2/16/2026 |
| 1.4.2 | 105 | 2/16/2026 |
| 1.4.1 | 109 | 2/16/2026 |
| 1.4.0 | 103 | 2/16/2026 |
| 1.3.1 | 304 | 12/25/2025 |
| 1.3.0 | 193 | 12/24/2025 |
| 1.2.4 | 194 | 12/24/2025 |
| 1.2.3 | 207 | 12/24/2025 |