Linger.HttpClient.Contracts 0.5.0-alpha

This is a prerelease version of Linger.HttpClient.Contracts.
dotnet add package Linger.HttpClient.Contracts --version 0.5.0-alpha
                    
NuGet\Install-Package Linger.HttpClient.Contracts -Version 0.5.0-alpha
                    
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="Linger.HttpClient.Contracts" Version="0.5.0-alpha" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Linger.HttpClient.Contracts" Version="0.5.0-alpha" />
                    
Directory.Packages.props
<PackageReference Include="Linger.HttpClient.Contracts" />
                    
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 Linger.HttpClient.Contracts --version 0.5.0-alpha
                    
#r "nuget: Linger.HttpClient.Contracts, 0.5.0-alpha"
                    
#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.
#addin nuget:?package=Linger.HttpClient.Contracts&version=0.5.0-alpha&prerelease
                    
Install Linger.HttpClient.Contracts as a Cake Addin
#tool nuget:?package=Linger.HttpClient.Contracts&version=0.5.0-alpha&prerelease
                    
Install Linger.HttpClient.Contracts as a Cake Tool

Linger.HttpClient.Contracts

Table of Contents

Overview

Linger.HttpClient.Contracts defines standard interfaces and contracts for HTTP client operations, serving as the foundation for Linger HTTP client implementations. By using unified contracts, you can easily switch between different HTTP client implementations without modifying your business code.

Key Components

  • Linger.HttpClient.Contracts: Core interfaces and contracts (this project)
  • Linger.HttpClient.Standard: Implementation based on .NET standard HttpClient

Features

  • Strongly typed HTTP client interfaces
  • Support for various HTTP methods (GET, POST, PUT, DELETE)
  • File upload capabilities
  • Request/response handling
  • User-friendly error handling
  • Timeout management

Installation

# Install interfaces and contracts
dotnet add package Linger.HttpClient.Contracts

# Install implementation based on standard HttpClient
dotnet add package Linger.HttpClient.Standard

# For resilience features (automatic retries, circuit breaker, etc.)
dotnet add package Microsoft.Extensions.Http.Resilience

ApiResult and Linger.Results Integration

The ApiResult type is specifically designed to work seamlessly with the Linger.Results project, especially with its ASP.NET Core integration. The Errors property in ApiResult is specifically intended to receive error information converted by the ToActionResult() method from the Linger.Results.AspNetCore project.

Error Information Format Mapping

When an API server uses Linger.Results to return results and converts them using the ToActionResult() method:

// Server code
public Result<UserDto> GetUser(int id)
{
    if (userNotFound)
        return Result<UserDto>.NotFound("User not found");
        
    return Result.Success(userDto);
}

// Controller
[HttpGet("{id}")]
public ActionResult<UserDto> GetUser(int id)
{
    var result = _userService.GetUser(id);
    return result.ToActionResult(); // Converts to JSON response with errors on failure
}

The client can receive and process these error messages directly through ApiResult:

// Client code
var apiResult = await _httpClient.CallApi<UserDto>($"api/users/{id}");

if (!apiResult.IsSuccess)
{
    // apiResult.Errors will contain error information converted by ToActionResult()
    foreach (var error in apiResult.Errors)
    {
        Console.WriteLine($"Error code: {error.Code}, Message: {error.Message}");
    }
}

Error Structure Mapping

The Error record type in Linger.Results is mapped to the ApiResult.Errors collection:

Linger.Results (Server) ApiResult (Client)
Error.Code ApiError.Code
Error.Message ApiError.Message

This design ensures consistent and complete transfer of error information from server to client, allowing the client to precisely understand and handle business errors returned by the server.

Dependency Injection Integration

Using HttpClientFactory

The recommended way to use Linger HTTP clients is with Microsoft's HttpClientFactory and HTTP Resilience:

