ARSoft.RestApiClient 3.2.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package ARSoft.RestApiClient --version 3.2.0
                    
NuGet\Install-Package ARSoft.RestApiClient -Version 3.2.0
                    
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="ARSoft.RestApiClient" Version="3.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ARSoft.RestApiClient" Version="3.2.0" />
                    
Directory.Packages.props
<PackageReference Include="ARSoft.RestApiClient" />
                    
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 ARSoft.RestApiClient --version 3.2.0
                    
#r "nuget: ARSoft.RestApiClient, 3.2.0"
                    
#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 ARSoft.RestApiClient@3.2.0
                    
#: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=ARSoft.RestApiClient&version=3.2.0
                    
Install as a Cake Addin
#tool nuget:?package=ARSoft.RestApiClient&version=3.2.0
                    
Install as a Cake Tool

ApiClient Library for .NET Core and .NET 5+ applications

A modern, resilient HTTP API client library for .NET Core and .NET 5+ applications, providing clean, async HTTP operations with built-in retry policies using Polly v8, comprehensive error handling, and structured logging support. Optimized for high-performance API consumption in cross-platform and cloud-native environments.

๐ŸŽฏ Features

  • Modern .NET Support: Built for .NET 6+ with nullable reference types
  • Polly v8 Integration: Built-in resilience pipelines with exponential backoff and jitter
  • Structured Logging: ILogger integration for comprehensive request/response logging
  • Flexible Authentication: Bearer, Basic, and API Key authentication support
  • Custom Headers Support: Add dynamic headers per request or globally
  • Thread-Safe Design: Internal shared HttpClient with proper concurrency handling
  • Clean Architecture: SOLID principles with dependency injection support
  • Comprehensive Error Handling: Detailed error responses with HTTP status codes
  • Async/Await: Full asynchronous operation support with cancellation tokens
  • Configuration Validation: Prevents unsafe configuration changes after initialization

๐Ÿš€ Quick Start

Installation

Add to your project via NuGet Package Manager or .csproj:

<PackageReference Include="Polly.Core" Version="8.6.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.8" />

๐Ÿ“– Basic Usage

Simple Setup

// Create client with base address
var apiClient = new ApiClient(
    baseAddress: new Uri("https://api.example.com"),
    timeout: TimeSpan.FromSeconds(30));

// Make requests
var response = await apiClient.GetAsync<User>(new Uri("users/1", UriKind.Relative));
if (response.Success)
{
    Console.WriteLine($"User: {response.Data.Name}");
}

Dependency Injection Setup

// Program.cs - ASP.NET Core
builder.Services.AddSingleton<IApiClient>(sp =>
{
    var logger = sp.GetRequiredService<ILogger<ApiClient>>();
    var client = new ApiClient(
        baseAddress: new Uri("https://api.example.com"),
        timeout: TimeSpan.FromSeconds(60),
        logger: logger);
    
    // Add default headers before first use
    client.AddDefaultRequestHeader("User-Agent", "MyApp/1.0");
    client.AddDefaultRequestHeader("Accept-Language", "en-US");
    
    return client;
});

๐Ÿ“‹ Detailed Examples

1. GET Request with Authentication

public class UserService
{
    private readonly IApiClient _apiClient;
    private readonly ILogger<UserService> _logger;

    public UserService(IApiClient apiClient, ILogger<UserService> logger)
    {
        _apiClient = apiClient;
        _logger = logger;
    }

    public async Task<List<User>> GetUsersAsync(string bearerToken, CancellationToken cancellationToken = default)
    {
        var response = await _apiClient.GetAsync<List<User>>(
            new Uri("https://api.example.com/users"),
            authToken: bearerToken,
            authType: AuthType.Bearer,
            cancellationToken: cancellationToken);

        if (response.Success)
        {
            _logger.LogInformation("Retrieved {Count} users", response.Data?.Count ?? 0);
            return response.Data ?? new List<User>();
        }

        _logger.LogError("Failed to retrieve users: {Error}", response.ErrorMessage);
        throw new HttpRequestException($"Failed to get users: {response.ErrorMessage}");
    }
}

2. Using Custom Headers Per Request

public class ApiService
{
    private readonly IApiClient _apiClient;

    public ApiService(IApiClient apiClient)
    {
        _apiClient = apiClient;
    }

