FEx.Flurlx 0.4.0-alpha.442

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

FEx.Flurlx - Resilient HTTP Client Module

Overview

FEx.Flurlx provides a modern, resilient HTTP client infrastructure built on Flurl.Http and Polly. It delivers comprehensive failure handling, automatic retries, circuit breaking, request throttling, and graceful degradation for API integrations.

Key Features

  • 🔄 Automatic Retries - Exponential backoff for transient failures
  • ⚡ Circuit Breaker - Prevents cascading failures when endpoints degrade
  • ⏱️ Timeout Protection - Prevents hanging requests
  • 🚦 Bulkhead Isolation - Limits concurrent requests (critical for slow APIs)
  • 🛡️ Fallback Support - Graceful degradation with cached responses
  • 📝 Comprehensive Logging - Track resilience policy activations
  • ⚙️ Configurable Policies - Fine-tune resilience per API

FExPollyPolicyBuilder

The FExPollyPolicyBuilder is the heart of FEx.Flurlx's resilience strategy. It creates a comprehensive Polly policy suite that wraps HTTP requests with multiple layers of protection.

Basic Usage

using FEx.Flurlx.Configuration;
using FEx.Flurlx.Services;
using FEx.Logging.Abstractions.Extensions;

// Create policy builder (optionally with logger)
var logger = this.GetLogger();
var policyBuilder = new FExPollyPolicyBuilder(logger);

// Create configuration
var config = new PollyPolicyConfiguration
{
    MaxRetryAttempts = 3,
    RequestTimeout = TimeSpan.FromSeconds(30),
    MaxParallelization = 10
};

// Build the full resilience policy
var resiliencePolicy = policyBuilder.BuildFullSuitePolicy(config);

// Use with FlurlApiBase or directly with HttpClient
var response = await resiliencePolicy.ExecuteAsync(async ct =>
{
    return await flurlClient.Request("api/endpoint")
        .GetAsync(cancellationToken: ct)
        .ResponseMessage;
});

Policy Configuration

Default Configuration
var config = new PollyPolicyConfiguration(); // Uses sensible defaults

Defaults:

  • Retry: 3 attempts with 1s initial delay
  • Timeout: 30 seconds per request
  • Circuit Breaker: Opens after 5 consecutive failures, stays open for 1 minute
  • Bulkhead: 10 concurrent requests, 20 queued
  • Fallback: Enabled
Slow API Configuration

For slow/unreliable APIs (e.g., Synology DSM, IoT devices):

var config = PollyPolicyConfiguration.SlowApiDefaults();

Slow API Defaults:

  • Retry: 5 attempts with 2s initial delay
  • Timeout: 2 minutes per request
  • Circuit Breaker: Opens after 10 consecutive failures, stays open for 5 minutes
  • Bulkhead: 3 concurrent requests, 10 queued
  • Fallback: Enabled
Fast API Configuration

For fast, reliable APIs (e.g., internal microservices):

var config = PollyPolicyConfiguration.FastApiDefaults();

Fast API Defaults:

  • Retry: 2 attempts with 500ms initial delay
  • Timeout: 10 seconds per request
  • Circuit Breaker: Opens after 3 consecutive failures, stays open for 30 seconds
  • Bulkhead: 20 concurrent requests, 50 queued
  • Fallback: Disabled
Custom Configuration
var config = new PollyPolicyConfiguration
{
    // Retry settings
    MaxRetryAttempts = 5,
    InitialRetryDelay = TimeSpan.FromSeconds(2),
    
    // Timeout
    RequestTimeout = TimeSpan.FromMinutes(1),
    
    // Circuit Breaker
    CircuitBreakerFailureThreshold = 10,
    CircuitBreakerDuration = TimeSpan.FromMinutes(5),
    
    // Bulkhead (for slow APIs)
    MaxParallelization = 3,
    MaxQueuingActions = 10,
    
    // Fallback
    EnableFallback = true
};

Understanding the Policies

1. Retry Policy

Automatically retries failed requests with exponential backoff.

Triggers Retry On:

  • HttpRequestException (network failures)
  • TimeoutRejectedException (timeouts)
  • HTTP 408 (Request Timeout)
  • HTTP 429 (Too Many Requests)
  • HTTP 5xx (Server Errors)

Retry Delay Formula:

delay = 2^retryAttempt * InitialRetryDelay

Example:

  • Attempt 1: Wait 2 seconds
  • Attempt 2: Wait 4 seconds
  • Attempt 3: Wait 8 seconds
2. Circuit Breaker Policy

Prevents overwhelming a failing endpoint by "opening the circuit" after too many failures.

States:

  • Closed (Normal): All requests pass through
  • Open (Tripped): All requests fail immediately
  • Half-Open (Testing): Single test request allowed

