MX.Api.Client
2.0.183.1
dotnet add package MX.Api.Client --version 2.0.183.1
NuGet\Install-Package MX.Api.Client -Version 2.0.183.1
<PackageReference Include="MX.Api.Client" Version="2.0.183.1" />
<PackageVersion Include="MX.Api.Client" Version="2.0.183.1" />
<PackageReference Include="MX.Api.Client" />
paket add MX.Api.Client --version 2.0.183.1
#r "nuget: MX.Api.Client, 2.0.183.1"
#:package MX.Api.Client@2.0.183.1
#addin nuget:?package=MX.Api.Client&version=2.0.183.1
#tool nuget:?package=MX.Api.Client&version=2.0.183.1
MX.Api.Client
Resilient REST API client library for building robust .NET applications that consume APIs. Provides authentication, retry policies, token management, and standardized response processing built on top of MX.Api.Abstractions.
Installation
dotnet add package MX.Api.Client
Key Features
- 🔐 Multi-Authentication Support - API keys, Bearer tokens, and Entra ID with automatic token management
- 🛡️ Built-in Resilience - Retry policies, circuit breakers, and exponential backoff
- ⚡ High Performance - Thread-safe operations with efficient caching and connection pooling
- 🔄 Standardized Responses - Uses
ApiResponse<T>
models for consistent error handling - 📊 Comprehensive Logging - Integration with Microsoft.Extensions.Logging for diagnostics
Quick Start
1. Basic Setup
// Program.cs
builder.Services.AddApiClient()
.WithBaseUrl("https://api.example.com")
.WithApiKeyAuthentication("your-api-key");
// Register your client
builder.Services.AddTransient<MyApiClient>();
2. Create Your Client
public class MyApiClient : BaseApi
{
private readonly ILogger<MyApiClient> _logger;
public MyApiClient(
ILogger<MyApiClient> logger,
IApiTokenProvider apiTokenProvider,
IRestClientService restClientService,
IOptions<ApiClientOptions> options)
: base(logger, apiTokenProvider, restClientService, options)
{
_logger = logger;
}
public async Task<ApiResult<User>> GetUserAsync(string userId, CancellationToken cancellationToken = default)
{
try
{
var request = await CreateRequestAsync($"users/{userId}", Method.Get, cancellationToken);
var response = await ExecuteAsync(request, false, cancellationToken);
return response.ToApiResponse<User>();
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogError(ex, "Failed to get user {UserId}", userId);
var errorResponse = new ApiResponse<User>(new ApiError("CLIENT_ERROR", "Failed to retrieve user"));
return new ApiResult<User>(HttpStatusCode.InternalServerError, errorResponse);
}
}
}
3. Use Your Client
public class UserService
{
private readonly MyApiClient _apiClient;
public UserService(MyApiClient apiClient)
{
_apiClient = apiClient;
}
public async Task<User?> GetUserAsync(string userId)
{
var result = await _apiClient.GetUserAsync(userId);
if (result.IsSuccess)
return result.Result?.Data;
if (result.IsNotFound)
return null;
throw new ApplicationException($"API error: {result.StatusCode}");
}
}
Registration Patterns
Simple Registration with AddApiClient
The AddApiClient<TInterface, TImplementation>
method provides a simplified registration pattern for basic scenarios:
// Simple registration with interface and implementation
builder.Services.AddApiClient<IUsersApiClient, UsersApiClient>(options =>
{
options.WithBaseUrl("https://users.example.com")
.WithApiKeyAuthentication("your-api-key");
});
// The client interface
public interface IUsersApiClient
{
Task<ApiResult<User>> GetUserAsync(string userId, CancellationToken cancellationToken = default);
Task<ApiResult<CollectionModel<User>>> GetUsersAsync(FilterOptions? filter = null, CancellationToken cancellationToken = default);
}
// The client implementation
public class UsersApiClient : BaseApi, IUsersApiClient
{
public UsersApiClient(
ILogger<BaseApi<ApiClientOptions>> logger,
IApiTokenProvider apiTokenProvider,
IRestClientService restClientService,
ApiClientOptions options)
: base(logger, apiTokenProvider, restClientService, options)
{
}
public async Task<ApiResult<User>> GetUserAsync(string userId, CancellationToken cancellationToken = default)
{
var request = await CreateRequestAsync($"users/{userId}", Method.Get, cancellationToken);
var response = await ExecuteAsync(request, false, cancellationToken);
return response.ToApiResponse<User>();
}
// Other API methods...
}
Advanced Registration with AddTypedApiClient
The AddTypedApiClient<TInterface, TImplementation, TOptions, TBuilder>
method provides full control over options types for complex scenarios:
// Define custom options class
public class UsersApiClientOptions : ApiClientOptionsBase
{
public string? ApiVersion { get; set; }
public int CacheTimeoutMinutes { get; set; } = 5;
// Custom validation
public override void Validate()
{
base.Validate();
if (string.IsNullOrEmpty(ApiVersion))
throw new InvalidOperationException("ApiVersion is required for UsersApiClient");
}
}
// Define custom builder
public class UsersApiClientOptionsBuilder : ApiClientOptionsBuilder<UsersApiClientOptions, UsersApiClientOptionsBuilder>
{
public UsersApiClientOptionsBuilder WithApiVersion(string version)
{
Options.ApiVersion = version;
return this;
}
public UsersApiClientOptionsBuilder WithCacheTimeout(int minutes)
{
Options.CacheTimeoutMinutes = minutes;
return this;
}
}
// Register with strongly-typed options
builder.Services.AddTypedApiClient<IUsersApiClient, UsersApiClient, UsersApiClientOptions, UsersApiClientOptionsBuilder>(options =>
{
options.WithBaseUrl("https://users.example.com")
.WithApiKeyAuthentication("your-api-key")
.WithApiVersion("v2")
.WithCacheTimeout(10);
});
// The client implementation using strongly-typed options
public class UsersApiClient : BaseApi<UsersApiClientOptions>, IUsersApiClient
{
public UsersApiClient(
ILogger<BaseApi<UsersApiClientOptions>> logger,
IApiTokenProvider apiTokenProvider,
IRestClientService restClientService,
UsersApiClientOptions options)
: base(logger, apiTokenProvider, restClientService, options)
{
}
public async Task<ApiResult<User>> GetUserAsync(string userId, CancellationToken cancellationToken = default)
{
var request = await CreateRequestAsync($"users/{userId}", Method.Get, cancellationToken);
// Access strongly-typed options
if (!string.IsNullOrEmpty(Options.ApiVersion))
{
request.AddQueryParameter("version", Options.ApiVersion);
}
var response = await ExecuteAsync(request, false, cancellationToken);
return response.ToApiResponse<User>();
}
// Other API methods...
}
When to Use Each Pattern
Use AddApiClient<TInterface, TImplementation>
when:
- You have simple configuration needs
- Default
ApiClientOptions
are sufficient - You want minimal setup overhead
- You're building straightforward API clients
Use AddTypedApiClient<TInterface, TImplementation, TOptions, TBuilder>
when:
- You need custom configuration properties
- You want strongly-typed options validation
- You're building reusable API client libraries
- You need complex configuration scenarios
- You want to enforce specific configuration patterns
Authentication Methods
API Key Authentication
// Default header (X-API-Key)
builder.Services.AddApiClient()
.WithApiKeyAuthentication("your-api-key");
// Custom header
builder.Services.Configure<ApiClientOptions>(options => options
.WithApiKey("your-api-key", "X-Custom-Api-Key"));
Bearer Token Authentication
builder.Services.Configure<ApiClientOptions>(options => options
.WithBearerToken("your-bearer-token"));
Entra ID (Azure AD) Authentication
// Using DefaultAzureCredential
builder.Services.AddApiClient()
.WithEntraIdAuthentication("api://your-api-audience");
// With specific tenant
builder.Services.Configure<ApiClientOptions>(options => options
.WithEntraIdAuthentication("api://your-api-audience", "your-tenant-id"));
Client Credentials Flow
builder.Services.Configure<ApiClientOptions>(options => options
.WithClientCredentials(
audience: "api://your-api",
tenantId: "tenant-id",
clientId: "client-id",
clientSecret: "client-secret"));
Multiple Authentication Methods
// Azure API Management + Backend API
builder.Services.Configure<ApiClientOptions>(options => options
.WithBaseUrl("https://your-api.azure-api.net")
.WithSubscriptionKey("apim-subscription-key") // For APIM
.WithEntraIdAuthentication("api://backend-api")); // For backend
Multiple API Clients
Named Configurations
// Register base service
builder.Services.AddApiClient();
// Configure multiple named clients
builder.Services.Configure<ApiClientOptions>("UsersApi", options => options
.WithBaseUrl("https://users.example.com")
.WithApiKeyAuthentication("users-api-key"));
builder.Services.Configure<ApiClientOptions>("OrdersApi", options => options
.WithBaseUrl("https://orders.example.com")
.WithEntraIdAuthentication("api://orders-api"));
// Register typed clients
builder.Services.AddTransient<UsersApiClient>();
builder.Services.AddTransient<OrdersApiClient>();
Named Client Implementation
public class UsersApiClient : BaseApi
{
public UsersApiClient(
ILogger<UsersApiClient> logger,
IApiTokenProvider apiTokenProvider,
IRestClientService restClientService,
IOptionsSnapshot<ApiClientOptions> optionsSnapshot)
: base(logger, apiTokenProvider, restClientService, optionsSnapshot, "UsersApi")
{
}
// API methods...
}
Advanced Features
Custom Request Configuration
public async Task<ApiResult<T>> CustomRequestAsync<T>(CancellationToken cancellationToken = default)
{
var request = await CreateRequestAsync("endpoint", Method.Get, cancellationToken);
// Add custom headers
request.AddHeader("X-Correlation-ID", Guid.NewGuid().ToString());
request.AddHeader("X-Client-Version", "1.0.0");
// Custom timeout
request.Timeout = TimeSpan.FromMinutes(5);
var response = await ExecuteAsync(request, false, cancellationToken);
return response.ToApiResponse<T>();
}
File Upload/Download
// File upload
public async Task<ApiResult<UploadResponse>> UploadFileAsync(
Stream fileStream,
string fileName,
CancellationToken cancellationToken = default)
{
var request = await CreateRequestAsync("files/upload", Method.Post, cancellationToken);
request.AddFile("file", fileStream.ToArray(), fileName, "application/octet-stream");
var response = await ExecuteAsync(request, false, cancellationToken);
return response.ToApiResponse<UploadResponse>();
}
// File download
public async Task<Stream> DownloadFileAsync(string fileId, CancellationToken cancellationToken = default)
{
var request = await CreateRequestAsync($"files/{fileId}", Method.Get, cancellationToken);
var response = await ExecuteAsync(request, false, cancellationToken);
if (response.IsSuccessful && response.RawBytes != null)
return new MemoryStream(response.RawBytes);
throw new ApplicationException($"Download failed: {response.StatusCode}");
}
Error Handling
public async Task<User?> GetUserSafelyAsync(string userId)
{
var result = await GetUserAsync(userId);
if (result.IsSuccess)
return result.Result?.Data;
if (result.IsNotFound)
return null;
if (result.IsUnauthorized)
throw new UnauthorizedAccessException("API access denied");
if (result.IsBadRequest)
throw new ArgumentException($"Invalid user ID: {userId}");
throw new ApplicationException($"API call failed: {result.StatusCode}");
}
Configuration
appsettings.json
{
"ApiClient": {
"BaseUrl": "https://api.example.com",
"MaxRetryCount": 3,
"TimeoutSeconds": 30,
"ApiKey": "your-api-key"
}
}
Programmatic Configuration
builder.Services.Configure<ApiClientOptions>(options => options
.WithBaseUrl("https://api.example.com")
.WithMaxRetryCount(5)
.WithApiKeyAuthentication("your-api-key"));
Dependencies
This package depends on:
- MX.Api.Abstractions - Core response models and interfaces
- Azure.Identity - For Entra ID authentication
- RestSharp - HTTP client functionality
- Polly - Resilience patterns and retry policies
- Microsoft.Extensions.* - Logging, configuration, and dependency injection
Documentation
📖 Implementation Guide - API Consumers - Complete guide for consuming APIs
📖 API Design Patterns - Understanding the underlying patterns { private readonly ILogger<MyApiClient> logger;
public MyApiClient( ILogger<MyApiClient> logger, IApiTokenProvider apiTokenProvider, IRestClientService restClientService, IOptions<ApiClientOptions> options) : base(logger, apiTokenProvider, restClientService, options) { this.logger = logger; }
// Implement custom API methods public async Task<ApiResponse<ResourceDto>> GetResourceAsync(string id, CancellationToken cancellationToken = default) { try { var request = await CreateRequestAsync($"resources/{id}", Method.Get, cancellationToken); var response = await ExecuteAsync(request, false, cancellationToken);
return response.ToApiResponse<ResourceDto>(); } catch (Exception ex) when (ex is not OperationCanceledException) { logger.LogError(ex, "Failed to retrieve resource with ID {ResourceId}", id); var errorResponse = new ApiResponse<ResourceDto>(new ApiError("InternalError", "An unexpected error occurred")); return new ApiResult<ResourceDto>(HttpStatusCode.InternalServerError, errorResponse); }
} }
### Updating API Key at Runtime
```csharp
// Update authentication options
var authOptions = new ApiKeyAuthenticationOptions
{
ApiKey = "new-api-key",
HeaderName = "Ocp-Apim-Subscription-Key" // or your custom header name
};
Authentication Methods
API Key Authentication
Use this when your API requires an API key in a header (like Azure API Management).
services.AddApiClient()
.WithApiKeyAuthentication("your-api-key", "X-API-Key"); // Custom header name
// Or configure via options
services.Configure<ApiClientOptions>(options => options
.WithApiKeyAuthentication("your-api-key", "X-API-Key"));
// For Azure API Management subscription keys
services.Configure<ApiClientOptions>(options => options
.WithSubscriptionKey("your-subscription-key")); // Uses Ocp-Apim-Subscription-Key header
Entra ID Authentication
Use this when your API requires OAuth tokens from Entra ID (formerly Azure AD).
services.AddApiClient()
.WithEntraIdAuthentication("api://your-api-audience");
// Or configure via options
services.Configure<ApiClientOptions>(options => options
.WithEntraIdAuthentication("api://your-api-audience"));
Multiple Authentication Methods
For APIs behind Azure API Management that require both subscription keys and identity tokens:
services.Configure<ApiClientOptions>(options => options
.WithBaseUrl("https://your-api-via-apim.azure-api.net")
.WithSubscriptionKey("your-apim-subscription-key") // For API Management
.WithEntraIdAuthentication("api://your-api-audience")); // For underlying API
// This will result in requests having both:
// - Ocp-Apim-Subscription-Key: your-apim-subscription-key
// - Authorization: Bearer <entra-id-token>
Custom combinations:
services.Configure<ApiClientOptions>(options => options
.WithApiKeyAuthentication("primary-key", "X-Primary-Key")
.WithApiKeyAuthentication("secondary-key", "X-Secondary-Key")
.WithEntraIdAuthentication("api://your-api-audience"));
With custom credential options:
services.AddApiClient()
.WithEntraIdAuthentication("api://your-api-audience", options =>
{
options.ExcludeManagedIdentityCredential = true;
// Other DefaultAzureCredentialOptions
});
Error Handling and Resilience
The library includes built-in retry policies with exponential backoff for transient failures:
services.Configure<ApiClientOptions>(options =>
{
options.MaxRetryCount = 3; // Configure retry count (default is 3)
});
You can also customize the retry behavior:
// Custom retry policy
services.AddApiClient()
.WithCustomRetryPolicy(retryCount =>
Policy
.Handle<HttpRequestException>()
.OrResult<RestResponse>(r => r.StatusCode == HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(
retryCount,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) +
TimeSpan.FromMilliseconds(Random.Shared.Next(0, 1000))
)
);
Advanced Usage
Working with Collections and Filtering
public async Task<ApiResponse<CollectionModel<ResourceDto>>> GetResourcesAsync(FilterOptions filter, CancellationToken cancellationToken = default)
{
try
{
var request = await CreateRequestAsync("resources", Method.Get, cancellationToken);
// Add filter options to request
request.AddFilterOptions(filter);
var response = await ExecuteAsync(request, false, cancellationToken);
return response.ToApiResponse<CollectionModel<ResourceDto>>();
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
logger.LogError(ex, "Failed to retrieve resources");
var errorResponse = new ApiResponse<CollectionModel<ResourceDto>>(new ApiError("InternalError", "An unexpected error occurred"));
return new ApiResult<CollectionModel<ResourceDto>>(HttpStatusCode.InternalServerError, errorResponse);
}
}
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. 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. |
-
net9.0
- Azure.Identity (>= 1.15.0)
- Microsoft.Extensions.Caching.Abstractions (>= 9.0.8)
- Microsoft.Extensions.Caching.Memory (>= 9.0.8)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.8)
- Microsoft.Extensions.Http (>= 9.0.8)
- Microsoft.Extensions.Http.Polly (>= 9.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.8)
- Microsoft.Extensions.Options (>= 9.0.8)
- MX.Api.Abstractions (>= 2.0.183.1)
- Newtonsoft.Json (>= 13.0.3)
- Polly (>= 8.6.3)
- RestSharp (>= 112.1.0)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on MX.Api.Client:
Package | Downloads |
---|---|
XtremeIdiots.Portal.Repository.Api.Client.V1
Versioned client for the XtremeIdiots Portal Repository API V1. |
|
XtremeIdiots.Portal.Repository.Api.Client.V2
Versioned client for the XtremeIdiots Portal Repository API V2. |
|
MX.GeoLocation.Api.Client.V1
This package provides a web service client to query the geolocation service. |
|
XtremeIdiots.Portal.Integrations.Servers.Api.Client.V1
Client for the XtremeIdiots Portal Servers API. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
2.0.183.1 | 0 | 8/26/2025 |
2.0.182.1 | 125 | 8/23/2025 |
2.0.181.1 | 44 | 8/22/2025 |
2.0.180.1 | 184 | 8/21/2025 |
2.0.179.1 | 118 | 8/20/2025 |
2.0.178.1 | 103 | 8/20/2025 |
2.0.177.1 | 178 | 8/18/2025 |
2.0.176.1 | 137 | 8/11/2025 |
2.0.175.1 | 211 | 8/4/2025 |
2.0.174.1 | 199 | 7/28/2025 |
2.0.173.1 | 282 | 7/21/2025 |
2.0.172.1 | 151 | 7/14/2025 |
2.0.171.1 | 439 | 7/8/2025 |
2.0.170.1 | 185 | 7/8/2025 |
2.0.169.1 | 140 | 7/8/2025 |
2.0.168.1 | 135 | 7/8/2025 |
2.0.166.1 | 138 | 7/7/2025 |
2.0.165.1 | 209 | 7/6/2025 |
2.0.164.1 | 156 | 7/6/2025 |
2.0.163.1 | 129 | 7/6/2025 |
2.0.162.1 | 135 | 7/6/2025 |
2.0.161.1 | 113 | 7/5/2025 |
2.0.160.1 | 82 | 7/5/2025 |
2.0.159.1 | 78 | 7/5/2025 |
2.0.158.1 | 67 | 7/5/2025 |
2.0.157.1 | 70 | 7/5/2025 |
2.0.156.1 | 71 | 7/5/2025 |
2.0.155.1 | 70 | 7/5/2025 |
2.0.154.1 | 70 | 7/5/2025 |
2.0.153.1 | 68 | 7/5/2025 |
2.0.152.1 | 74 | 7/5/2025 |
2.0.151.1 | 75 | 7/5/2025 |