    // Example 1: Request-specific tracing header
    public async Task<Order> GetOrderAsync(string orderId, string traceId)
    {
        var customHeaders = new Dictionary<string, string>
        {
            { "X-Trace-Id", traceId },
            { "X-Request-Source", "Mobile-App" }
        };

        var response = await _apiClient.GetAsync<Order>(
            new Uri($"https://api.orders.com/orders/{orderId}"),
            customHeaders: customHeaders);

        return response.Data!;
    }

    // Example 2: API versioning with custom header
    public async Task<Product> GetProductV2Async(int productId)
    {
        var headers = new Dictionary<string, string>
        {
            { "X-API-Version", "2.0" },
            { "X-Feature-Flags", "new-pricing,bulk-discount" }
        };

        var response = await _apiClient.GetAsync<Product>(
            new Uri($"https://api.store.com/products/{productId}"),
            customHeaders: headers);

        return response.Data!;
    }

    // Example 3: Combining authentication with custom headers
    public async Task<Report> GenerateReportAsync(string apiKey, string reportType, string format)
    {
        var customHeaders = new Dictionary<string, string>
        {
            { "X-Report-Type", reportType },
            { "X-Output-Format", format },
            { "X-Request-Priority", "high" }
        };

        var response = await _apiClient.GetAsync<Report>(
            new Uri("https://api.analytics.com/reports/generate"),
            authToken: apiKey,
            authType: AuthType.ApiKey,
            customHeaders: customHeaders);

        return response.Data!;
    }
}

public record Order(string Id, decimal Total, DateTime CreatedAt);
public record Product(int Id, string Name, decimal Price);
public record Report(string Id, byte[] Data, string Format);

3. POST Request with Custom Headers

public class PaymentService
{
    private readonly IApiClient _apiClient;

    public PaymentService(IApiClient apiClient)
    {
        _apiClient = apiClient;
    }

    public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest payment, string idempotencyKey)
    {
        // Idempotency key prevents duplicate charges
        var customHeaders = new Dictionary<string, string>
        {
            { "X-Idempotency-Key", idempotencyKey },
            { "X-Client-Version", "1.2.3" },
            { "X-Device-Id", Environment.MachineName }
        };

        var response = await _apiClient.PostAsync<PaymentRequest, PaymentResult>(
            new Uri("https://api.payments.com/v1/charge"),
            payload: payment,
            authToken: "sk_live_xyz123",
            authType: AuthType.ApiKey,
            customHeaders: customHeaders);

        if (!response.Success)
        {
            throw new PaymentException($"Payment failed: {response.ErrorMessage}");
        }

        return response.Data!;
    }
}

public record PaymentRequest(decimal Amount, string Currency, string CardToken);
public record PaymentResult(string TransactionId, string Status, DateTime ProcessedAt);
public class PaymentException : Exception 
{ 
    public PaymentException(string message) : base(message) { } 
}

4. Advanced Configuration with Default and Custom Headers

public class EnterpriseApiClient
{
    private readonly IApiClient _apiClient;

    public EnterpriseApiClient(ILogger<ApiClient> logger, string environment)
    {
        _apiClient = new ApiClient(
            baseAddress: new Uri("https://api.enterprise.com"),
            timeout: TimeSpan.FromSeconds(120),
            logger: logger);

        // Set default headers that apply to ALL requests
        _apiClient.AddDefaultRequestHeader("X-Environment", environment);
        _apiClient.AddDefaultRequestHeader("X-Client-Type", "EnterpriseClient");
        _apiClient.AddDefaultRequestHeader("User-Agent", "EnterpriseApp/2.0");
    }

    public async Task<Customer> CreateCustomerAsync(
        CreateCustomerRequest request, 
        string apiKey, 
        string correlationId)
    {
        // These custom headers are ONLY for this specific request
        var requestHeaders = new Dictionary<string, string>
        {
            { "X-Correlation-Id", correlationId },
            { "X-Operation", "CreateCustomer" },
            { "X-Request-Timestamp", DateTime.UtcNow.ToString("o") }
        };

        var response = await _apiClient.PostAsync<CreateCustomerRequest, Customer>(
            new Uri("customers", UriKind.Relative),
            payload: request,
            authToken: apiKey,
            authType: AuthType.ApiKey,
            customHeaders: requestHeaders);

        return response.Data!;
    }

