Myth.Rest
3.0.3
dotnet add package Myth.Rest --version 3.0.3
NuGet\Install-Package Myth.Rest -Version 3.0.3
<PackageReference Include="Myth.Rest" Version="3.0.3" />
<PackageVersion Include="Myth.Rest" Version="3.0.3" />
<PackageReference Include="Myth.Rest" />
paket add Myth.Rest --version 3.0.3
#r "nuget: Myth.Rest, 3.0.3"
#:package Myth.Rest@3.0.3
#addin nuget:?package=Myth.Rest&version=3.0.3
#tool nuget:?package=Myth.Rest&version=3.0.3
Myth.Rest
A powerful .NET library for consuming REST APIs with a fluent, chainable interface. Built with enterprise-grade features including advanced retry policies, circuit breakers, dependency injection, and comprehensive error handling.
⭐ Features
- Fluent Interface: Simple, chainable API design
- Advanced Retry Policies: Exponential backoff, jitter, custom strategies
- Circuit Breaker: Prevent cascading failures in distributed systems
- Dependency Injection: Full ASP.NET Core DI integration
- Factory Pattern: Manage multiple API configurations
- File Operations: Built-in support for uploads and downloads
- Logging Integration: Structured logging with Microsoft.Extensions.Logging
- Type Safety: Strong typing with automatic serialization/deserialization
- Fallback Support: Graceful degradation with custom fallback responses
- Exception-Oriented: Clear error handling and custom exceptions
📦 Installation
dotnet add package Myth.Rest
🚀 Quick Start
Basic Usage
var response = await Rest
.Create()
.Configure(config => config
.WithBaseUrl("https://api.example.com")
.WithBearerAuthorization("your-token")
.WithRetry())
.DoGet("users")
.OnResult(result => result
.UseTypeForSuccess<List<User>>())
.OnError(error => error
.ThrowForNonSuccess())
.BuildAsync();
var users = response.GetAs<List<User>>();
Dependency Injection Setup
Program.cs (Minimal API)
builder.Services.AddRest(config => config
.WithBaseUrl("https://api.example.com")
.WithBearerAuthorization("token")
.WithRetry());
// Or use the factory pattern for multiple configurations
builder.Services.AddRestFactory()
.AddRestConfiguration("api1", config => config
.WithBaseUrl("https://api1.example.com")
.WithBearerAuthorization("token1"))
.AddRestConfiguration("api2", config => config
.WithBaseUrl("https://api2.example.com")
.WithBasicAuthorization("user", "pass"));
Using in Controllers/Services
public class UserService
{
private readonly IRestRequest _restClient;
private readonly IRestFactory _restFactory;
public UserService(IRestRequest restClient, IRestFactory restFactory)
{
_restClient = restClient;
_restFactory = restFactory;
}
public async Task<List<User>> GetUsersAsync()
{
var response = await _restClient
.DoGet("users")
.OnResult(r => r.UseTypeForSuccess<List<User>>())
.OnError(e => e.ThrowForNonSuccess())
.BuildAsync();
return response.GetAs<List<User>>();
}
public async Task<Product[]> GetProductsFromApi2Async()
{
var response = await _restFactory
.Create("api2") // Uses named configuration
.DoGet("products")
.OnResult(r => r.UseTypeForSuccess<Product[]>())
.BuildAsync();
return response.GetAs<Product[]>();
}
}
🔧 Configuration
Basic Configuration
.Configure(config => config
.WithBaseUrl("https://api.example.com")
.WithTimeout(TimeSpan.FromSeconds(30))
.WithContentType("application/json")
.WithBearerAuthorization("your-bearer-token")
.WithHeader("X-Custom-Header", "value")
.WithBodySerialization(CaseStrategy.CamelCase)
.WithBodyDeserialization(CaseStrategy.SnakeCase))
Advanced Configuration
Custom HttpClient
.Configure(config => config
.WithClient(customHttpClient)
// or
.WithHttpClientFactory(httpClientFactory, "named-client"))
Type Converters
.Configure(config => config
.WithTypeConverter<IUserRepository, UserRepository>()) // Interface to concrete type mapping
Logging Integration
.Configure(config => config
.WithLogging(logger, logRequests: true, logResponses: true))
Authorization Methods
.WithAuthorization(scheme, token)
: Custom authorization header.WithBearerAuthorization(token)
: Bearer token authentication.WithBasicAuthorization(username, password)
: Basic authentication (auto-encoded).WithBasicAuthorization(encodedToken)
: Basic authentication with pre-encoded token
🔄 Retry Policies
The library provides enterprise-grade retry mechanisms following industry standards.
Smart Default (Recommended)
.WithRetry() // 3 attempts, exponential backoff with jitter, server errors only
Custom Retry Strategies
Exponential Backoff with Jitter (Recommended)
.WithRetry(retry => retry
.WithMaxAttempts(5)
.UseExponentialBackoffWithJitter(
baseDelay: TimeSpan.FromSeconds(1),
multiplier: 2.0,
maxDelay: TimeSpan.FromSeconds(30),
jitterRange: TimeSpan.FromMilliseconds(100))
.ForServerErrors()
.ForExceptions(typeof(TaskCanceledException)))
Other Strategies
Exponential Backoff
.UseExponentialBackoff(TimeSpan.FromSeconds(1), multiplier: 2.0)
Fixed Delay
.UseFixedDelay(TimeSpan.FromSeconds(2))
Random Delay
.UseRandom(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5))
Retry Configuration Options
.WithRetry(retry => retry
.WithMaxAttempts(3)
.ForServerErrors() // 5xx and 429 status codes
.ForStatusCodes(HttpStatusCode.BadGateway, HttpStatusCode.ServiceUnavailable)
.ForExceptions(typeof(HttpRequestException), typeof(TaskCanceledException)))
⚡ Circuit Breaker
Prevent cascading failures in distributed systems:
var circuitBreaker = new CircuitBreaker(
failureThreshold: 5,
timeout: TimeSpan.FromMinutes(1),
halfOpenRetryTimeout: TimeSpan.FromSeconds(30));
.Configure(config => config
.WithCircuitBreaker(circuitBreaker))
The circuit breaker has three states:
- Closed: Normal operation
- Open: Failures exceeded threshold, requests are blocked
- Half-Open: Testing if service recovered
🎯 HTTP Operations
GET Requests
.DoGet("users/{id}")
.DoGet("products?category=electronics")
POST/PUT/PATCH Requests
.DoPost("users", newUser)
.DoPut("users/123", updatedUser)
.DoPatch("users/123", partialUpdate)
DELETE Requests
.DoDelete("users/123")
📁 File Operations
Downloads
var response = await Rest
.Create()
.Configure(config => config
.WithBaseUrl("https://api.example.com")
.WithRetry())
.DoDownload("files/document.pdf")
.OnError(error => error.ThrowForNonSuccess())
.BuildAsync();
// Save to file
await response.SaveToFileAsync("./downloads", "document.pdf", replaceExisting: true);
// Or get as stream
var stream = response.ToStream();
Uploads
Upload from Stream
.DoUpload("files/upload", fileStream, "application/pdf")
Upload from byte array
.DoUpload("files/upload", fileBytes, "image/jpeg")
Upload from IFormFile (ASP.NET Core)
.DoUpload("files/upload", formFile)
Upload with custom HTTP method
.DoUpload("files/upload", file, settings => settings.UsePutAsMethod())
// Available: UsePostAsMethod(), UsePutAsMethod(), UsePatchAsMethod()
✅ Result Handling
Type Mapping by Status Code
.OnResult(result => result
.UseTypeForSuccess<User>() // 2xx status codes
.UseTypeFor<ErrorResponse>(HttpStatusCode.BadRequest)
.UseTypeFor<List<ValidationError>>(HttpStatusCode.UnprocessableEntity)
.UseEmptyFor(HttpStatusCode.NoContent)) // Empty response for 204
Conditional Type Mapping
.OnResult(result => result
.UseTypeFor<SuccessResponse>(
HttpStatusCode.OK,
body => body.success == true)
.UseTypeFor<ErrorResponse>(
HttpStatusCode.OK,
body => body.success == false))
Multiple Status Codes
.OnResult(result => result
.UseTypeFor<ErrorResponse>(new[] {
HttpStatusCode.BadRequest,
HttpStatusCode.Conflict,
HttpStatusCode.UnprocessableEntity
}))
❌ Error Handling
Basic Error Handling
.OnError(error => error
.ThrowForNonSuccess() // Throw for any non-2xx status
.ThrowFor(HttpStatusCode.Unauthorized) // Throw for specific status
.NotThrowFor(HttpStatusCode.NotFound)) // Don't throw for 404
Conditional Error Handling
.OnError(error => error
.ThrowFor(HttpStatusCode.BadRequest,
body => body.errorCode == "VALIDATION_ERROR"))
Fallback Responses
.OnError(error => error
.UseFallback(HttpStatusCode.ServiceUnavailable, new { message = "Service temporarily unavailable" })
.UseFallback(HttpStatusCode.NotFound, "{}"))
🏗️ Advanced Patterns
Repository Pattern
public class UserRepository
{
private readonly IRestRequest _client;
public UserRepository(IRestRequest client)
{
_client = client;
}
public async Task<User> GetUserAsync(int id, CancellationToken cancellationToken = default)
{
var response = await _client
.DoGet($"users/{id}")
.OnResult(r => r.UseTypeForSuccess<User>())
.OnError(e => e
.ThrowForNonSuccess()
.UseFallback(HttpStatusCode.NotFound, new User { Id = id, Name = "Unknown" }))
.BuildAsync(cancellationToken);
return response.GetAs<User>();
}
public async Task<User> CreateUserAsync(CreateUserRequest request, CancellationToken cancellationToken = default)
{
var response = await _client
.DoPost("users", request)
.OnResult(r => r
.UseTypeFor<User>(HttpStatusCode.Created)
.UseTypeFor<ValidationErrorResponse>(HttpStatusCode.BadRequest))
.OnError(e => e.ThrowForNonSuccess())
.BuildAsync(cancellationToken);
return response.GetAs<User>();
}
}
Multi-API Factory Pattern
public class ApiService
{
private readonly IRestFactory _restFactory;
public ApiService(IRestFactory restFactory)
{
_restFactory = restFactory;
}
public async Task<UserProfile> GetUserProfileAsync(int userId)
{
// Get user from API 1
var userResponse = await _restFactory
.Create("userApi")
.DoGet($"users/{userId}")
.OnResult(r => r.UseTypeForSuccess<User>())
.BuildAsync();
// Get preferences from API 2
var preferencesResponse = await _restFactory
.Create("preferencesApi")
.DoGet($"preferences/{userId}")
.OnResult(r => r.UseTypeForSuccess<UserPreferences>())
.OnError(e => e.UseFallback(HttpStatusCode.NotFound, new UserPreferences()))
.BuildAsync();
return new UserProfile
{
User = userResponse.GetAs<User>(),
Preferences = preferencesResponse.GetAs<UserPreferences>()
};
}
}
APIs that Always Return 200 OK
For APIs that return business logic errors as 200 OK:
var response = await Rest
.Create()
.Configure(config => config
.WithBaseUrl("https://legacy-api.com")
.WithRetry(retry => retry
.WithMaxAttempts(2)
.UseFixedDelay(TimeSpan.FromSeconds(1))))
.DoGet("users")
.OnResult(result => result
.UseTypeFor<List<User>>(HttpStatusCode.OK, body => body.success == true))
.OnError(error => error
.ThrowFor(HttpStatusCode.OK, body => body.success == false)
.ThrowForNonSuccess())
.BuildAsync();
🔧 Enterprise Scenarios
E-commerce with Different Retry Strategies
// Critical operations - Conservative retry
services.AddRestConfiguration("orders", config => config
.WithBaseUrl("https://orders-api.com")
.WithRetry(retry => retry
.WithMaxAttempts(2)
.UseExponentialBackoff(TimeSpan.FromSeconds(2))
.ForStatusCodes(HttpStatusCode.ServiceUnavailable, HttpStatusCode.TooManyRequests)));
// Read operations - Aggressive retry
services.AddRestConfiguration("catalog", config => config
.WithBaseUrl("https://catalog-api.com")
.WithRetry(retry => retry
.WithMaxAttempts(5)
.UseExponentialBackoffWithJitter(TimeSpan.FromSeconds(1))
.ForServerErrors()
.ForExceptions(typeof(TaskCanceledException))));
Microservices Communication
services.AddRestFactory()
.AddRestConfiguration("userService", config => config
.WithBaseUrl("https://user-service:8080")
.WithCircuitBreaker(new CircuitBreaker(5, TimeSpan.FromMinutes(1)))
.WithRetry())
.AddRestConfiguration("orderService", config => config
.WithBaseUrl("https://order-service:8080")
.WithCircuitBreaker(new CircuitBreaker(3, TimeSpan.FromMinutes(2)))
.WithRetry(retry => retry
.WithMaxAttempts(2)
.UseFixedDelay(TimeSpan.FromSeconds(3))));
📊 Response Information
Every response contains comprehensive metadata:
var response = await Rest.Create()...BuildAsync();
Console.WriteLine($"Status: {response.StatusCode}");
Console.WriteLine($"URL: {response.Url}");
Console.WriteLine($"Method: {response.Method}");
Console.WriteLine($"Elapsed Time: {response.ElapsedTime}");
Console.WriteLine($"Retries Made: {response.RetriesMade}");
Console.WriteLine($"Fallback Used: {response.FallbackUsed}");
Console.WriteLine($"Is Success: {response.IsSuccessStatusCode()}");
// Get typed result
var user = response.GetAs<User>();
// Get raw content
var jsonString = response.ToString();
var bytes = response.ToByteArray();
var stream = response.ToStream();
⚠️ Exception Types
The library provides specific exceptions for different scenarios:
NonSuccessException
: Thrown for HTTP error status codesNotMappedResultTypeException
: When no type mapping is found for a status codeDifferentResponseTypeException
: When trying to cast to wrong typeParsingTypeException
: When JSON deserialization failsFileAlreadyExistsOnDownloadException
: When download file already existsNoActionMadeException
: When no HTTP action was definedCircuitBreakerOpenException
: When circuit breaker is open
🎛️ Diagnostics & Monitoring
The library includes built-in diagnostics using .NET's Activity API:
// Activities are automatically created with tags:
// - http.url
// - http.method
// - Operation timing
Integration with OpenTelemetry and other observability tools is seamless.
📋 Best Practices
- Use Dependency Injection: Register REST clients as services for better testability
- Configure Retry Policies: Always use retry policies for production scenarios
- Implement Circuit Breakers: Prevent cascade failures in microservices
- Handle Errors Gracefully: Use fallbacks for non-critical operations
- Use Typed Responses: Leverage strong typing for better code maintainability
- Configure Timeouts: Set appropriate timeouts for your scenarios
- Log Requests/Responses: Enable logging for debugging and monitoring
- Use Named Configurations: Use factory pattern for multiple API integrations
📄 License
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | 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 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 was computed. 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. |
-
net8.0
- Myth.Commons (>= 3.0.3)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Myth.Rest:
Package | Downloads |
---|---|
Packs.Template.BaseApi
Basis for any Packs API |
|
Harpy.Presentation
Basis for the presentation layer of the Harpy Framework |
|
Harpy.Test.Presentation
Basis for the presentation test layer of the Harpy Framework |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
3.0.3 | 50 | 8/30/2025 |
3.0.2 | 46 | 8/23/2025 |
3.0.2-preview.4 | 111 | 8/21/2025 |
3.0.2-preview.3 | 91 | 8/16/2025 |
3.0.2-preview.1 | 65 | 5/23/2025 |
3.0.1 | 2,153 | 3/12/2025 |
3.0.1-preview.2 | 141 | 3/11/2025 |
3.0.1-preview.1 | 73 | 2/5/2025 |
3.0.0.2-preview | 100 | 12/10/2024 |
3.0.0.1-preview | 126 | 6/29/2024 |
3.0.0 | 4,649 | 12/10/2024 |
3.0.0-preview | 122 | 6/28/2024 |
2.0.0.17 | 2,771 | 3/12/2024 |
2.0.0.16 | 15,816 | 12/15/2023 |
2.0.0.15 | 141 | 12/15/2023 |
2.0.0.14 | 856 | 7/12/2023 |
2.0.0.13 | 186 | 7/12/2023 |
2.0.0.12 | 198 | 7/12/2023 |
2.0.0.11 | 2,022 | 8/11/2022 |
2.0.0.10 | 734 | 7/20/2022 |
2.0.0.9 | 749 | 7/15/2022 |
2.0.0.8 | 713 | 7/12/2022 |
2.0.0.7 | 733 | 6/20/2022 |
2.0.0.6 | 763 | 5/23/2022 |
2.0.0.5 | 767 | 5/18/2022 |
2.0.0 | 711 | 2/17/2022 |