Zwedze.Aetherweave.Http 0.2.5

dotnet add package Zwedze.Aetherweave.Http --version 0.2.5
                    
NuGet\Install-Package Zwedze.Aetherweave.Http -Version 0.2.5
                    
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="Zwedze.Aetherweave.Http" Version="0.2.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Zwedze.Aetherweave.Http" Version="0.2.5" />
                    
Directory.Packages.props
<PackageReference Include="Zwedze.Aetherweave.Http" />
                    
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 Zwedze.Aetherweave.Http --version 0.2.5
                    
#r "nuget: Zwedze.Aetherweave.Http, 0.2.5"
                    
#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 Zwedze.Aetherweave.Http@0.2.5
                    
#: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=Zwedze.Aetherweave.Http&version=0.2.5
                    
Install as a Cake Addin
#tool nuget:?package=Zwedze.Aetherweave.Http&version=0.2.5
                    
Install as a Cake Tool

Aetherweave.Http

Clean, type-safe HttpClient configuration with built-in profiling, content tracing, and error handling.

Features

  • Configuration-Based Setup - Configure HttpClients from appsettings.json with IOptions validation
  • Built-in Profiling - Automatic request timing and performance tracking
  • Content Tracing - Log response content for debugging
  • Error Handling - Custom error handlers for failed HTTP requests
  • Chainable API - Fluent builder pattern for adding handlers
  • Startup Validation - Configuration validated at application startup
  • Type-Safe - Strongly-typed clients with dependency injection

Installation

dotnet add package Zwedze.Aetherweave.Http

Quick Start

1. Define Your HTTP Client Interface

public interface IOrderServiceClient
{
    Task<Order> GetOrderAsync(int orderId, CancellationToken ct);
    Task<Order> CreateOrderAsync(CreateOrderRequest request, CancellationToken ct);
}

public sealed class OrderServiceClient(HttpClient httpClient) : IOrderServiceClient
{
    public async Task<Order> GetOrderAsync(int orderId, CancellationToken ct)
    {
        var response = await httpClient.GetAsync($"/api/orders/{orderId}", ct);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<Order>(ct) 
            ?? throw new InvalidOperationException("Order not found");
    }

    public async Task<Order> CreateOrderAsync(CreateOrderRequest request, CancellationToken ct)
    {
        var response = await httpClient.PostAsJsonAsync("/api/orders", request, ct);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<Order>(ct) 
            ?? throw new InvalidOperationException("Failed to create order");
    }
}

2. Configure in appsettings.json

{
  "Aetherweave": {
    "HttpClients": {
      "OrderService": {
        "BaseAddress": "https://api.orders.example.com",
        "Timeout": "00:00:30",
        "EnableProfiling": true,
        "EnableContentTracing": false,
        "MaxContentLogSize": 10000
      }
    }
  }
}

3. Register in Program.cs

services.AddAetherweaveHttpClient<IOrderServiceClient, OrderServiceClient>(
    configuration,
    "OrderService");

4. Use in Your Application

public sealed class OrderController(IOrderServiceClient orderClient) : ControllerBase
{
    [HttpGet("{id}")]
    public async Task<IActionResult> GetOrder(int id, CancellationToken ct)
    {
        var order = await orderClient.GetOrderAsync(id, ct);
        return Ok(order);
    }
}

Configuration Options

HttpClientOptions

Property Type Default Required Description
BaseAddress string - Base URL for the HTTP client (must be absolute URI)
Timeout TimeSpan 00:00:30 Request timeout (must be > 0)
EnableProfiling bool false Enable request timing and performance logging
EnableContentTracing bool false Enable response content logging
MaxContentLogSize int 10000 Maximum bytes to log (content truncated if larger)

Multiple Clients Configuration

{
  "Aetherweave": {
    "HttpClients": {
      "OrderService": {
        "BaseAddress": "https://api.orders.example.com",
        "Timeout": "00:00:30",
        "EnableProfiling": true,
        "EnableContentTracing": false
      },
      "PaymentService": {
        "BaseAddress": "https://api.payments.example.com",
        "Timeout": "00:01:00",
        "EnableProfiling": false,
        "EnableContentTracing": true,
        "MaxContentLogSize": 5000
      },
      "InventoryService": {
        "BaseAddress": "https://api.inventory.example.com",
        "Timeout": "00:00:15",
        "EnableProfiling": true,
        "EnableContentTracing": true
      }
    }
  }
}
// Register all clients
services.AddAetherweaveHttpClient<IOrderServiceClient, OrderServiceClient>(
    configuration, "OrderService");
    