    // Headers can be built dynamically based on context
    public async Task<List<Transaction>> GetTransactionsAsync(
        string accountId, 
        string userId,
        TransactionQueryOptions options)
    {
        var headers = new Dictionary<string, string>
        {
            { "X-User-Id", userId },
            { "X-Account-Id", accountId }
        };

        // Add conditional headers based on options
        if (options.IncludeMetadata)
        {
            headers["X-Include-Metadata"] = "true";
        }

        if (options.PageSize.HasValue)
        {
            headers["X-Page-Size"] = options.PageSize.Value.ToString();
        }

        var response = await _apiClient.GetAsync<List<Transaction>>(
            new Uri($"accounts/{accountId}/transactions", UriKind.Relative),
            customHeaders: headers);

        return response.Data ?? new List<Transaction>();
    }
}

public record CreateCustomerRequest(string Name, string Email);
public record Customer(string Id, string Name, string Email);
public record Transaction(string Id, decimal Amount, DateTime Date);
public record TransactionQueryOptions(bool IncludeMetadata, int? PageSize);

5. Multi-Tenant API with Custom Headers

public class MultiTenantApiService
{
    private readonly IApiClient _apiClient;
    private readonly ILogger<MultiTenantApiService> _logger;

    public MultiTenantApiService(IApiClient apiClient, ILogger<MultiTenantApiService> logger)
    {
        _apiClient = apiClient;
        _logger = logger;
    }

    public async Task<TenantData> GetTenantDataAsync(
        string tenantId, 
        string userId, 
        string accessToken)
    {
        // Multi-tenant headers
        var headers = new Dictionary<string, string>
        {
            { "X-Tenant-Id", tenantId },
            { "X-User-Id", userId },
            { "X-Data-Region", "us-east-1" },
            { "X-Request-Context", $"tenant={tenantId},user={userId}" }
        };

        var response = await _apiClient.GetAsync<TenantData>(
            new Uri($"https://api.saas-platform.com/tenants/{tenantId}/data"),
            authToken: accessToken,
            authType: AuthType.Bearer,
            customHeaders: headers);

        if (response.Success)
        {
            _logger.LogInformation(
                "Retrieved data for tenant {TenantId} by user {UserId}", 
                tenantId, 
                userId);
            return response.Data!;
        }

        throw new UnauthorizedAccessException(
            $"Failed to access tenant data: {response.ErrorMessage}");
    }
}

public record TenantData(string TenantId, string Name, Dictionary<string, object> Settings);

6. Webhook Signature Verification with Custom Headers

public class WebhookClient
{
    private readonly IApiClient _apiClient;

    public WebhookClient(IApiClient apiClient)
    {
        _apiClient = apiClient;
    }

    public async Task<WebhookResponse> SendWebhookAsync(
        string webhookUrl, 
        WebhookPayload payload,
        string secret)
    {
        // Calculate signature
        var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
        var signature = CalculateSignature(payload, secret, timestamp);

        var headers = new Dictionary<string, string>
        {
            { "X-Webhook-Signature", signature },
            { "X-Webhook-Timestamp", timestamp },
            { "X-Webhook-Id", Guid.NewGuid().ToString() }
        };

        var response = await _apiClient.PostAsync<WebhookPayload, WebhookResponse>(
            new Uri(webhookUrl),
            payload: payload,
            customHeaders: headers);

        return response.Data!;
    }

    private string CalculateSignature(WebhookPayload payload, string secret, string timestamp)
    {
        // Implementation of HMAC-SHA256 signature
        var data = $"{timestamp}.{JsonSerializer.Serialize(payload)}";
        using var hmac = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(secret));
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
        return Convert.ToBase64String(hash);
    }
}

public record WebhookPayload(string Event, object Data);
public record WebhookResponse(bool Accepted, string Message);

7. Rate Limiting and Retry with Custom Headers

public class RateLimitedApiService
{
    private readonly IApiClient _apiClient;
    private readonly ILogger<RateLimitedApiService> _logger;

    public RateLimitedApiService(IApiClient apiClient, ILogger<RateLimitedApiService> logger)
    {
        _apiClient = apiClient;
        _logger = logger;
    }