Example: After 5 consecutive failures (default), circuit opens for 1 minute. No requests are sent during this time. After 1 minute, one test request is allowed. If it succeeds, circuit closes. If it fails, circuit opens for another minute.

3. Timeout Policy

Prevents requests from hanging indefinitely.

Strategy: Pessimistic timeout (hard cancellation)

Example: If RequestTimeout = 30s, any request taking longer than 30 seconds is cancelled.

4. Bulkhead Policy

Limits concurrent requests to prevent overwhelming slow APIs.

Parameters:

  • MaxParallelization: Maximum concurrent requests
  • MaxQueuingActions: Maximum queued requests waiting for execution

Example: With MaxParallelization = 3 and MaxQueuingActions = 10:

  • First 3 requests execute immediately
  • Next 10 requests wait in queue
  • 14th request is rejected immediately (BulkheadRejectedException)

Critical for Slow APIs: Prevents 100s of concurrent requests to a slow endpoint (e.g., Synology DSM).

5. Fallback Policy

Provides graceful degradation when all other policies fail.

Default Behavior: Returns HTTP 503 (Service Unavailable) with error message.

Future Enhancement: Cache integration to return stale data when API is unavailable.

Policy Wrapping Order

Policies are applied in a specific order (innermost to outermost):

Fallback
  └─ Retry
      └─ Circuit Breaker
          └─ Timeout
              └─ Bulkhead
                  └─ HTTP Request

Why This Order?

  1. Bulkhead (innermost): Limits concurrent execution first
  2. Timeout: Cancels individual request attempts
  3. Circuit Breaker: Prevents retrying a known-bad endpoint
  4. Retry: Handles transient failures
  5. Fallback (outermost): Last resort when everything fails

Using with FlurlApiBase

The typical pattern is to inject the policy into your API client:

public class MySynoApi : FlurlApiBase
{
    public MySynoApi(
        IFlurlClient flurlClient,
        IAsyncPolicy<HttpResponseMessage> resiliencePolicy)
        : base(flurlClient, resiliencePolicy)
    {
    }

    public async Task<MyData> GetDataAsync(CancellationToken ct = default)
    {
        // Automatically protected by all Polly policies
        return await GetResponseAsync<MyData>(
            "api/data",
            func: req => req.SetQueryParam("filter", "active"),
            method: RequestMethod.GET,
            cancellationToken: ct);
    }
}

Logging

When a logger is provided, FExPollyPolicyBuilder logs all resilience events:

Retry:

[FExPolly] Retry 1/3 after 2.0s due to status code: InternalServerError

Circuit Breaker:

[FExPolly] Circuit breaker OPENED for 60s after 5 failures
[FExPolly] Circuit breaker HALF-OPEN - testing endpoint
[FExPolly] Circuit breaker CLOSED - endpoint recovered

Timeout:

[FExPolly] Request timed out after 30.0s

Bulkhead:

[FExPolly] Bulkhead rejected request - 10 concurrent + 20 queued limit reached

Fallback:

[FExPolly] Fallback activated due to: Connection refused

Real-World Example: Synology DSM API

Synology DSM APIs are notoriously slow and unreliable. Here's a production-ready configuration:

public class SynologyApiConfiguration : IApiConfiguration
{
    public Url BaseUrl => "https://my-synology:5001";
    public string ClientName => "SynologyDSM";
    public bool IgnoreSSLErrors => true; // Self-signed cert
    
    public PollyPolicyConfiguration PollyConfig => new()
    {
        // Generous retries (Synology can be slow)
        MaxRetryAttempts = 5,
        InitialRetryDelay = TimeSpan.FromSeconds(3),

        // Long timeout (file operations take time)
        RequestTimeout = TimeSpan.FromMinutes(5),

        // Persistent circuit breaker (don't hammer the NAS)
        CircuitBreakerFailureThreshold = 10,
        CircuitBreakerDuration = TimeSpan.FromMinutes(10),

        // Strict concurrency (NAS has limited resources)
        MaxParallelization = 2,
        MaxQueuingActions = 5,

        // Enable fallback to return cached file list
        EnableFallback = true
    };
}

Integration with FEx DI

The module uses StrongInject for dependency injection:

// In your composition root
services.AddSingleton<IApiConfiguration, MySynologyApiConfiguration>();

// FlurlConfigurator automatically creates and exposes the resilience policy
services.AddSingleton<IFlurlConfigurator, FlurlConfigurator>();

// Get the policy from the configurator
var policy = serviceProvider
    .GetRequiredService<IFlurlConfigurator>()
    .GetResiliencePolicy();

Performance Considerations

