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

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

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