    public async Task<SearchResults> SearchAsync(
        string query, 
        string apiKey,
        int priority = 1)
    {
        var headers = new Dictionary<string, string>
        {
            { "X-Rate-Limit-Tier", "premium" },
            { "X-Request-Priority", priority.ToString() },
            { "X-Request-Id", Guid.NewGuid().ToString() }
        };

        var response = await _apiClient.GetAsync<SearchResults>(
            new Uri($"https://api.search.com/search?q={Uri.EscapeDataString(query)}"),
            authToken: apiKey,
            authType: AuthType.ApiKey,
            customHeaders: headers);

        if (response.StatusCode == HttpStatusCode.TooManyRequests)
        {
            _logger.LogWarning("Rate limit exceeded for query: {Query}", query);
            // Response headers would contain rate limit info
            throw new RateLimitException("Rate limit exceeded");
        }

        return response.Data!;
    }
}

public record SearchResults(List<SearchResult> Results, int TotalCount);
public record SearchResult(string Id, string Title, string Url);
public class RateLimitException : Exception 
{ 
    public RateLimitException(string message) : base(message) { } 
}

๐Ÿ“ง Configuration Options

Authentication Types

// Bearer Token (JWT)
await apiClient.GetAsync<User>(url, authToken: "eyJhbGci...", authType: AuthType.Bearer);

// Basic Authentication (Base64 encoded username:password)
var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
await apiClient.GetAsync<User>(url, authToken: credentials, authType: AuthType.Basic);

// API Key (X-API-Key header)
await apiClient.GetAsync<User>(url, authToken: "your-secret-api-key", authType: AuthType.ApiKey);

// No authentication
await apiClient.GetAsync<User>(url);

Custom Headers Usage Patterns

// Pattern 1: Simple custom headers
var headers = new Dictionary<string, string>
{
    { "X-Custom-Header", "value" }
};
await apiClient.GetAsync<Data>(url, customHeaders: headers);

// Pattern 2: Multiple custom headers
var headers = new Dictionary<string, string>
{
    { "X-Trace-Id", Guid.NewGuid().ToString() },
    { "X-User-Agent", "MyApp/1.0" },
    { "X-Device-Type", "Mobile" }
};
await apiClient.PostAsync<Request, Response>(url, payload, customHeaders: headers);

// Pattern 3: Combining authentication and custom headers
await apiClient.GetAsync<User>(
    url, 
    authToken: "token123",
    authType: AuthType.Bearer,
    customHeaders: new Dictionary<string, string> 
    { 
        { "X-Request-Id", requestId } 
    });

// Pattern 4: Default headers + request-specific headers
var client = new ApiClient(new Uri("https://api.example.com"));
client.AddDefaultRequestHeader("X-Client-Id", "abc123"); // Applies to all requests

// This request will have BOTH the default header AND the custom header
await client.GetAsync<Data>(
    new Uri("data", UriKind.Relative),
    customHeaders: new Dictionary<string, string> { { "X-Request-Id", "xyz" } });

Custom JSON Serialization

var jsonOptions = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    PropertyNameCaseInsensitive = true,
    WriteIndented = false,
    Converters = { new JsonStringEnumConverter() }
};

var apiClient = new ApiClient(
    baseAddress: new Uri("https://api.example.com"),
    jsonOptions: jsonOptions);

Advanced Resilience Pipelines with Polly v8

// Custom retry pipeline with exponential backoff
var retryOptions = new RetryStrategyOptions<HttpResponseMessage>
{
    ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
        .Handle<HttpRequestException>()
        .Handle<TaskCanceledException>()
        .HandleResult(r => r.StatusCode == HttpStatusCode.TooManyRequests || 
                          (int)r.StatusCode >= 500),
    MaxRetryAttempts = 5,
    Delay = TimeSpan.FromSeconds(2),
    BackoffType = DelayBackoffType.Exponential,
    UseJitter = true,
    OnRetry = args =>
    {
        Console.WriteLine($"Retry {args.AttemptNumber} after {args.RetryDelay}");
        return ValueTask.CompletedTask;
    }
};

var retryPipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
    .AddRetry(retryOptions)
    .Build();

var apiClient = new ApiClient(
    baseAddress: new Uri("https://api.example.com"),
    retryPipeline: retryPipeline);

๐Ÿ›ก๏ธ Error Handling

