Atc.Rest.Client
2.0.12
dotnet add package Atc.Rest.Client --version 2.0.12
NuGet\Install-Package Atc.Rest.Client -Version 2.0.12
<PackageReference Include="Atc.Rest.Client" Version="2.0.12" />
<PackageVersion Include="Atc.Rest.Client" Version="2.0.12" />
<PackageReference Include="Atc.Rest.Client" />
paket add Atc.Rest.Client --version 2.0.12
#r "nuget: Atc.Rest.Client, 2.0.12"
#:package Atc.Rest.Client@2.0.12
#addin nuget:?package=Atc.Rest.Client&version=2.0.12
#tool nuget:?package=Atc.Rest.Client&version=2.0.12
ATC.Net REST Client
A lightweight and flexible REST client library for .NET, providing a clean abstraction over HttpClient with built-in support for request building, response handling, and dependency injection.
Table of Contents
- ATC.Net REST Client
Features
- Fluent HTTP Request Building: Build complex HTTP requests with a clean, chainable API
- Typed Response Handling: Strongly-typed success and error responses
- Flexible Configuration: Multiple ways to configure HTTP clients
- Dependency Injection Ready: Seamless integration with Microsoft.Extensions.DependencyInjection
- Path Templates: Support for URI templates with parameter replacement
- Query & Header Parameters: Easy addition of query strings and headers
- Custom Serialization: Pluggable contract serialization (defaults to JSON)
- Response Processing: Built-in support for success/error response handling
- Multipart Form Data: File upload support with Stream-based API
- Binary Responses: Handle file downloads with byte[] or Stream responses
- Streaming Support: IAsyncEnumerable streaming for large datasets
- HTTP Completion Options: Control response buffering for streaming scenarios
Getting Started
Installation
Install the package via NuGet:
dotnet add package Atc.Rest.Client
Service Registration
There are multiple ways to register services with dependency injection:
Approach 1: Core Services Only (No HttpClient Configuration)
Use this approach when you configure HttpClient separately or use source-generated endpoints:
using Atc.Rest.Client.Options;
// Registers IHttpMessageFactory and IContractSerializer (default JSON) only
services.AddAtcRestClientCore();
// Or with a custom serializer
services.AddAtcRestClientCore(myCustomSerializer);
Approach 2: Direct Configuration
Use this approach when you have straightforward configuration needs:
using Atc.Rest.Client.Options;
services.AddAtcRestClient(
clientName: "MyApiClient",
baseAddress: new Uri("https://api.example.com"),
timeout: TimeSpan.FromSeconds(30));
Approach 3: Custom Options Type
Use this approach when you need to register the options as a singleton for later retrieval:
// Define a custom options class
public sealed class MyApiClientOptions : AtcRestClientOptions
{
public string ApiKey { get; set; } = string.Empty;
}
// Register with custom options
var options = new MyApiClientOptions
{
BaseAddress = new Uri("https://api.example.com"),
Timeout = TimeSpan.FromSeconds(30),
ApiKey = "your-api-key"
};
services.AddAtcRestClient(
clientName: "MyApiClient",
options: options);
Creating an Endpoint
Create an endpoint class that uses IHttpMessageFactory to build and send requests:
public interface IUsersEndpoint
{
Task<EndpointResponse<User>> GetUserAsync(int userId, CancellationToken cancellationToken = default);
}
public class UsersEndpoint : IUsersEndpoint
{
private readonly IHttpClientFactory clientFactory;
private readonly IHttpMessageFactory messageFactory;
public UsersEndpoint(
IHttpClientFactory clientFactory,
IHttpMessageFactory messageFactory)
{
this.clientFactory = clientFactory;
this.messageFactory = messageFactory;
}
public async Task<EndpointResponse<User>> GetUserAsync(
int userId,
CancellationToken cancellationToken = default)
{
var client = clientFactory.CreateClient("MyApiClient");
var requestBuilder = messageFactory.FromTemplate("/api/users/{userId}");
requestBuilder.WithPathParameter("userId", userId);
using var request = requestBuilder.Build(HttpMethod.Get);
using var response = await client.SendAsync(request, cancellationToken);
var responseBuilder = messageFactory.FromResponse(response);
responseBuilder.AddSuccessResponse<User>(HttpStatusCode.OK);
responseBuilder.AddErrorResponse<ProblemDetails>(HttpStatusCode.NotFound);
return await responseBuilder.BuildResponseAsync<User>(cancellationToken);
}
}
Register the endpoint:
services.AddSingleton<IUsersEndpoint, UsersEndpoint>();
Usage Examples
Simple GET Request
var requestBuilder = messageFactory.FromTemplate("/api/products");
using var request = requestBuilder.Build(HttpMethod.Get);
using var response = await client.SendAsync(request, cancellationToken);
var responseBuilder = messageFactory.FromResponse(response);
responseBuilder.AddSuccessResponse<List<Product>>(HttpStatusCode.OK);
var result = await responseBuilder.BuildResponseAsync<List<Product>>(cancellationToken);
if (result.IsSuccess)
{
var products = result.OkContent;
// Process products
}
POST Request with Body
var newUser = new CreateUserRequest
{
Name = "John Doe",
Email = "john@example.com"
};
var requestBuilder = messageFactory.FromTemplate("/api/users");
requestBuilder.WithBody(newUser);
using var request = requestBuilder.Build(HttpMethod.Post);
using var response = await client.SendAsync(request, cancellationToken);
var responseBuilder = messageFactory.FromResponse(response);
responseBuilder.AddSuccessResponse<User>(HttpStatusCode.Created);
responseBuilder.AddErrorResponse<ValidationProblemDetails>(HttpStatusCode.BadRequest);
var result = await responseBuilder.BuildResponseAsync<User>(cancellationToken);
Using Path and Query Parameters
var requestBuilder = messageFactory.FromTemplate("/api/users/{userId}/posts");
requestBuilder.WithPathParameter("userId", 123);
requestBuilder.WithQueryParameter("pageSize", 10);
requestBuilder.WithQueryParameter("page", 1);
requestBuilder.WithQueryParameter("orderBy", "createdDate");
using var request = requestBuilder.Build(HttpMethod.Get);
// Results in: GET /api/users/123/posts?pageSize=10&page=1&orderBy=createdDate
File Upload (Multipart Form Data)
Upload files using the Stream-based multipart form data API:
// Single file upload
await using var fileStream = File.OpenRead("document.pdf");
var requestBuilder = messageFactory.FromTemplate("/api/files/upload");
requestBuilder.WithFile(fileStream, "file", "document.pdf", "application/pdf");
requestBuilder.WithFormField("description", "My document");
using var request = requestBuilder.Build(HttpMethod.Post);
using var response = await client.SendAsync(request, cancellationToken);
Upload multiple files:
await using var file1 = File.OpenRead("image1.png");
await using var file2 = File.OpenRead("image2.png");
var files = new List<(Stream, string, string, string?)>
{
(file1, "images", "image1.png", "image/png"),
(file2, "images", "image2.png", "image/png")
};
var requestBuilder = messageFactory.FromTemplate("/api/files/upload-multiple");
requestBuilder.WithFiles(files);
using var request = requestBuilder.Build(HttpMethod.Post);
File Download (Binary Response)
Download files as byte arrays or streams:
var requestBuilder = messageFactory.FromTemplate("/api/files/{fileId}");
requestBuilder.WithPathParameter("fileId", "123");
using var request = requestBuilder.Build(HttpMethod.Get);
using var response = await client.SendAsync(request, cancellationToken);
var responseBuilder = messageFactory.FromResponse(response);
// Option 1: Get as byte array
var binaryResponse = await responseBuilder.BuildBinaryResponseAsync(cancellationToken);
if (binaryResponse.IsSuccess)
{
var content = binaryResponse.Content;
var fileName = binaryResponse.FileName;
var contentType = binaryResponse.ContentType;
// Save or process the file...
}
// Option 2: Get as stream (for large files)
var streamResponse = await responseBuilder.BuildStreamBinaryResponseAsync(cancellationToken);
if (streamResponse.IsSuccess)
{
await using var contentStream = streamResponse.ContentStream;
await using var fileStream = File.Create(streamResponse.FileName ?? "download.bin");
await contentStream!.CopyToAsync(fileStream, cancellationToken);
}
Streaming Responses (IAsyncEnumerable)
Stream large datasets efficiently using IAsyncEnumerable:
var requestBuilder = messageFactory.FromTemplate("/api/data/stream");
// Set HttpCompletionOption for streaming (don't buffer the entire response)
requestBuilder.WithHttpCompletionOption(HttpCompletionOption.ResponseHeadersRead);
using var request = requestBuilder.Build(HttpMethod.Get);
using var response = await client.SendAsync(
request,
requestBuilder.HttpCompletionOption, // Use the configured option
cancellationToken);
var responseBuilder = messageFactory.FromResponse(response);
// Stream items as they arrive
await foreach (var item in responseBuilder.BuildStreamingResponseAsync<DataItem>(cancellationToken))
{
if (item is not null)
{
Console.WriteLine($"Received: {item.Name}");
}
}
Handling Responses
Success and Error Response Handling
var responseBuilder = messageFactory.FromResponse(response);
responseBuilder.AddSuccessResponse<User>(HttpStatusCode.OK);
responseBuilder.AddErrorResponse<ProblemDetails>(HttpStatusCode.BadRequest);
responseBuilder.AddErrorResponse<ProblemDetails>(HttpStatusCode.NotFound);
var result = await responseBuilder.BuildResponseAsync<User, ProblemDetails>(cancellationToken);
if (result.IsOk)
{
var user = result.OkContent;
Console.WriteLine($"Success: {user.Name}");
}
else if (result.IsBadRequest)
{
var problem = result.BadRequestContent;
Console.WriteLine($"Validation Error: {problem.Detail}");
}
else if (result.IsNotFound)
{
Console.WriteLine("User not found");
}
Custom Response Processing
var responseBuilder = messageFactory.FromResponse(response);
responseBuilder.AddSuccessResponse<User>(HttpStatusCode.OK);
var result = await responseBuilder.BuildResponseAsync(
response => new CustomResult
{
Success = response.IsSuccess,
StatusCode = response.StatusCode,
User = response.ContentObject as User
},
cancellationToken);
Best Practices
Choosing Between Overloads
| Scenario | Recommended Approach |
|---|---|
| Simple HTTP client with just base URL and timeout | Non-generic overload (AddAtcRestClient(string, Uri, TimeSpan)) |
| Additional configuration properties needed | Generic overload with custom options type |
Multiple Client Registration
When registering multiple HTTP clients, consider using a consistent naming convention:
// Good: Clear, distinct names
services.AddAtcRestClient("Users-API", new Uri("https://users.api.com"), TimeSpan.FromSeconds(30));
services.AddAtcRestClient("Orders-API", new Uri("https://orders.api.com"), TimeSpan.FromSeconds(60));
services.AddAtcRestClient("Payments-API", new Uri("https://payments.api.com"), TimeSpan.FromSeconds(45));
API Reference
Core Types
AddAtcRestClientCore Extension Method
Registers core services (IHttpMessageFactory and IContractSerializer) without HttpClient configuration:
IServiceCollection AddAtcRestClientCore(
this IServiceCollection services,
IContractSerializer? contractSerializer = null)
AddAtcRestClient Extension Methods (Internal)
These methods are used by source-generated code and are hidden from IntelliSense:
// With HttpClient configuration
IServiceCollection AddAtcRestClient(
this IServiceCollection services,
string clientName,
Uri baseAddress,
TimeSpan timeout,
Action<IHttpClientBuilder>? httpClientBuilder = null,
IContractSerializer? contractSerializer = null)
// Generic overload for typed options
IServiceCollection AddAtcRestClient<TOptions>(
this IServiceCollection services,
string clientName,
TOptions options,
Action<IHttpClientBuilder>? httpClientBuilder = null,
IContractSerializer? contractSerializer = null)
where TOptions : AtcRestClientOptions, new()
AtcRestClientOptions
public class AtcRestClientOptions
{
public virtual Uri? BaseAddress { get; set; }
public virtual TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
}
IHttpMessageFactory
public interface IHttpMessageFactory
{
IMessageRequestBuilder FromTemplate(string pathTemplate);
IMessageResponseBuilder FromResponse(HttpResponseMessage? response);
}
IMessageRequestBuilder
public interface IMessageRequestBuilder
{
IMessageRequestBuilder WithPathParameter(string name, object? value);
IMessageRequestBuilder WithQueryParameter(string name, object? value);
IMessageRequestBuilder WithHeaderParameter(string name, object? value);
IMessageRequestBuilder WithBody<TBody>(TBody body);
HttpRequestMessage Build(HttpMethod method);
// HTTP completion option for streaming
IMessageRequestBuilder WithHttpCompletionOption(HttpCompletionOption completionOption);
HttpCompletionOption HttpCompletionOption { get; }
// Multipart form data support
IMessageRequestBuilder WithFile(Stream stream, string name, string fileName, string? contentType = null);
IMessageRequestBuilder WithFiles(IEnumerable<(Stream Stream, string Name, string FileName, string? ContentType)> files);
IMessageRequestBuilder WithFormField(string name, string value);
}
IMessageResponseBuilder
public interface IMessageResponseBuilder
{
IMessageResponseBuilder AddSuccessResponse(HttpStatusCode statusCode);
IMessageResponseBuilder AddSuccessResponse<TResponseContent>(HttpStatusCode statusCode);
IMessageResponseBuilder AddErrorResponse(HttpStatusCode statusCode);
IMessageResponseBuilder AddErrorResponse<TResponseContent>(HttpStatusCode statusCode);
Task<TResult> BuildResponseAsync<TResult>(
Func<EndpointResponse, TResult> factory,
CancellationToken cancellationToken);
Task<EndpointResponse<TSuccessContent>> BuildResponseAsync<TSuccessContent>(
CancellationToken cancellationToken)
where TSuccessContent : class;
Task<EndpointResponse<TSuccessContent, TErrorContent>> BuildResponseAsync<TSuccessContent, TErrorContent>(
CancellationToken cancellationToken)
where TSuccessContent : class
where TErrorContent : class;
// Binary response support
Task<BinaryEndpointResponse> BuildBinaryResponseAsync(CancellationToken cancellationToken);
Task<StreamBinaryEndpointResponse> BuildStreamBinaryResponseAsync(CancellationToken cancellationToken);
// Streaming support
IAsyncEnumerable<T?> BuildStreamingResponseAsync<T>(CancellationToken cancellationToken = default);
}
EndpointResponse
public class EndpointResponse : IEndpointResponse
{
public bool IsSuccess { get; }
public HttpStatusCode StatusCode { get; }
public string Content { get; }
public object? ContentObject { get; }
public IReadOnlyDictionary<string, IEnumerable<string>> Headers { get; }
protected InvalidOperationException InvalidContentAccessException<TExpected>(
HttpStatusCode expectedStatusCode,
string propertyName);
}
// Generic variants available:
// - EndpointResponse<TSuccess>
// - EndpointResponse<TSuccess, TError>
BinaryEndpointResponse
public class BinaryEndpointResponse : IBinaryEndpointResponse
{
public bool IsSuccess { get; }
public bool IsOk { get; } // True if StatusCode == 200
public HttpStatusCode StatusCode { get; }
public byte[]? Content { get; }
public string? ContentType { get; }
public string? FileName { get; }
public long? ContentLength { get; }
protected InvalidOperationException InvalidContentAccessException(
HttpStatusCode expectedStatusCode,
string propertyName);
}
StreamBinaryEndpointResponse
public class StreamBinaryEndpointResponse : IStreamBinaryEndpointResponse
{
public bool IsSuccess { get; }
public bool IsOk { get; } // True if StatusCode == 200
public HttpStatusCode StatusCode { get; }
public Stream? ContentStream { get; }
public string? ContentType { get; }
public string? FileName { get; }
public long? ContentLength { get; }
public void Dispose();
protected InvalidOperationException InvalidContentAccessException(
HttpStatusCode expectedStatusCode,
string propertyName);
}
How to Contribute
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. 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 was computed. 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 was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.AspNetCore.Http (>= 2.3.0)
- Microsoft.Bcl.AsyncInterfaces (>= 9.0.10)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.10)
- Microsoft.Extensions.Http (>= 9.0.10)
- Microsoft.Extensions.Options (>= 9.0.10)
- System.Text.Json (>= 9.0.10)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Atc.Rest.Client:
| Package | Downloads |
|---|---|
|
Atc.Rest.ApiGenerator
Atc.Rest.ApiGenerator is a WebApi C# code generator using a OpenApi 3.0.x specification YAML file. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.12 | 276 | 12/7/2025 |
| 2.0.11 | 201 | 12/4/2025 |
| 2.0.10 | 201 | 12/4/2025 |
| 2.0.1 | 426 | 11/10/2025 |
| 1.0.84 | 3,355 | 12/17/2024 |
| 1.0.79 | 4,172 | 7/14/2024 |
| 1.0.74 | 300 | 6/28/2024 |
| 1.0.69 | 276 | 5/8/2024 |
| 1.0.55 | 3,871 | 7/5/2023 |
| 1.0.53 | 6,836 | 6/9/2023 |
| 1.0.48 | 12,910 | 2/2/2023 |
| 1.0.36 | 14,812 | 1/24/2022 |
| 1.0.31 | 4,523 | 11/7/2021 |
| 1.0.29 | 11,254 | 2/3/2021 |
| 1.0.25 | 592 | 2/2/2021 |