Myth.Rest 3.0.3

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

Myth.Rest

NuGet Version NuGet Version

License

pt-br en

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.

.WithRetry() // 3 attempts, exponential backoff with jitter, server errors only

Custom Retry Strategies

.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 codes
  • NotMappedResultTypeException: When no type mapping is found for a status code
  • DifferentResponseTypeException: When trying to cast to wrong type
  • ParsingTypeException: When JSON deserialization fails
  • FileAlreadyExistsOnDownloadException: When download file already exists
  • NoActionMadeException: When no HTTP action was defined
  • CircuitBreakerOpenException: 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

  1. Use Dependency Injection: Register REST clients as services for better testability
  2. Configure Retry Policies: Always use retry policies for production scenarios
  3. Implement Circuit Breakers: Prevent cascade failures in microservices
  4. Handle Errors Gracefully: Use fallbacks for non-critical operations
  5. Use Typed Responses: Leverage strong typing for better code maintainability
  6. Configure Timeouts: Set appropriate timeouts for your scenarios
  7. Log Requests/Responses: Enable logging for debugging and monitoring
  8. 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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