services.AddAetherweaveHttpClient<IPaymentServiceClient, PaymentServiceClient>(
    configuration, "PaymentService");
    
services.AddAetherweaveHttpClient<IInventoryServiceClient, InventoryServiceClient>(
    configuration, "InventoryService");

Built-in Handlers

Profiling Handler

Automatically logs request timing when EnableProfiling is true:

[2025-12-20 10:30:45] HTTP GET https://api.orders.example.com/api/orders/123 completed in 245ms with status 200

Configuration:

{
  "OrderService": {
    "BaseAddress": "https://api.orders.example.com",
    "EnableProfiling": true
  }
}

Content Tracing Handler

Logs response content when EnableContentTracing is true:

[2025-12-20 10:30:45] HTTP GET https://api.orders.example.com/api/orders/123 returned 200 (1234 bytes): {"orderId":123,"total":99.99,...}

Configuration:

{
  "OrderService": {
    "BaseAddress": "https://api.orders.example.com",
    "EnableContentTracing": true,
    "MaxContentLogSize": 5000
  }
}

Security Warning: ⚠️ Never enable EnableContentTracing in production with sensitive data (passwords, credit cards, etc.)!

Authentication

This package is purely generic HttpClient plumbing — it has no knowledge of any authentication flow. For auth, use one of the dedicated Aetherweave Security packages, each of which builds on top of AddAetherweaveHttpClient/WithHandler/WithErrorHandler above:

Case Package
Protecting your own API by validating incoming JWTs Zwedze.Aetherweave.Security.Jwt
Backend-to-backend — your service calling other APIs as itself (OAuth2 client credentials) Zwedze.Aetherweave.Security.ClientCredentials
Interactive user login for a Blazor WebAssembly UI (Authorization Code + PKCE) Zwedze.Aetherweave.Security.Oidc

Advanced Usage

Adding Custom Handlers

services.AddAetherweaveHttpClient<IOrderServiceClient, OrderServiceClient>(
        configuration, 
        "OrderService")
    .WithHandler<CorrelationIdHandler>()
    .WithHandler<RetryPolicyHandler>();

Custom handler example:

public sealed class CorrelationIdHandler(ICorrelationIdAccessor correlationIdAccessor) : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        request.Headers.Add("X-Correlation-Id", correlationIdAccessor.CorrelationId);

        return await base.SendAsync(request, cancellationToken);
    }
}

// Register
services.AddAetherweaveHttpClient<IOrderServiceClient, OrderServiceClient>(
        configuration, 
        "OrderService")
    .WithHandler<CorrelationIdHandler>();

Custom Error Handling

public sealed class OrderServiceErrorHandler(ILogger<OrderServiceErrorHandler> logger) : IHttpErrorHandler
{
    public async Task HandleError(
        HttpRequestMessage request,
        HttpResponseMessage response,
        HttpStatusCode statusCode)
    {
        var content = await response.Content.ReadAsStringAsync();
        
        logger.LogError(
            "Order service request to {Uri} failed with {StatusCode}: {Content}",
            request.RequestUri,
            statusCode,
            content);

        throw statusCode switch
        {
            HttpStatusCode.NotFound => new OrderNotFoundException(content),
            HttpStatusCode.BadRequest => new InvalidOrderException(content),
            HttpStatusCode.Unauthorized => new UnauthorizedException(),
            _ => new OrderServiceException($"Request failed with status {statusCode}")
        };
    }
}

// Register
services.AddAetherweaveHttpClient<IOrderServiceClient, OrderServiceClient>(
        configuration, 
        "OrderService")
    .WithErrorHandler<OrderServiceErrorHandler>();

Combining Multiple Handlers

services.AddAetherweaveHttpClient<IOrderServiceClient, OrderServiceClient>(
        configuration, 
        "OrderService")
    .WithHandler<CorrelationIdHandler>()
    .WithHandler<RetryPolicyHandler>()
    .WithErrorHandler<OrderServiceErrorHandler>();

