Real-world business examples showing when and how to use Reliable.HttpClient for different domains and use cases.
- E-commerce Platform - Payment, inventory, and recommendation APIs
- Microservices Architecture - Service-to-service communication
- External API Integration - Third-party APIs with rate limits
- Legacy System Integration - Unreliable legacy systems
- Product Catalog Service - High-performance catalog with caching
- Configuration Service - Centralized config with fallback
💡 Configuration Details: For technical configuration patterns, see Configuration Examples
Business Context: Online store with payment processing, inventory management, and personalized recommendations.
Resilience Strategy: Different reliability requirements for different business functions.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Payment API - mission critical, use aggressive resilience
services.AddHttpClient<PaymentApiClient>(c =>
{
c.BaseAddress = new Uri("https://api.payments.com");
c.DefaultRequestHeaders.Add("Authorization", "Bearer token");
})
.AddResilience(HttpClientPresets.SlowExternalApi(), options =>
{
// Customize for payments - even more aggressive
options.Retry.MaxRetries = 5;
options.CircuitBreaker.FailuresBeforeOpen = 3;
options.CircuitBreaker.OpenDuration = TimeSpan.FromMinutes(5);
});
// Inventory API - important but less critical
services.AddHttpClient<InventoryApiClient>(c =>
{
c.BaseAddress = new Uri("https://api.inventory.com");
})
.AddResilience(HttpClientPresets.FastInternalApi());
// Recommendation API - optional feature, minimal resilience
services.AddHttpClient<RecommendationApiClient>(c =>
{
c.BaseAddress = new Uri("https://api.recommendations.com");
})
.AddResilience(HttpClientPresets.RealTimeApi());
}
}Key Insights:
- Payment API: Maximum resilience - business can't afford payment failures
- Inventory API: Balanced approach - important but not mission critical
- Recommendation API: Fast-fail - won't block checkout if unavailable
Business Context: Order processing system communicating with user, notification, and analytics services.
Resilience Strategy: Service criticality determines resilience level.
public class ServiceConfiguration
{
public static void ConfigureHttpClients(IServiceCollection services, IConfiguration config)
{
// User service - authentication critical
services.AddHttpClient("user-service", c =>
{
c.BaseAddress = new Uri(config["Services:UserService:BaseUrl"]);
})
.AddResilience(HttpClientPresets.FastInternalApi());
// Notification service - important but can fail gracefully
services.AddHttpClient("notification-service", c =>
{
c.BaseAddress = new Uri(config["Services:NotificationService:BaseUrl"]);
})
.AddResilience(builder => builder
.WithRetry(retry => retry.WithMaxRetries(2))
.WithCircuitBreaker(cb => cb.WithFailureThreshold(8)));
// Analytics service - fire-and-forget
services.AddHttpClient("analytics-service", c =>
{
c.BaseAddress = new Uri(config["Services:AnalyticsService:BaseUrl"]);
})
.AddResilience(HttpClientPresets.RealTimeApi());
}
}Key Insights:
- User Service: Must work - authentication blocks everything
- Notification Service: Should work - but order can complete without it
- Analytics Service: Nice to have - fire-and-forget pattern
Business Context: Integrating with third-party APIs that have rate limits and varying reliability.
Resilience Strategy: Handle rate limits gracefully with longer delays and more tolerance.
// Rate-limited external API configuration
services.AddHttpClient<ExternalApiClient>(c =>
{
c.BaseAddress = new Uri("https://api.external.com");
c.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
})
.AddResilience(HttpClientPresets.SlowExternalApi(), options =>
{
// Customize for rate limits
options.Retry.BaseDelay = TimeSpan.FromSeconds(5); // Longer delays
options.CircuitBreaker.FailuresBeforeOpen = 8; // More tolerant
options.CircuitBreaker.OpenDuration = TimeSpan.FromMinutes(10);
});Key Insights:
- Rate Limits: Longer delays prevent hitting rate limits repeatedly
- Circuit Breaker: More tolerant threshold accounts for rate limit responses
- Recovery Time: Longer circuit breaker duration allows rate limits to reset
Business Context: Working with old, unreliable internal systems that can't be easily replaced.
Resilience Strategy: Maximum patience with graceful degradation using cache fallback.
services.AddHttpClient<LegacySystemClient>(c =>
{
c.BaseAddress = new Uri("http://legacy-system.internal");
})
.AddResilience(builder => builder
.WithTimeout(TimeSpan.FromSeconds(45)) // Legacy systems are slow
.WithRetry(retry => retry
.WithMaxRetries(6) // More retries for flaky system
.WithBaseDelay(TimeSpan.FromSeconds(3)))
.WithCircuitBreaker(cb => cb
.WithFailureThreshold(15) // Very tolerant
.WithOpenDuration(TimeSpan.FromMinutes(15))));Key Insights:
- Long Timeouts: Legacy systems need time to respond
- Many Retries: Flaky systems need multiple attempts
- High Tolerance: Circuit breaker rarely opens
- Cache Fallback: Always have backup data ready
Business Context: E-commerce product catalog with high traffic and performance requirements.
Caching Strategy: Different cache durations based on data volatility.
services.AddMemoryCache();
// Individual products - longer cache (products don't change often)
services.AddHttpClient<ProductCatalogService>("products")
.AddResilience(HttpClientPresets.FastInternalApi())
.AddMemoryCache<Product>(options =>
{
options.DefaultExpiry = TimeSpan.FromHours(1);
options.VaryByHeaders = new[] { "Accept-Language", "Currency" };
options.MaxCacheSize = 5000;
});
// Product lists - shorter cache (inventory changes more frequently)
services.AddHttpClient<ProductCatalogService>("catalog")
.AddResilience(HttpClientPresets.FastInternalApi())
.AddMemoryCache<ProductList>(options =>
{
options.DefaultExpiry = TimeSpan.FromMinutes(10);
options.VaryByHeaders = new[] { "Accept-Language" };
});Key Insights:
- Product Data: Cache longer - product details rarely change
- Product Lists: Cache shorter - inventory levels change frequently
- Localization: Cache varies by language and currency
- Cache Invalidation: Manual invalidation for immediate updates
Business Context: Centralized configuration service with fallback for system resilience.
Resilience Strategy: Cache successful responses as emergency fallback data.
services.AddMemoryCache();
services.AddHttpClient<ConfigurationService>()
.AddResilience(HttpClientPresets.FastInternalApi())
.AddMemoryCache<AppConfig>(options =>
{
options.DefaultExpiry = TimeSpan.FromMinutes(30);
options.RespectCacheControlHeaders = true;
});Key Insights:
- Configuration: Cache for 30 minutes with HTTP header respect
- Fallback Strategy: Store successful responses for emergency use
- Auto-refresh: Respect Cache-Control headers from server
- Multiple Environments: Different cache keys per environment
Business Context: CRM system that handles multiple entity types (Leads, Contacts, Companies, Orders, Products, etc.) through a REST API.
Challenge: Traditional approach requires separate handler registrations for each entity type, leading to "Generic Hell" with 15+ DI registrations.
Solution: Use universal response handlers to eliminate boilerplate and simplify architecture.
// Traditional approach - lots of registrations
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Need separate handler for each entity type
services.AddSingleton<IHttpResponseHandler<Lead>, JsonResponseHandler<Lead>>();
services.AddSingleton<IHttpResponseHandler<Contact>, JsonResponseHandler<Contact>>();
services.AddSingleton<IHttpResponseHandler<Company>, JsonResponseHandler<Company>>();
services.AddSingleton<IHttpResponseHandler<Order>, JsonResponseHandler<Order>>();
services.AddSingleton<IHttpResponseHandler<Product>, JsonResponseHandler<Product>>();
services.AddSingleton<IHttpResponseHandler<User>, JsonResponseHandler<User>>();
services.AddSingleton<IHttpResponseHandler<Invoice>, JsonResponseHandler<Invoice>>();
services.AddSingleton<IHttpResponseHandler<Campaign>, JsonResponseHandler<Campaign>>();
services.AddSingleton<IHttpResponseHandler<Deal>, JsonResponseHandler<Deal>>();
services.AddSingleton<IHttpResponseHandler<Task>, JsonResponseHandler<Task>>();
// ... 15+ registrations total
services.AddHttpClient<CrmApiClient>(c =>
{
c.BaseAddress = new Uri("https://api.crm.com");
c.DefaultRequestHeaders.Add("Authorization", "Bearer token");
})
.AddResilience(HttpClientPresets.SlowExternalApi());
}
}
// Overloaded constructor with many dependencies
public class CrmApiClient
{
public CrmApiClient(
HttpClient httpClient,
IHttpResponseHandler<Lead> leadHandler,
IHttpResponseHandler<Contact> contactHandler,
IHttpResponseHandler<Company> companyHandler,
IHttpResponseHandler<Order> orderHandler,
IHttpResponseHandler<Product> productHandler,
IHttpResponseHandler<User> userHandler,
IHttpResponseHandler<Invoice> invoiceHandler,
IHttpResponseHandler<Campaign> campaignHandler,
IHttpResponseHandler<Deal> dealHandler,
IHttpResponseHandler<Task> taskHandler,
// ... more handlers
ILogger<CrmApiClient> logger)
{
// Constructor becomes unmanageable
}
}// Clean approach - single registration
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Universal HTTP client with caching
services.AddHttpClientWithCache(options =>
{
options.DefaultExpiry = TimeSpan.FromMinutes(10);
});
// Configure the HttpClient
services.AddHttpClient(c =>
{
c.BaseAddress = new Uri("https://api.crm.com");
c.DefaultRequestHeaders.Add("Authorization", "Bearer token");
})
.AddResilience(HttpClientPresets.SlowExternalApi());
});
// Register API client
services.AddScoped<ICrmApiClient, CrmApiClient>();
}
}
// Clean constructor with minimal dependencies
public interface ICrmApiClient
{
Task<Lead> GetLeadAsync(int id);
Task<Contact> GetContactAsync(int id);
Task<Company> GetCompanyAsync(int id);
Task<Order> CreateOrderAsync(CreateOrderRequest request);
Task<Product> UpdateProductAsync(int id, UpdateProductRequest request);
Task<bool> DeleteLeadAsync(int id);
Task ClearLeadCacheAsync();
}
public class CrmApiClient : ICrmApiClient
{
private readonly IHttpClientWithCache _httpClient;
private readonly ILogger<CrmApiClient> _logger;
public CrmApiClient(IHttpClientWithCache httpClient, ILogger<CrmApiClient> logger)
{
_httpClient = httpClient;
_logger = logger;
}
// Clean, elegant methods for any entity type
public async Task<Lead> GetLeadAsync(int id)
{
try
{
return await _httpClient.GetAsync<Lead>(
$"/api/leads/{id}",
TimeSpan.FromMinutes(5)); // Cached for 5 minutes
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Failed to get lead {LeadId}", id);
throw;
}
}
public async Task<Contact> GetContactAsync(int id) =>
await _httpClient.GetAsync<Contact>($"/api/contacts/{id}", TimeSpan.FromMinutes(5));
public async Task<Company> GetCompanyAsync(int id) =>
await _httpClient.GetAsync<Company>($"/api/companies/{id}", TimeSpan.FromMinutes(10));
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
// POST requests automatically invalidate related cache entries
return await _httpClient.PostAsync<CreateOrderRequest, Order>("/api/orders", request);
}
public async Task<Product> UpdateProductAsync(int id, UpdateProductRequest request)
{
// PUT requests also invalidate cache
return await _httpClient.PutAsync<UpdateProductRequest, Product>($"/api/products/{id}", request);
}
public async Task<bool> DeleteLeadAsync(int id)
{
try
{
var response = await _httpClient.DeleteAsync<ApiResponse>($"/api/leads/{id}");
return response.Success;
}
catch (HttpRequestException ex) when (ex.Message.Contains("404"))
{
return true; // Already deleted
}
}
public async Task ClearLeadCacheAsync()
{
await _httpClient.InvalidateCacheAsync("/api/leads");
}
}// Standard DTOs - no special attributes needed
public record Lead(int Id, string Name, string Email, string Company, string Status);
public record Contact(int Id, string FirstName, string LastName, string Email, string Phone);
public record Company(int Id, string Name, string Website, string Industry);
public record Order(int Id, int CustomerId, decimal Amount, DateTime OrderDate, string Status);
public record Product(int Id, string Name, decimal Price, string Category, bool InStock);
public record CreateOrderRequest(int CustomerId, OrderItem[] Items);
public record UpdateProductRequest(string Name, decimal Price, bool InStock);
public record ApiResponse(bool Success, string Message);public class LeadService
{
private readonly ICrmApiClient _crmClient;
public LeadService(ICrmApiClient crmClient)
{
_crmClient = crmClient;
}
public async Task<LeadSummary> GetLeadSummaryAsync(int leadId)
{
// All these calls benefit from caching and resilience automatically
var lead = await _crmClient.GetLeadAsync(leadId);
var company = await _crmClient.GetCompanyAsync(lead.CompanyId);
var orders = await _crmClient.GetLeadOrdersAsync(leadId);
return new LeadSummary(lead, company, orders);
}
public async Task<Order> ConvertLeadToOrderAsync(int leadId, CreateOrderRequest request)
{
// This will automatically invalidate lead cache
var order = await _crmClient.CreateOrderAsync(request);
// Clear lead cache since conversion changes lead status
await _crmClient.ClearLeadCacheAsync();
return order;
}
}Key Benefits:
- Reduced Lines of Code: From 1000+ to ~300 lines (-70%)
- Fewer DI Registrations: From 15+ to 1 registration (-93%)
- Simpler Constructor: From 7+ dependencies to 2 dependencies (-70%)
- Easier Testing: From 15+ mocks to 1-2 mocks (-80%)
- Automatic Caching: GET requests cached, mutations invalidate cache
- Universal Pattern: Works with any REST API and any entity types
Key Insights:
- Scalability: Pattern works for any number of entity types
- Cache Strategy: GET operations cached, POST/PUT/DELETE operations invalidate
- Error Handling: Centralized error handling with specific business logic
- Testing: Much simpler unit testing with fewer mocks
- Migration: 100% backward compatible with existing
IHttpResponseHandler<T>
Business Context: High-performance e-commerce catalog serving thousands of product requests per second.
Caching Strategy: Type-safe generic caching for known product types with optimized serialization.
using Reliable.HttpClient.Caching.Generic;
using Reliable.HttpClient.Caching.Generic.Extensions;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Product API - high performance with type-safe caching
services.AddHttpClient<ProductApiClient>()
.AddResilience(HttpClientPresets.FastInternalApi())
.AddGenericMemoryCache<Product>(options =>
{
options.DefaultExpiry = TimeSpan.FromMinutes(15);
options.MaxCacheSize = 10000; // Large cache for popular products
});
// Category API - longer cache for stable data
services.AddGenericHttpClientCaching<Category>(options =>
{
options.DefaultExpiry = TimeSpan.FromHours(2);
});
// Inventory API - short cache for dynamic data
services.AddGenericHttpClientCaching<InventoryLevel>(options =>
{
options.DefaultExpiry = TimeSpan.FromMinutes(1);
});
}
}
// Type-safe product service
public class ProductService
{
private readonly CachedHttpClient<Product> _productClient;
private readonly CachedHttpClient<Category> _categoryClient;
private readonly CachedHttpClient<InventoryLevel> _inventoryClient;
public ProductService(
CachedHttpClient<Product> productClient,
CachedHttpClient<Category> categoryClient,
CachedHttpClient<InventoryLevel> inventoryClient)
{
_productClient = productClient;
_categoryClient = categoryClient;
_inventoryClient = inventoryClient;
}
public async Task<ProductDetails> GetProductDetailsAsync(int productId)
{
// All requests are cached with optimal serialization
var product = await _productClient.GetFromJsonAsync($"/products/{productId}");
var category = await _categoryClient.GetFromJsonAsync($"/categories/{product.CategoryId}");
var inventory = await _inventoryClient.GetFromJsonAsync($"/inventory/{productId}");
return new ProductDetails(product, category, inventory);
}
}
// Well-defined DTOs for optimal caching
public record Product(int Id, string Name, decimal Price, int CategoryId, string Description);
public record Category(int Id, string Name, string Description);
public record InventoryLevel(int ProductId, int Available, int Reserved);
public record ProductDetails(Product Product, Category Category, InventoryLevel Inventory);Key Benefits:
- Type Safety: Compile-time checking for all cached responses
- Performance: Optimized serialization without boxing/unboxing
- Memory Efficiency: Type-specific cache storage
- Scalability: Handles high-traffic scenarios efficiently
Business Context: Centralized configuration service providing application settings with fallback support.
Caching Strategy: Universal caching for flexible configuration types with long cache durations.
using Reliable.HttpClient.Caching;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Configuration API - universal caching for multiple config types
services.AddHttpClientWithCache(options =>
{
options.DefaultExpiry = TimeSpan.FromMinutes(30); // Long cache for stable config
});
services.AddSingleton<IConfigurationService, ConfigurationService>();
}
}
// Universal configuration service
public class ConfigurationService : IConfigurationService
{
private readonly IHttpClientWithCache _client;
private readonly ILogger<ConfigurationService> _logger;
public ConfigurationService(IHttpClientWithCache client, ILogger<ConfigurationService> logger)
{
_client = client;
_logger = logger;
}
public async Task<T> GetConfigAsync<T>(string key, T fallback = default) where T : class
{
try
{
return await _client.GetAsync<T>($"/config/{key}");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get config {Key}, using fallback", key);
return fallback ?? throw new ConfigurationException($"No fallback for {key}");
}
}
public async Task<bool> UpdateConfigAsync<T>(string key, T value) where T : class
{
try
{
await _client.PostAsync<T, object>($"/config/{key}", value);
// Invalidate cache after update
await _client.InvalidateCacheAsync($"*{key}*");
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to update config {Key}", key);
return false;
}
}
}
// Usage in business services
public class EmailService
{
private readonly IConfigurationService _config;
public EmailService(IConfigurationService config)
{
_config = config;
}
public async Task SendEmailAsync(string to, string subject, string body)
{
// Configuration cached for 30 minutes
var smtpConfig = await _config.GetConfigAsync<SmtpConfig>("smtp", new SmtpConfig
{
Host = "localhost",
Port = 587,
EnableSsl = false
});
// Use configuration...
}
}
public record SmtpConfig(string Host, int Port, bool EnableSsl, string Username, string Password);
public record DatabaseConfig(string ConnectionString, int CommandTimeout, int MaxRetries);
public record FeatureFlags(bool EnableNewUi, bool EnableBetaFeatures, string ApiVersion);Key Benefits:
- Flexibility: Works with any configuration type
- Fallback Support: Graceful degradation when config service is unavailable
- Cache Invalidation: Automatic cache refresh on configuration updates
- Simple Integration: Easy to use across all application services
Business Context: API client that needs to handle OAuth authentication with token refresh and per-request authorization.
Challenge: Different requests need different tokens (user tokens, service tokens) and request-specific headers for tracing.
public interface IOAuthTokenProvider
{
Task<string> GetUserTokenAsync(string userId);
Task<string> GetServiceTokenAsync();
Task<string> RefreshTokenAsync(string refreshToken);
}
public class UserApiClient
{
private readonly IHttpClientAdapter _client;
private readonly IOAuthTokenProvider _tokenProvider;
private readonly ILogger<UserApiClient> _logger;
public UserApiClient(IHttpClientAdapter client, IOAuthTokenProvider tokenProvider, ILogger<UserApiClient> logger)
{
_client = client;
_tokenProvider = tokenProvider;
_logger = logger;
}
public async Task<UserProfile> GetUserProfileAsync(string userId, string? requestId = null)
{
var token = await _tokenProvider.GetUserTokenAsync(userId);
var headers = new Dictionary<string, string>
{
{ "Authorization", $"Bearer {token}" },
{ "X-Request-Id", requestId ?? Guid.NewGuid().ToString() },
{ "X-User-Context", userId }
};
_logger.LogInformation("Fetching user profile for {UserId}", userId);
return await _client.GetAsync<UserProfile>($"/users/{userId}/profile", headers);
}
public async Task<UserProfile> UpdateUserProfileAsync(string userId, UpdateProfileRequest request)
{
var token = await _tokenProvider.GetUserTokenAsync(userId);
var headers = new Dictionary<string, string>
{
{ "Authorization", $"Bearer {token}" },
{ "X-Request-Id", Guid.NewGuid().ToString() },
{ "X-User-Context", userId },
{ "X-Action", "profile-update" }
};
return await _client.PutAsync<UpdateProfileRequest, UserProfile>($"/users/{userId}/profile", request, headers);
}
public async Task<IEnumerable<User>> SearchUsersAsync(string query, int page = 1, int pageSize = 20)
{
// Service-to-service call with service token
var serviceToken = await _tokenProvider.GetServiceTokenAsync();
var headers = new Dictionary<string, string>
{
{ "Authorization", $"Bearer {serviceToken}" },
{ "X-Request-Id", Guid.NewGuid().ToString() },
{ "X-Page", page.ToString() },
{ "X-Page-Size", pageSize.ToString() }
};
var response = await _client.GetAsync<SearchResponse<User>>($"/users/search?q={query}&page={page}&size={pageSize}", headers);
return response.Results;
}
}
// Registration
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IOAuthTokenProvider, OAuthTokenProvider>();
services.AddHttpClient<UserApiClient>(c =>
{
c.BaseAddress = new Uri("https://api.users.com");
})
.AddResilience(builder => builder
.WithHeader("Accept", "application/json")
.WithHeader("X-Client-Id", "my-service")
.WithUserAgent("MyService/1.0")
.WithRetry(retry => retry
.WithMaxRetries(3)
.WithBaseDelay(TimeSpan.FromSeconds(1)))
.WithCircuitBreaker(cb => cb
.WithFailureThreshold(5)
.WithOpenDuration(TimeSpan.FromMinutes(2))));
services.AddScoped<IHttpClientAdapter, HttpClientAdapter>();
}
}Key Benefits:
- Dynamic token handling per request
- Request tracing with unique IDs
- User context preservation
- Service vs user token separation
Business Context: SaaS application serving multiple tenants with tenant-specific configurations and headers.
public class TenantApiClient
{
private readonly IHttpClientAdapter _client;
private readonly ITenantContext _tenantContext;
private readonly ILogger<TenantApiClient> _logger;
public TenantApiClient(IHttpClientAdapter client, ITenantContext tenantContext, ILogger<TenantApiClient> logger)
{
_client = client;
_tenantContext = tenantContext;
_logger = logger;
}
public async Task<TenantData> GetTenantDataAsync(string dataId)
{
var tenant = _tenantContext.CurrentTenant;
var headers = CreateTenantHeaders(tenant);
return await _client.GetAsync<TenantData>($"/data/{dataId}", headers);
}
public async Task<TenantSettings> UpdateTenantSettingsAsync(TenantSettings settings)
{
var tenant = _tenantContext.CurrentTenant;
var headers = CreateTenantHeaders(tenant);
headers["X-Action"] = "settings-update";
headers["X-Version"] = settings.Version.ToString();
return await _client.PutAsync<TenantSettings, TenantSettings>($"/tenants/{tenant.Id}/settings", settings, headers);
}
private Dictionary<string, string> CreateTenantHeaders(Tenant tenant)
{
return new Dictionary<string, string>
{
{ "X-Tenant-Id", tenant.Id.ToString() },
{ "X-Tenant-Tier", tenant.Tier.ToString() },
{ "X-Region", tenant.Region },
{ "X-Request-Id", Guid.NewGuid().ToString() },
{ "Authorization", $"Bearer {tenant.ApiKey}" }
};
}
}Business Context: Client that needs to support multiple API versions with version-specific headers and content negotiation.
public class VersionedApiClient
{
private readonly IHttpClientAdapter _client;
private readonly ILogger<VersionedApiClient> _logger;
public VersionedApiClient(IHttpClientAdapter client, ILogger<VersionedApiClient> logger)
{
_client = client;
_logger = logger;
}
public async Task<ProductV1> GetProductV1Async(int productId)
{
var headers = new Dictionary<string, string>
{
{ "Accept", "application/vnd.api.v1+json" },
{ "X-API-Version", "1.0" }
};
return await _client.GetAsync<ProductV1>($"/products/{productId}", headers);
}
public async Task<ProductV2> GetProductV2Async(int productId)
{
var headers = new Dictionary<string, string>
{
{ "Accept", "application/vnd.api.v2+json" },
{ "X-API-Version", "2.0" },
{ "X-Features", "enhanced-metadata,pricing-tiers" }
};
return await _client.GetAsync<ProductV2>($"/products/{productId}", headers);
}
public async Task<ProductV3> GetProductV3Async(int productId, bool includeRecommendations = false)
{
var headers = new Dictionary<string, string>
{
{ "Accept", "application/vnd.api.v3+json" },
{ "X-API-Version", "3.0" }
};
if (includeRecommendations)
{
headers["X-Include"] = "recommendations,related-products";
}
return await _client.GetAsync<ProductV3>($"/products/{productId}", headers);
}
// Generic version for testing new API versions
public async Task<T> GetWithVersionAsync<T>(string endpoint, string version, Dictionary<string, string>? additionalHeaders = null)
{
var headers = new Dictionary<string, string>
{
{ "Accept", $"application/vnd.api.v{version}+json" },
{ "X-API-Version", version },
{ "X-Client", "VersionedClient/1.0" }
};
if (additionalHeaders is not null)
{
foreach (var header in additionalHeaders)
{
headers[header.Key] = header.Value;
}
}
return await _client.GetAsync<T>(endpoint, headers);
}
}Each business scenario requires different resilience and caching strategies:
| Scenario | Primary Concern | Recommended Preset | Caching Approach |
|---|---|---|---|
| E-commerce Payments | Zero downtime | SlowExternalApi() |
No caching (critical data) |
| Microservices | Service isolation | FastInternalApi() |
Vary by service criticality |
| External APIs | Rate limit handling | SlowExternalApi() |
Longer cache (reduce calls) |
| Legacy Systems | Maximum patience | Custom builder | Aggressive caching |
| Product Catalog | Performance | FastInternalApi() + Generic |
Type-safe, optimized cache |
| Configuration | System stability | FastInternalApi() + Universal |
Flexible, fallback support |
| Universal REST API | Maintainability | Universal handlers | Single registration pattern |
| OAuth API Client | Dynamic auth | Custom with IHttpClientAdapter |
Token-aware per request |
| Multi-Tenant | Tenant isolation | Headers per tenant | Tenant-specific caching |
| API Versioning | Version flexibility | Version-specific headers | Version-aware cache keys |
💡 Next Steps: See Configuration Examples for detailed configuration patterns and techniques.