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
                    
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="Atc.Rest.Client" Version="2.0.12" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Atc.Rest.Client" Version="2.0.12" />
                    
Directory.Packages.props
<PackageReference Include="Atc.Rest.Client" />
                    
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 Atc.Rest.Client --version 2.0.12
                    
#r "nuget: Atc.Rest.Client, 2.0.12"
                    
#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 Atc.Rest.Client@2.0.12
                    
#: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=Atc.Rest.Client&version=2.0.12
                    
Install as a Cake Addin
#tool nuget:?package=Atc.Rest.Client&version=2.0.12
                    
Install as a Cake Tool

NuGet Version

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

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

Contribution Guidelines

Coding Guidelines

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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