Bulkhead is Your Friend

For slow APIs, always configure Bulkhead to prevent resource exhaustion:

MaxParallelization = 3,  // Only 3 concurrent requests
MaxQueuingActions = 10   // Max 10 waiting requests

Without Bulkhead, 100 threads making slow API calls = 100 hung connections.

Circuit Breaker Saves Resources

When an API goes down, Circuit Breaker prevents wasting time on doomed requests:

CircuitBreakerFailureThreshold = 5,
CircuitBreakerDuration = TimeSpan.FromMinutes(1)

After 5 failures, requests fail immediately for 1 minute instead of timing out.

Timeout Prevents Hangs

Always set reasonable timeouts:

RequestTimeout = TimeSpan.FromSeconds(30)  // Don't let requests hang forever

Migration from FlakEssentials.Flurl

If migrating from the old FlakEssentials.Flurl (AsyncWorkersService-based):

Before:

public class MyApi : FlurlApiBase<MyFlurlClientService>
{
    public MyApi(MyFlurlClientService flurlClientService)
        : base(flurlClientService)
    {
    }
}

After:

public class MyApi : FlurlApiBase
{
    public MyApi(
        IFlurlClient flurlClient,
        IAsyncPolicy<HttpResponseMessage> resiliencePolicy)
        : base(flurlClient, resiliencePolicy)
    {
    }
}

Benefits:

  • ✅ No more complex worker pool management
  • ✅ Industry-standard Polly policies
  • ✅ Better observability with logging
  • ✅ More flexible configuration
  • ✅ Easier testing (mock policies)

Testing

Mock the policy for unit tests:

// No-op policy for testing
var testPolicy = Policy.NoOpAsync<HttpResponseMessage>();

var api = new MyApi(mockFlurlClient, testPolicy);

Or test resilience behavior:

// Simulate circuit breaker
var circuitBreakerPolicy = Policy
    .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    .CircuitBreakerAsync(1, TimeSpan.FromSeconds(30));

// First failure opens circuit
await circuitBreakerPolicy.ExecuteAsync(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError)));

// Second request fails immediately with BrokenCircuitException
await Assert.ThrowsAsync<BrokenCircuitException>(async () =>
    await circuitBreakerPolicy.ExecuteAsync(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)))
);

Troubleshooting

Circuit Breaker Opening Too Often

Symptom: Circuit keeps opening even though API is healthy

Fix: Increase failure threshold or reduce status codes that trigger it:

CircuitBreakerFailureThreshold = 10  // More tolerant

Bulkhead Rejecting Requests

Symptom: BulkheadRejectedException even with low load

Fix: Increase parallelization or queue size:

MaxParallelization = 20,
MaxQueuingActions = 50

Retries Not Happening

Symptom: Requests fail immediately without retries

Fix: Check if Circuit Breaker is open (it bypasses retry). Increase circuit duration or disable it temporarily.

Timeouts Too Aggressive

Symptom: Requests timeout even when API is responding

Fix: Increase timeout:

RequestTimeout = TimeSpan.FromMinutes(2)  // For slow operations

See Also


FEx.Flurlx - Resilient HTTP client module for the FEx framework
Built with ❤️ for production-grade API integrations

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 was computed.  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 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. 
.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 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  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 FEx.Flurlx:

Package Downloads
FEx.AzureDevOpsx

Client models and utilities for Azure DevOps / TFS projects, changesets, and builds

FEx.Flurlx.Newtonsoft

Opt-in Newtonsoft.Json serializer for FEx.Flurlx (replaces the default System.Text.Json serializer). Reference this and register FExFlurlxNewtonsoftModule instead of FExFlurlxModule.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.0-alpha.497 0 10/19/2026
0.4.0-alpha.485 31 10/18/2026
0.4.0-alpha.476 44 10/16/2026
0.4.0-alpha.466 39 10/16/2026
0.4.0-alpha.464 41 10/16/2026
0.4.0-alpha.442 50 10/14/2026
0.4.0-alpha.432 48 10/13/2026
0.4.0-alpha.411 52 10/11/2026
0.4.0-alpha.398 56 10/11/2026
0.4.0-alpha.394 58 10/11/2026
0.4.0-alpha.374 62 10/11/2026
0.4.0-alpha.360 59 10/11/2026
0.4.0-alpha.355 54 10/10/2026
0.4.0-alpha.352 54 10/10/2026
0.4.0-alpha.346 56 10/10/2026
0.4.0-alpha.341 56 10/10/2026
0.4.0-alpha.315 56 10/10/2026
0.4.0-alpha.312 56 10/10/2026
0.4.0-alpha.307 59 10/10/2026
0.3.0 177 8/13/2026
Loading failed