Response Structure

public class ApiResponse<T>
{
    public bool Success { get; set; }           // Operation success status
    public T? Data { get; set; }               // Response data (if successful)
    public string? ErrorMessage { get; set; }  // Error description
    public string? ErrorData { get; set; }     // Raw error response
    public HttpStatusCode StatusCode { get; set; } // HTTP status code
}

Error Handling Patterns

var response = await apiClient.GetAsync<User>(userUrl);

// Pattern 1: Simple success check
if (response.Success)
{
    ProcessUser(response.Data!);
}
else
{
    _logger.LogError("API call failed: {Error}", response.ErrorMessage);
}

// Pattern 2: Status code specific handling
switch (response.StatusCode)
{
    case HttpStatusCode.OK:
        ProcessUser(response.Data!);
        break;
    case HttpStatusCode.NotFound:
        // Handle user not found
        break;
    case HttpStatusCode.Unauthorized:
        // Refresh token or redirect to login
        break;
    case HttpStatusCode.TooManyRequests:
        // Rate limit exceeded
        break;
    default:
        _logger.LogError("Unexpected error {StatusCode}: {Error}", 
            response.StatusCode, response.ErrorMessage);
        break;
}

// Pattern 3: Exception-based handling
public async Task<User> GetUserOrThrowAsync(int userId)
{
    var response = await apiClient.GetAsync<User>(
        new Uri($"https://api.example.com/users/{userId}"));
    
    return response.Success 
        ? response.Data! 
        : throw new HttpRequestException(
            $"Failed to get user {userId}: {response.ErrorMessage}");
}

๐Ÿ”’ Configuration Safety

Configuration Lock Mechanism

The ApiClient prevents configuration changes after the first request is sent to ensure thread-safety and predictable behavior:

var client = new ApiClient(new Uri("https://api.example.com"));

// โœ… OK - Before first request
client.AddDefaultRequestHeader("X-Custom", "value");
client.SetTimeout(TimeSpan.FromSeconds(30));

// Send first request
await client.GetAsync<Data>();

// โŒ THROWS ApiClientConfigurationException - After first request
try
{
    client.SetTimeout(TimeSpan.FromSeconds(60));
}
catch (ApiClientConfigurationException ex)
{
    Console.WriteLine($"Configuration error: {ex.Reason}");
    // Output: Configuration error: TimeoutModificationNotAllowed
}

ApiClientConfigurationException

public enum ApiClientConfigurationReason
{
    HeadersModificationNotAllowed,
    BaseAddressModificationNotAllowed,
    TimeoutModificationNotAllowed,
    ClientDisposed
}

// Example handling
try
{
    client.AddDefaultRequestHeader("X-New-Header", "value");
}
catch (ApiClientConfigurationException ex) when 
    (ex.Reason == ApiClientConfigurationReason.HeadersModificationNotAllowed)
{
    // Handle configuration lock
    _logger.LogWarning("Cannot modify headers after requests have been sent");
}

๐Ÿ” Logging Integration

The ApiClient integrates with Microsoft.Extensions.Logging:

// Configure logging in appsettings.json
{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "ARSoft.RestApiClient.ApiClient": "Debug"
        }
    }
}

// The ApiClient will automatically log:
// - Request cancellations (Debug level)
// - HTTP errors (Error level)

๐Ÿงช Testing

Unit Testing with Mock

[Test]
public async Task GetAsync_WithCustomHeaders_Success()
{
    // Arrange
    var mockHandler = new Mock<HttpMessageHandler>();
    var expectedResponse = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent(JsonSerializer.Serialize(
            new User { Id = 1, Name = "Test User" }))
    };
    
    mockHandler.Protected()
        .Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.Is<HttpRequestMessage>(req => 
                req.Headers.Contains("X-Custom-Header")),
            ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(expectedResponse);
    
    var apiClient = new ApiClient(new Uri("https://api.test.com"));
    
    // Act
    var headers = new Dictionary<string, string> 
    { 
        { "X-Custom-Header", "test-value" } 
    };
    var result = await apiClient.GetAsync<User>(
        new Uri("users/1", UriKind.Relative),
        customHeaders: headers);
    
    // Assert
    Assert.IsTrue(result.Success);
    Assert.AreEqual("Test User", result.Data!.Name);
}

