Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,8 +1,54 @@
<Project>
<PropertyGroup>
<LangVersion>12.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>

<!-- Common properties for test projects -->
<PropertyGroup Condition="'$(IsTestProject)' == 'true' or $(MSBuildProjectName.Contains('Tests'))">
<TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<!-- Common properties for library projects -->
<PropertyGroup
Condition="'$(IsTestProject)' != 'true' and !$(MSBuildProjectName.Contains('Tests'))">
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<!-- Code analyzers for all projects -->
<PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.8.2-beta.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.219">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<!-- Common test packages for all test projects -->
<ItemGroup Condition="'$(IsTestProject)' == 'true' or $(MSBuildProjectName.Contains('Tests'))">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="Moq" Version="4.20.72" />
</ItemGroup>

<!-- Source Link только для библиотек -->
<ItemGroup Condition="!$(MSBuildProjectName.Contains('Tests')) and '$(IsTestProject)' != 'true'">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>
171 changes: 43 additions & 128 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@
A comprehensive resilience and caching ecosystem for HttpClient with built-in retry policies, circuit breakers, and intelligent response caching.
Based on [Polly](https://github.com/App-vNext/Polly) but with zero configuration required.

## 🎯 Choose Your Approach

**Not sure which approach to use?** [→ Read our Choosing Guide](docs/choosing-approach.md)

| Your Use Case | Recommended Approach | Documentation |
|---------------|---------------------|---------------|
| **Single API with 1-2 entity types** | Traditional Generic | [Getting Started](docs/getting-started.md) |
| **REST API with 5+ entity types** | Universal Handlers | [Common Scenarios - Universal REST API](docs/examples/common-scenarios.md#universal-rest-api-client) |
| **Need HttpClient substitution** | IHttpClientAdapter | [HttpClient Substitution](docs/examples/http-client-substitution.md) |
| **Custom serialization/error handling** | Custom Response Handler | [Advanced Usage](docs/advanced-usage.md) |

## Packages

| Package | Purpose | Version |
Expand All @@ -39,161 +50,65 @@ Based on [Polly](https://github.com/App-vNext/Polly) but with zero configuration

## Quick Start

### 1️⃣ Install & Add Resilience (2 lines of code)

```bash
dotnet add package Reliable.HttpClient
```

```csharp
builder.Services.AddHttpClient<WeatherApiClient>(c =>
{
c.BaseAddress = new Uri("https://api.weather.com");
})
.AddResilience(); // ✨ That's it! Zero configuration needed
```

**You now have:**

- Automatic retries (3 attempts with smart backoff)
- Circuit breaker (prevents cascading failures)
- Smart error handling (5xx, timeouts, rate limits)

### 2️⃣ Add Caching (Optional)

Want to cache responses? Add one more package and line:

```bash
dotnet add package Reliable.HttpClient.Caching
```

```csharp
builder.Services.AddMemoryCache(); // Standard .NET caching

builder.Services.AddHttpClient<WeatherApiClient>(c =>
{
c.BaseAddress = new Uri("https://api.weather.com");
})
.AddResilience()
.AddMemoryCache<WeatherResponse>(); // ✨ Intelligent caching added!
```

**Now you also have:**

- Automatic response caching (5-minute default)
- Smart cache keys (collision-resistant SHA256)
- Manual cache invalidation

### 3️⃣ Use Your Client
// Add to your Program.cs
builder.Services.AddHttpClient<ApiClient>(c => c.BaseAddress = new Uri("https://api.example.com"))
.AddResilience(); // That's it! ✨

```csharp
public class WeatherService
// Use anywhere
public class ApiClient(HttpClient client)
{
private readonly HttpClient _client;

public WeatherService(IHttpClientFactory factory)
{
_client = factory.CreateClient<WeatherApiClient>();
}

public async Task<WeatherResponse> GetWeatherAsync(string city)
{
// This call now has retry, circuit breaker, AND caching!
var response = await _client.GetAsync($"/weather?city={city}");
return await response.Content.ReadFromJsonAsync<WeatherResponse>();
}
public async Task<Data> GetDataAsync() =>
await client.GetFromJsonAsync<Data>("/endpoint");
}
```

> 🎯 **That's it!** You're production-ready with 2-3 lines of configuration.
**You now have:** Automatic retries + Circuit breaker + Smart error handling

## What You Get
> 🚀 **Need details?** See [Getting Started Guide](docs/getting-started.md) for step-by-step setup
> 🆕 **Building REST APIs?** Check [Universal Response Handlers](docs/examples/common-scenarios.md#universal-rest-api-client)
> 🔄 **Need substitution patterns?** See [HttpClient Substitution Guide](docs/examples/http-client-substitution.md)

- ✅ **Retry Policy**: 3 attempts with exponential backoff + jitter
- ✅ **Circuit Breaker**: Opens after 5 failures, stays open for 1 minute
- ✅ **Smart Error Handling**: Retries on 5xx, 408, 429, and network errors
- ✅ **HTTP Response Caching**: 5-minute default expiry with SHA256 cache keys
- ✅ **Multiple Configuration Options**: Zero-config, presets, or custom setup
- ✅ **Production Ready**: Used by companies in production environments
## Key Features

> 📖 **See [Key Features Table](docs/README.md#key-features) for complete feature comparison**
✅ **Zero Configuration** - Works out of the box
✅ **Resilience** - Retry + Circuit breaker
✅ **Caching** - Intelligent HTTP response caching
✅ **Universal Handlers** - Non-generic response handling for REST APIs
✅ **HttpClient Substitution** - Switch between cached/non-cached implementations
✅ **Production Ready** - Used by companies in production

## Advanced Configuration (Optional)
> 📖 **Full Feature List**: [Documentation](docs/README.md#key-features)

Need custom settings? Multiple ways to configure:
## Need Customization?

```csharp
// Option 1: Traditional configuration
builder.Services.AddHttpClient<ApiClient>()
.AddResilience(options => options.Retry.MaxRetries = 5);

// Option 2: Fluent builder
builder.Services.AddHttpClient<ApiClient>()
.AddResilience(builder => builder.WithRetry(r => r.WithMaxRetries(5)));
// Custom settings
.AddResilience(options => options.Retry.MaxRetries = 5);

// Option 3: Ready-made presets
builder.Services.AddHttpClient<ApiClient>()
.AddResilience(HttpClientPresets.SlowExternalApi());
// Ready-made presets
.AddResilience(HttpClientPresets.SlowExternalApi());
```

> 📖 **See [Configuration Guide](docs/configuration.md) for complete configuration options**## Trusted By
> 📖 **Full Configuration**: [Configuration Guide](docs/configuration.md)

## Trusted By

Organizations using Reliable.HttpClient in production:

[![PlanFact](https://raw.githubusercontent.com/akrisanov/Reliable.HttpClient/refs/heads/main/docs/assets/logos/planfact.png)](https://planfact.io)

## Documentation

- [Getting Started Guide](docs/getting-started.md) - Quick setup and basic usage
- [Configuration Reference](docs/configuration.md) - Complete options reference
- [Advanced Usage](docs/advanced-usage.md) - Advanced patterns and techniques
- [HTTP Caching Guide](docs/caching.md) - Complete caching documentation
- [Common Scenarios](docs/examples/common-scenarios.md) - Real-world examples
- [Complete Feature List](docs/README.md#key-features) - Detailed feature comparison

## Complete Example

Here's a complete working example showing both packages in action:

### The Service

```csharp
public class WeatherService
{
private readonly HttpClient _httpClient;

public WeatherService(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient<WeatherApiClient>();
}

public async Task<WeatherData> GetWeatherAsync(string city)
{
// This call has retry, circuit breaker, AND caching automatically!
var response = await _httpClient.GetAsync($"/weather?city={city}");
response.EnsureSuccessStatusCode();

return await response.Content.ReadFromJsonAsync<WeatherData>();
}
}
```

### The Registration

```csharp
// In Program.cs
services.AddMemoryCache();

services.AddHttpClient<WeatherApiClient>(c =>
{
c.BaseAddress = new Uri("https://api.weather.com");
c.DefaultRequestHeaders.Add("API-Key", "your-key");
})
.AddResilience() // Retry + Circuit breaker
.AddMemoryCache<WeatherData>(); // Response caching
```

**That's it!** Production-ready HTTP client with resilience and caching in just a few lines. 🚀
- [Getting Started Guide](docs/getting-started.md) - Step-by-step setup
- [Common Scenarios](docs/examples/common-scenarios.md) - Real-world examples 🆕
- [Configuration Reference](docs/configuration.md) - Complete options
- [Advanced Usage](docs/advanced-usage.md) - Advanced patterns
- [HTTP Caching Guide](docs/caching.md) - Caching documentation

## Contributing

Expand Down
4 changes: 2 additions & 2 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ coverage:
status:
project:
default:
target: 80%
target: 50%
threshold: 1%
patch:
default:
target: 80%
target: 50%
threshold: 1%

ignore:
Expand Down
53 changes: 52 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,51 @@ Welcome to the comprehensive documentation for Reliable.HttpClient - a complete
| **Reliable.HttpClient** | Core resilience (retry + circuit breaker) | Core features documented below |
| **Reliable.HttpClient.Caching** | HTTP response caching extension | [Caching Guide](caching.md) |

## What's New in v1.0+
## What's New in v1.1+

### ✨ Universal Response Handlers

Eliminate "Generic Hell" for REST APIs with many entity types:

```csharp
// Before: Multiple registrations per entity type
services.AddSingleton<IHttpResponseHandler<User>, JsonResponseHandler<User>>();
services.AddSingleton<IHttpResponseHandler<Order>, JsonResponseHandler<Order>>();
// ... many more

// After: One registration for all entity types
services.AddHttpClientWithCache();

public class ApiClient(IHttpClientWithCache client)
{
public async Task<User> GetUserAsync(int id) =>
await client.GetAsync<User>($"/users/{id}");

public async Task<Order> GetOrderAsync(int id) =>
await client.GetAsync<Order>($"/orders/{id}");
// Works with any entity type!
}
```

### 🔄 HttpClient Substitution Pattern

Seamlessly switch between cached and non-cached implementations:

```csharp
// Base client using adapter interface
public class ApiClient(IHttpClientAdapter client)
{
public async Task<T> GetAsync<T>(string endpoint) =>
await client.GetAsync<T>(endpoint);
}

// Cached version inherits everything, adds caching
public class CachedApiClient : ApiClient
{
public CachedApiClient(IHttpClientWithCache client) : base(client) { }
// Automatic caching + cache invalidation methods
}
```

### ✨ Fluent Configuration API

Expand All @@ -32,9 +76,16 @@ services.AddHttpClient<ApiClient>()
### Getting Started

- [Quick Start Guide](getting-started.md) - Get up and running in minutes
- [Choosing the Right Approach](choosing-approach.md) - **NEW!** Which pattern to use when
- [Installation & Setup](getting-started.md#installation) - Package installation and basic configuration
- [First Steps](getting-started.md#basic-setup) - Your first resilient HttpClient

### Architecture Patterns

- [Universal Response Handlers](examples/common-scenarios.md#universal-rest-api-client) - **NEW!** For REST APIs with many entity types
- [HttpClient Substitution](examples/http-client-substitution.md) - **NEW!** Inheritance-friendly patterns
- [Traditional Generic Approach](getting-started.md) - Maximum type safety and control

### Configuration

- [Configuration Reference](configuration.md) - Complete options reference including new Builder API
Expand Down
2 changes: 1 addition & 1 deletion docs/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ public class MonitoredCacheProvider<T> : IHttpResponseCache<T>
{
var result = await _innerCache.GetAsync(key);

if (result != null)
if (result is not null)
{
_logger.LogInformation("Cache hit for key: {Key}", key);
}
Expand Down
Loading