Handler execution order:

  1. ProfilingHandler (starts timer) - built-in, outermost
  2. ContentTracingHandler (logs response) - built-in
  3. CorrelationIdHandler (adds header)
  4. RetryPolicyHandler (retries on failure)
  5. HttpErrorResponseHandler (custom error handling)
  6. → Actual HTTP request →
  7. HttpErrorResponseHandler (processes errors)
  8. RetryPolicyHandler (retry if needed)
  9. CorrelationIdHandler
  10. ContentTracingHandler (logs response content)
  11. ProfilingHandler (logs timing)

Environment-Specific Configuration

appsettings.Development.json:

{
  "Aetherweave": {
    "HttpClients": {
      "OrderService": {
        "BaseAddress": "https://dev-api.orders.example.com",
        "Timeout": "00:05:00",
        "EnableProfiling": true,
        "EnableContentTracing": true,
        "MaxContentLogSize": 50000
      }
    }
  }
}

appsettings.Production.json:

{
  "Aetherweave": {
    "HttpClients": {
      "OrderService": {
        "BaseAddress": "https://api.orders.example.com",
        "Timeout": "00:00:30",
        "EnableProfiling": false,
        "EnableContentTracing": false
      }
    }
  }
}

Using with Polly for Resilience

services.AddAetherweaveHttpClient<IOrderServiceClient, OrderServiceClient>(
        configuration, 
        "OrderService")
    .AddPolicyHandler(Policy<HttpResponseMessage>
        .Handle<HttpRequestException>()
        .OrResult(r => !r.IsSuccessStatusCode)
        .WaitAndRetryAsync(3, retryAttempt => 
            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))))
    .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)));

Integration with Aetherweave.Application

Command Handler with HTTP Client

public sealed class CreateOrderHandler(
    IOrderServiceClient orderServiceClient,
    IUnitOfWorkFactory uowFactory,
    IOrderRepository orderRepository) : ICommandHandler<CreateOrderCommand, Guid>
{
    public async Task<ResponseWrapper<Guid>> Handle(
        CreateOrderCommand request,
        CancellationToken cancellationToken)
    {
        await using var uow = uowFactory.CreateTransactional();
        
        try
        {
            // Create order via external API
            var externalOrder = await orderServiceClient.CreateOrderAsync(
                new CreateOrderRequest(request.Items),
                cancellationToken);
            
            // Save locally
            var order = new Order(
                Id<Order>.From(externalOrder.Id),
                Code<Order>.From(externalOrder.OrderNumber));
                
            await orderRepository.AddAsync(order, cancellationToken);
            await uow.SaveChanges(cancellationToken);
            await uow.Commit(cancellationToken);
            
            return ResponseWrapper.Ok(order.Id);
        }
        catch (OrderServiceException ex)
        {
            return ResponseWrapper.Fail<Guid>(
                ErrorFactory.Create("EXTERNAL_SERVICE_ERROR", ex.Message));
        }
    }
}

Best Practices

✅ DO

  1. Use interfaces for HTTP clients:

    // Good
    public interface IOrderServiceClient { ... }
    public sealed class OrderServiceClient : IOrderServiceClient { ... }
    
    // Bad
    public sealed class OrderServiceClient { ... }  // No interface
    
  2. Configure separate clients for different services:

    services.AddAetherweaveHttpClient<IOrderServiceClient, OrderServiceClient>(...);
    services.AddAetherweaveHttpClient<IPaymentServiceClient, PaymentServiceClient>(...);
    
  3. Use configuration for environment-specific settings:

    // appsettings.Development.json
    { "EnableContentTracing": true }
    
    // appsettings.Production.json
    { "EnableContentTracing": false }
    
  4. Set appropriate timeouts:

    {
      "Timeout": "00:00:30"  // 30 seconds for quick APIs
      "Timeout": "00:05:00"  // 5 minutes for long-running operations
    }
    
  5. Use custom error handlers for domain-specific errors:

    .WithErrorHandler<OrderServiceErrorHandler>()
    