๐Ÿ“ฆ Package Information

Dependencies

  • .NET 6+ (with nullable reference types support)
  • System.Text.Json 8.0.0+
  • Polly 8.6.2+
  • Polly.Core 8.6.2+
  • Microsoft.Extensions.Logging.Abstractions 9.0.0+

Performance Benefits

  • System.Text.Json: High-performance JSON serialization
  • Shared HttpClient: Single instance prevents socket exhaustion
  • HTTP Connection Pooling: Efficient connection reuse
  • Built-in Resilience: Reduces transient failure impact with Polly v8
  • Memory Efficiency: Streaming deserialization for large responses
  • Thread-Safe: Designed for concurrent requests

๐Ÿค Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ“„ Version History

v3.2.0 (Current - .NET 6+)

  • New Feature: Added customHeaders parameter to all HTTP methods for request-specific headers
  • Enhanced header flexibility with per-request custom header support
  • Improved examples demonstrating custom header usage patterns
  • Added comprehensive documentation for multi-tenant, webhook, and rate-limiting scenarios
  • Updated all method signatures to support Dictionary<string, string>? customHeaders

v3.1.1

  • Breaking Change: Removed constructor parameter for external HttpClient
  • Added internal shared HttpClient managed by library
  • Added ApiClientConfigurationException for post-start configuration changes
  • Improved concurrency and memory safety
  • Enhanced XML documentation for all public members
  • Internal locking for thread-safe configuration state
  • Disposing ApiClient no longer disposes shared HttpClient

v3.1.0

  • New Feature: Custom authentication type support (removed in v3.2.0 in favor of customHeaders)
  • Added CustomAuthInfo record for flexible header configuration
  • Enhanced authentication flexibility

v3.0.4

  • JsonSerializerOptions default settings improved
  • Constructor summary comments added for better IntelliSense support

v3.0.0

  • Breaking Changes: Migrated to Polly v8 with ResiliencePipeline
  • Updated to use modern Polly.Core
  • Improved nullable reference types support
  • Enhanced error handling and logging
  • Added proper resource disposal support
  • Simplified configuration with default retry strategies

Migration Notes

From v3.1.x to v3.2.0
  • Replace CustomAuthInfo usage with customHeaders dictionary
  • Old: new CustomAuthInfo { HeaderName = "token", HeaderValue = "abc" }
  • New: customHeaders: new Dictionary<string, string> { { "token", "abc" } }
From v3.0.x to v3.1.x
  • Remove external HttpClient instantiation
  • Use new constructor: new ApiClient(baseAddress, timeout, logger)
  • Configuration methods must be called before first request
From v2.x to v3.0.0
  • Replace IAsyncPolicy<HttpResponseMessage> with ResiliencePipeline<HttpResponseMessage>
  • Update policy creation to use ResiliencePipelineBuilder<T>
  • Use new RetryStrategyOptions configuration

Key Improvements in v3.2.0

  • Enhanced Flexibility: Custom headers per request without authentication coupling
  • Better Multi-Tenancy Support: Easy tenant isolation with headers
  • Improved Observability: Request tracing and correlation ID support
  • Webhook Integration: Built-in support for signature verification patterns
  • Rate Limiting: Header-based priority and tier management
Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on ARSoft.RestApiClient:

Package Downloads
SRA.DeliveryServicesAPIClient.BR

A .NET library for integrating with multiple Brazilian freight quotation services, providing a unified interface for shipping cost calculations.

Correios.DataProvider

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.3.1 96 5/9/2026
3.3.0 368 12/16/2025
3.2.0 296 12/15/2025
3.1.1 168 12/12/2025
3.0.1 190 10/24/2025
3.0.0 254 8/21/2025
1.0.0 214 8/21/2025

Modern .NET Support: Built for .NET Core 3.1+ and .NET 5+
 System.Text.Json: High-performance JSON serialization
 Polly Integration: Built-in retry policies with exponential backoff using modern Polly extensions
 Structured Logging: ILogger integration for comprehensive request/response logging
 Multiple Auth Types: Bearer, Basic, and API Key authentication support
 Clean Architecture: SOLID principles with dependency injection support
 Comprehensive Error Handling: Detailed error responses with HTTP status codes
 Async/Await: Full asynchronous operation support with cancellation tokens