// In your startup configuration
services.AddHttpClient<IHttpClient, StandardHttpClient>(client =>
{
    client.BaseAddress = new Uri("https://api.example.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.Timeout = TimeSpan.FromSeconds(30);
})
.AddResilienceHandler("Default", builder =>
{
    // Configure retry behavior
    builder.AddRetry(new HttpRetryStrategyOptions
    {
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromSeconds(2),
        ShouldHandle = args =>
        {
            // Retry on server errors and rate limiting
            return ValueTask.FromResult(args.Outcome.Result?.StatusCode is
                HttpStatusCode.RequestTimeout or      // 408
                HttpStatusCode.TooManyRequests or     // 429
                HttpStatusCode.BadGateway or          // 502
                HttpStatusCode.ServiceUnavailable or  // 503
                HttpStatusCode.GatewayTimeout);       // 504
        }
    });
});

In Service Classes

Once registered, you can inject and use IHttpClient in your services:

public class UserService
{
    private readonly IHttpClient _httpClient;

    public UserService(IHttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<ApiResult<UserInfo>> GetUserInfoAsync(string userId)
    {
        return await _httpClient.CallApi<UserInfo>($"api/users/{userId}");
    }

    public async Task<ApiResult<UserInfo>> CreateUserAsync(UserCreateModel model)
    {
        return await _httpClient.CallApi<UserInfo>("api/users", HttpMethodEnum.Post, model);
    }
}

Basic Usage

GET Requests

// Simple GET request
var result = await _httpClient.CallApi<UserData>("api/users/1");

// With query parameters
var users = await _httpClient.CallApi<List<UserData>>("api/users", 
    new { page = 1, pageSize = 10 });

POST Requests

// POST with JSON body
var newUser = await _httpClient.CallApi<UserData>("api/users", 
    HttpMethodEnum.Post, 
    new { Name = "John", Email = "john@example.com" });

File Upload

// File upload with form data
byte[] fileData = File.ReadAllBytes("document.pdf");
var formData = new Dictionary<string, string>
{
    { "description", "Sample document" }
};

var uploadResult = await _httpClient.CallApi<UploadResponse>(
    "api/files/upload", 
    HttpMethodEnum.Post, 
    formData, 
    fileData, 
    "document.pdf"
);

Error Handling

var result = await _httpClient.CallApi<UserData>($"api/users/{userId}");

if (result.IsSuccess)
{
    // Process successful response
    var user = result.Data;
    Console.WriteLine($"User: {user.Name}");
}
else
{
    // Handle error
    Console.WriteLine($"Error: {result.ErrorMsg}");
    
    // Check for specific status codes
    if (result.StatusCode == HttpStatusCode.Unauthorized)
    {
        // Handle authentication error
    }
}

Automatic Token Refresh

You can implement automatic token refresh using Microsoft.Extensions.Http.Resilience:

// Create a token refresh handler
public class TokenRefreshHandler
{
    private readonly AppState _appState;
    private readonly IServiceProvider _serviceProvider;
    private readonly SemaphoreSlim _semaphore = new(1, 1);

    public TokenRefreshHandler(AppState appState, IServiceProvider serviceProvider)
    {
        _appState = appState;
        _serviceProvider = serviceProvider;
    }

    public void ConfigureTokenRefreshResiliencePipeline(ResiliencePipelineBuilder<HttpResponseMessage> builder)
    {
        builder.AddRetry(new HttpRetryStrategyOptions
        {
            MaxRetryAttempts = 1, // Only try refreshing once
            ShouldHandle = args => 
            {
                bool shouldRetry = args.Outcome.Result?.StatusCode == HttpStatusCode.Unauthorized;
                return ValueTask.FromResult(shouldRetry);
            },
            OnRetry = async context =>
            {
                // Thread-safe token refresh
                await _semaphore.WaitAsync();
                try
                {
                    await RefreshTokenAsync();
                }
                finally
                {
                    _semaphore.Release();
                }
            },
            BackoffType = DelayBackoffType.Constant,
            Delay = TimeSpan.Zero // Retry immediately after token refresh
        });
    }

    private async Task RefreshTokenAsync()
    {
        // Implementation of token refresh logic
        // ...
    }
}

// Register and use in your application
services.AddSingleton<TokenRefreshHandler>();
services.AddHttpClient<IHttpClient, StandardHttpClient>(/* ... */)
    .AddResilienceHandler("TokenRefresh", (builder, context) =>
    {
        var tokenRefreshHandler = context.ServiceProvider.GetRequiredService<TokenRefreshHandler>();
        tokenRefreshHandler.ConfigureTokenRefreshResiliencePipeline(builder);
    });

Best Practices

  1. Use HttpClientFactory for lifecycle management
  2. Set reasonable timeout values
  3. Implement proper error handling
  4. Use strongly typed models for API responses
  5. Enable automatic token refresh for better user experience
  6. Use Microsoft.Extensions.Http.Resilience for retry logic

For more information, see the Linger.HttpClient.Standard documentation.

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 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 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. 
.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 is compatible.  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 (2)

Showing the top 2 NuGet packages that depend on Linger.HttpClient.Contracts:

Package Downloads
Linger.HttpClient.Flurl

Package Description

Linger.HttpClient.Standard

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.5.0-alpha 115 4/10/2025
0.4.0-alpha 114 4/1/2025