❌ DON'T

  1. Don't hardcode URLs in client implementations:

    // Bad
    var response = await httpClient.GetAsync("https://hardcoded-url.com/api");
    
    // Good - use BaseAddress from config
    var response = await httpClient.GetAsync("/api/orders");
    
  2. Don't enable content tracing with sensitive data:

    // BAD in production!
    {
      "PaymentService": {
        "EnableContentTracing": true  // Could log credit cards!
      }
    }
    
  3. Don't use the same client name for different services:

    // Bad - both use "ApiClient"
    services.AddAetherweaveHttpClient<IOrderClient, OrderClient>(config, "ApiClient");
    services.AddAetherweaveHttpClient<IPaymentClient, PaymentClient>(config, "ApiClient");
    
  4. Remember to validate configuration at startup:

    // Configuration automatically validated with .ValidateOnStart()
    // Will fail fast if BaseAddress is missing or invalid
    

Error Handling

ConfigurationNotFoundException

Thrown when the configuration section is not found:

try
{
    services.AddAetherweaveHttpClient<IOrderClient, OrderClient>(
        configuration, 
        "NonExistentClient");
}
catch (ConfigurationNotFoundException ex)
{
    // Configuration section 'Aetherweave:HttpClients:NonExistentClient' not found.
    // Ensure your appsettings.json contains the required configuration.
}

Validation Errors

Configuration validated at startup with detailed error messages:

Unhandled exception. Microsoft.Extensions.Options.OptionsValidationException: 
DataAnnotation validation failed for 'HttpClientOptions' members: 'Timeout' 
with the error: 'Timeout must be greater than zero'.

Performance Considerations

Profiling Overhead

When EnableProfiling is true:

  • Minimal overhead (~1-2ms per request)
  • Only measures elapsed time
  • Safe for production use

Content Tracing Overhead

When EnableContentTracing is true:

  • Significant overhead (reads entire response into memory)
  • Doubles memory usage for response
  • Not recommended for production
  • Use only for debugging/development

Handler Order Optimization

Handlers execute in order of registration:

// Optimal order for performance
services.AddAetherweaveHttpClient<IClient, Client>(config, "Client")
    .WithHandler<CacheHandler>()          // Check cache first
    .WithHandler<CorrelationIdHandler>()  // Then tag the request
    .WithHandler<RetryPolicyHandler>();   // Retry last

Migration from HttpClientFactory

Before (raw HttpClientFactory):

services.AddHttpClient("OrderService", client =>
{
    client.BaseAddress = new Uri("https://api.orders.com");
    client.Timeout = TimeSpan.FromSeconds(30);
});

// Usage
public class OrderService(IHttpClientFactory httpClientFactory)
{
    public async Task<Order> GetOrderAsync(int id)
    {
        var client = httpClientFactory.CreateClient("OrderService");
        var response = await client.GetAsync($"/api/orders/{id}");
        // ...
    }
}

After (Aetherweave):

// appsettings.json
{
  "Aetherweave": {
    "HttpClients": {
      "OrderService": {
        "BaseAddress": "https://api.orders.com",
        "Timeout": "00:00:30"
      }
    }
  }
}

// Program.cs
services.AddAetherweaveHttpClient<IOrderServiceClient, OrderServiceClient>(
    configuration,
    "OrderService");

// Usage
public sealed class OrderService(IOrderServiceClient orderClient)
{
    public async Task<Order> GetOrderAsync(int id)
    {
        return await orderClient.GetOrderAsync(id);
    }
}
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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 Zwedze.Aetherweave.Http:

Package Downloads
Zwedze.Aetherweave.Security.Oidc

Manage OIDC integration for api security.

Zwedze.Aetherweave.Security.ClientCredentials

Backend-to-backend OAuth2 client-credentials authentication for Aetherweave typed HttpClients.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.5 109 9/3/2026
0.2.5-pre.2 72 9/3/2026
0.2.5-pre.1 77 8/13/2026
0.2.5-extend-db-registratio... 62 8/31/2026
0.2.4 128 8/13/2026
0.2.4-pre.1 74 8/13/2026
0.2.3 119 8/6/2026
0.2.3-pre.1 71 8/6/2026
0.2.2 127 8/5/2026
0.2.2-pre.1 82 7/30/2026
0.2.1 118 7/29/2026
0.2.1-pre.1 71 7/29/2026
0.2.0 116 7/25/2026
0.1.2-pre.3 80 7/25/2026
0.1.2-pre.1 62 7/24/2026
0.1.2-http-auth.1 55 7/23/2026
0.1.1 114 6/16/2026
0.1.0-alpha.12 61 6/16/2026
0.0.1-pre.14 81 6/16/2026
0.0.1-pre.13 66 6/16/2026
Loading failed