Fetchy 2.0.2

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

Fetchy

NuGet Downloads

Fetchy is a lightweight .NET library that simplifies working with HttpClient and IHttpClientFactory.
It extends both with resilience, logging, and JSON helpers, providing extension methods for common HTTP operations with built-in error handling, retry policies, and authentication support.

Why Fetchy

Even though Microsoft.Extensions.Http already covers the basics, Fetchy differentiates itself by offering:

  • Unified API surface: Same extension methods available for both HttpClient and IHttpClientFactory.
  • Simplified syntax: Methods like GetResultAsync<T> and PostResultAsync<T> reduce boilerplate.
  • Built-in resilience: Preconfigured Polly policies (retry, timeout, fallback) without extra setup.
  • Developer ergonomics: Clear error handling, logging, and safe string retrieval (GetStringSafeAsync).
  • Authentication helpers: Easy AddBearerToken support for secure APIs.
  • Header support: Simple way to include custom headers across all methods.
  • File upload support: Easy way to send single or multiple files with headers, available for both HttpClient and IHttpClientFactory.

🚀 Quick Start

After installing Fetchy from NuGet:

dotnet add package Fetchy

Create a simple Console App and add the following code to Program.cs:


using System;
using System.Threading.Tasks;

//This example uses the JSONPlaceholder API. 
//The properties must match the keys returned by the API (id, title, body, userId)
public class Post
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public string Body { get; set; } = string.Empty;
    public int UserId { get; set; }
}

class Program
{
    static async Task Main()
    {
        var client = new HttpClient();

        // Perform a GET request and deserialize JSON into a typed object
        var post = await client.GetResultAsync<Post>("https://jsonplaceholder.typicode.com/posts/1");
        Console.WriteLine($"Post title: {post?.Title}");

        // Perform a POST request with a JSON body
        var newPost = await client.PostResultAsync<Post>(
            "https://jsonplaceholder.typicode.com/posts",
            new { userId = 1, title = "Hello World", body = "Testing Fetchy" }
        );
        Console.WriteLine($"Created Post ID: {newPost?.Id}");
    }
}

Run your app:

dotnet run

You’ll see the API responses printed directly in your console.


HttpClient Features

  • Retry policies: Automatic retries for transient failures using Polly.
  • Cancellation support: All methods accept CancellationToken for safe cancellation.
  • Integrated logging: Clear logs for requests, responses, and errors.
  • Exception handling: Simplified error messages with resilience built-in.
  • Bearer token authentication: Easy AddBearerToken helper for secure APIs.
  • Safe string retrieval: Reliable GetStringSafeAsync with retry + error handling.
  • Flexible JSON serialization: Support for JsonSerializerOptions (camelCase, naming policies, converters).
  • Header support: Pass custom headers (e.g. Accept, Authorization) directly in Fetchy methods without creating HttpRequestMessage
  • File upload methods: Use PostFileAsync for single file uploads or PostFilesAsync for multiple files, with optional headers and retry logic.

Full Example with HttpClient (Console App)


Program.cs

class Program
{
    static async Task Main(string[] args)
    {
        var example = new ExampleHttpClient();
        await example.Run();   
    }
}

ExampleHttpClient.cs:

using System;
using System.Text.Json;
using System.Threading.Tasks;

public class ExampleHttpClient
{
    public async Task Run()
    {
        var client = new HttpClient();

        // GET request with JSON
		//-------------------------
        // - Automatic JSON deserialization into a typed object (Post).
        // - Built-in retry logic with Polly for transient failures.
        // - Integrated logging (console output).
        // - Exception handling with clear error messages.
        var post = await client.GetResultAsync<Post>("https://jsonplaceholder.typicode.com/posts/1");
        Console.WriteLine($"Post: {post?.Title}");

        // POST request with JSON body
		//-------------------------
        // - Sends strongly typed JSON payload automatically.
        // - Handles serialization and deserialization seamlessly.
        // - Includes retry, logging, and cancellation support.
        var newPost = await client.PostResultAsync<Post>(
            "https://jsonplaceholder.typicode.com/posts",
            new { userId = 1, title = "Hello World", body = "Testing Fetchy" }
        );
        Console.WriteLine($"Created Post ID: {newPost?.Id}");

        // PUT request
		//-------------------------
        // - Updates existing resources with JSON payload.
        // - Same resilience features: retry, logging, cancellation.
        var updatedPost = await client.PutResultAsync<Post>(
            "https://jsonplaceholder.typicode.com/posts/1",
            new { id = 1, userId = 1, title = "Updated Title", body = "Updated body content" }
        );
        Console.WriteLine($"Updated Post Title: {updatedPost?.Title}");

        // DELETE request
		//-------------------------
        // - Deletes resources safely.
        // - Returns optional JSON response if available.
        // - Includes retry and logging for reliability.
        var deleted = await client.DeleteResultAsync<object>("https://jsonplaceholder.typicode.com/posts/1");
        Console.WriteLine("Deleted successfully");

        // Safe string retrieval
		//-------------------------
        // - Retrieves raw string/HTML safely.
        // - Ensures error handling and retry logic.
        var html = await client.GetStringSafeAsync("https://jsonplaceholder.typicode.com/posts");
        Console.WriteLine($"Response length: {html.Length}");

        // Custom JSON serialization
        // - Allows flexible serialization options (camelCase, naming policies, etc.).
        // - Useful when API responses use different casing or conventions.
        var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
        var customPost = await client.GetResultAsync<Post>("https://jsonplaceholder.typicode.com/posts/1", options);
        Console.WriteLine($"Custom serialized Post Title: {customPost?.Title}");
    }
}

🔐 Using Bearer Tokens

Some APIs require authentication with a JWT (JSON Web Token) or API key.
Fetchy makes this simple with the AddBearerToken extension method.

Fetchy does not generate tokens itself — you must obtain them from the API you are consuming.

How to get a token

Public APIs (e.g., jsonplaceholder) → no token required.

JWT-protected APIs → usually provide a login endpoint that returns a token:

var loginResponse = await client.PostResultAsync<LoginResponse>(
    "https://api.example.com/login",
    new { username = "user", password = "password" }
);

var token = loginResponse.Token; // Extract the token from the response

Adding the JWT token


// Bearer token authentication
//-------------------------
// - Adds JWT Bearer token to Authorization header.
// - Simplifies calling protected APIs.
// - Works seamlessly with all Fetchy methods.
client.AddBearerToken(loginResponse.Token);

// Call a protected endpoint
var result = await client.GetResultAsync<object>("https://api.example.com/protected");
Console.WriteLine("Fetched protected data successfully");

Adding Headers with HttpClient

Fetchy supports custom headers directly in its extension methods. You can pass a dictionary of headers to any HTTP method (GET, POST, PUT, DELETE, GetStringSafeAsync).

How it works:

  • Accept: application/json tells the server you expect JSON responses.
  • Authorization: Bearer <token> is used for authenticated APIs.
  • Headers can be added to any Fetchy method without manually creating HttpRequestMessage.

Program.cs:

class Program
{
    static async Task Main()
    {
        // GET request with headers
        var result = await client.GetResultAsync<string>(
			"https://api.example.com/data",
			headers: new Dictionary<string, string>
			{
				{ "Accept", "application/json" },
				{ "Authorization", "Bearer <your-token>" }
			}
		);

		Console.WriteLine(result);

         // PUT request with headers
        var updated = await client.PutResultAsync<object>(
            "https://api.example.com/data/1",
            new { id = 1, name = "Updated Resource" },
            headers: new Dictionary<string, string>
            {
                { "Accept", "application/json" },
                { "Authorization", "Bearer <your-token>" }
            }
        );
        Console.WriteLine("PUT request completed successfully");

        // DELETE request with headers
        var deleted = await client.DeleteResultAsync<object>(
            "https://api.example.com/data/1",
            headers: new Dictionary<string, string>
            {
                { "Accept", "application/json" },
                { "Authorization", "Bearer <your-token>" }
            }
        );
        Console.WriteLine("DELETE request completed successfully");
    }
}

Uploading Files with HttpClient

Single File Upload

using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var client = new HttpClient();

        using var fileStream = File.OpenRead("image.png");

        var response = await client.PostFileAsync(
            "https://api.example.com/upload",
            fileStream,
            "image.png",
            headers: new Dictionary<string, string>
            {
                { "Authorization", "Bearer <your-token>" }
            }
        );

        Console.WriteLine("File uploaded successfully");
    }
}

Multiple Files Upload

using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        var client = new HttpClient();

        using var file1 = File.OpenRead("doc1.pdf");
        using var file2 = File.OpenRead("photo.jpg");

        var files = new[]
        {
            (FileStream: (Stream)file1, FileName: "doc1.pdf"),
            (FileStream: (Stream)file2, FileName: "photo.jpg")
        };

        var response = await client.PostFilesAsync(
            "https://api.example.com/upload",
            files,
            headers: new Dictionary<string, string>
            {
                { "Authorization", "Bearer <your-token>" }
            }
        );

        Console.WriteLine("Multiple files uploaded successfully");
    }
}

IHttpClientFactory Features

  • Centralized client registration: Configure named clients in Program.cs.
  • Retry + circuit breaker integration: Combine Polly policies with factory clients.
  • Cancellation tokens: Safe cancellation across all factory-based calls.
  • Logging integration: Works seamlessly with ASP.NET Core logging.
  • Exception handling: Unified error handling for factory clients.
  • Safe string retrieval: GetStringSafeAsync with resilience applied.
  • Custom JSON serialization: Pass JsonSerializerOptions for flexible deserialization.
  • Header support: Add custom headers (e.g. Accept, Authorization) directly in Fetchy methods for named clients, without manually creating HttpRequestMessage
  • File upload methods: Use PostFileAsync for single file uploads or PostFilesAsync for multiple files, leveraging named clients from IHttpClientFactory.

Full Example with IHttpClientFactory (ASP.NET Core)

How it works

  • Program.cs registers a client, for example "JsonPlaceholder" and wires up ExampleService for DI.
  • ExampleService is a consumer class that receives IHttpClientFactory via constructor injection.
  • When you call Run(), it uses the extension method GetResultAsync<T> to fetch data from JSONPlaceholder.

//This example uses the JSONPlaceholder API. 
//The properties must match the keys returned by the API (id, title, body, userId)
public class Post
{
    public int Id { get; set; }
    public string Title { get; set; } = string.Empty;
    public string Body { get; set; } = string.Empty;
    public int UserId { get; set; }
}

HttpClientNames.cs:

public static class HttpClientNames
{
    //It's just an alias for the client configuration. 
    //You can name it anything you want, but it should match the name used in Program.cs when registering the client.
    public const string JsonPlaceholder = "JsonPlaceholder";
}

Program.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;

var builder = WebApplication.CreateBuilder(args);

// Register HttpClient with a named configuration
builder.Services.AddHttpClient(HttpClientNames.JsonPlaceholder, client =>
{
    client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

// Register your ExampleService for DI
builder.Services.AddTransient<ExampleService>();

var app = builder.Build();

// Run the app
using (var scope = app.Services.CreateScope())
{
    var service = scope.ServiceProvider.GetRequiredService<ExampleService>();
    await service.Run();
}

app.Run();

ExampleService.cs:

using System;
using System.Text.Json;
using System.Threading.Tasks;

public class ExampleService
{
    private readonly IHttpClientFactory _factory;

    public ExampleService(IHttpClientFactory factory)
    {
        _factory = factory;
    }

    public async Task Run()
    {
        // GET request
		//----------------------
        // - Automatic JSON deserialization into a typed object (Post).
        // - Built-in retry logic with Polly for transient failures.
        // - Integrated logging (console output).
        // - Exception handling with clear error messages.
        var post = await _factory.GetResultAsync<Post>(HttpClientNames.JsonPlaceholder, "posts/1");
        Console.WriteLine($"Post title: {post?.Title}");

        // POST request
		//----------------------
        // - Sends strongly typed JSON payload automatically.
        // - Handles serialization and deserialization seamlessly.
        // - Includes retry, logging, and cancellation support.
        var newPost = await _factory.PostResultAsync<Post>(
            HttpClientNames.JsonPlaceholder,
            "posts",
            new { userId = 1, title = "Hello World", body = "Testing Fetchy" }
        );
        Console.WriteLine($"Created Post ID: {newPost?.Id}");

        // PUT request
		//----------------------
        // - Updates existing resources with JSON payload.
        // - Same resilience features: retry, logging, cancellation.
        var updatedPost = await _factory.PutResultAsync<Post>(
            HttpClientNames.JsonPlaceholder,
            "posts/1",
            new { id = 1, userId = 1, title = "Updated Title", body = "Updated body content" }
        );
        Console.WriteLine($"Updated Post Title: {updatedPost?.Title}");

        // DELETE request
		//----------------------
        // - Deletes resources safely.
        // - Returns optional JSON response if available.
        // - Includes retry and logging for reliability.
        var deleted = await _factory.DeleteResultAsync<object>("JsonPlaceholder", "posts/1");
        Console.WriteLine("Deleted successfully");

        // Safe string retrieval
		//----------------------
        // - Retrieves raw string/HTML safely.
        // - Ensures error handling and retry logic.
        var html = await _factory.GetStringSafeAsync(HttpClientNames.JsonPlaceholder, "posts");
        Console.WriteLine($"Response length: {html.Length}");

        // Custom JSON serialization
		//----------------------
        // - Allows flexible serialization options (camelCase, naming policies, etc.).
        // - Useful when API responses use different casing or conventions.
        var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
        var customPost = await _factory.GetResultAsync<Post>(HttpClientNames.JsonPlaceholder, "posts/1", options);
        Console.WriteLine($"Custom serialized Post Title: {customPost?.Title}");
    }
}

Adding Headers with IHttpClientFactory

When using IHttpClientFactory with Fetchy, you can also pass headers in the same way. This is useful when working with named clients in dependency injection scenarios.

How it works:

  • Named clients (MyNamedClient) allow you to configure different HttpClient instances with specific defaults.
  • Fetchy applies headers on top of those defaults, giving you flexibility for authentication, content negotiation, or custom API requirements.
  • Works consistently across GET, POST, PUT, DELETE, and GetStringSafeAsync.

Program.cs:

class Program
{
    static async Task Main()
    {
        // GET request with headers
        var result = await factory.GetResultAsync<string>(
			"MyNamedClient",
			"https://api.example.com/data",
			headers: new Dictionary<string, string>
			{
				{ "Accept", "application/json" },
				{ "Authorization", "Bearer <your-token>" }
			}
		);

		Console.WriteLine(result);

        );

        // PUT request with headers
        var updated = await factory.PutResultAsync<object>(
            "MyNamedClient",
            "https://api.example.com/data/1",
            new { id = 1, name = "Updated Resource" },
            headers: new Dictionary<string, string>
            {
                { "Accept", "application/json" },
                { "Authorization", "Bearer <your-token>" }
            }
        );
        Console.WriteLine("PUT request completed successfully");

        // DELETE request with headers
        var deleted = await factory.DeleteResultAsync<object>(
            "MyNamedClient",
            "https://api.example.com/data/1",
            headers: new Dictionary<string, string>
            {
                { "Accept", "application/json" },
                { "Authorization", "Bearer <your-token>" }
            }
        );
        Console.WriteLine("DELETE request completed successfully");
    }
}

Uploading Files with IHttpClientFactory

Single File Upload

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

class Program
{
    static async Task Main()
    {
        var services = new ServiceCollection();
        services.AddHttpClient("MyNamedClient", client =>
        {
            client.BaseAddress = new Uri("https://api.example.com/");
        });

        var provider = services.BuildServiceProvider();
        var factory = provider.GetRequiredService<IHttpClientFactory>();

        using var fileStream = File.OpenRead("report.pdf");

        var response = await factory.PostFileAsync(
            "MyNamedClient",
            "upload",
            fileStream,
            "report.pdf",
            headers: new Dictionary<string, string>
            {
                { "Authorization", "Bearer <your-token>" }
            }
        );

        Console.WriteLine("File uploaded successfully via factory");
    }
}

Multiple Files Upload

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

class Program
{
    static async Task Main()
    {
        var services = new ServiceCollection();
        services.AddHttpClient("MyNamedClient", client =>
        {
            client.BaseAddress = new Uri("https://api.example.com/");
        });

        var provider = services.BuildServiceProvider();
        var factory = provider.GetRequiredService<IHttpClientFactory>();

        using var file1 = File.OpenRead("invoice.pdf");
        using var file2 = File.OpenRead("diagram.png");

        var files = new[]
        {
            (FileStream: (Stream)file1, FileName: "invoice.pdf"),
            (FileStream: (Stream)file2, FileName: "diagram.png")
        };

        var response = await factory.PostFilesAsync(
            "MyNamedClient",
            "upload",
            files,
            headers: new Dictionary<string, string>
            {
                { "Authorization", "Bearer <your-token>" }
            }
        );

        Console.WriteLine("Multiple files uploaded successfully via factory");
    }
}

Advantages

  • Built-in error handling
  • Retry logic with Polly
  • Integrated console logging (no setup required)
  • Authentication helpers
  • Flexible JSON serialization
  • Custom header injection for all requests without extra boilerplate
  • File upload capability

Methods Quick Reference

Method Description Key Features
GetResultAsync<T> Performs a GET request and deserializes JSON into a typed object. Retry with Polly · Logging · Cancellation support · JSON deserialization
PostResultAsync<T> Sends a POST request with a JSON body and returns a typed response. Automatic serialization · Retry · Logging · Cancellation support
PutResultAsync<T> Sends a PUT request to update resources with JSON payload. Serialization · Retry · Logging · Cancellation support
DeleteResultAsync<T> Sends a DELETE request and optionally deserializes JSON response. Retry · Logging · Cancellation support
GetStringSafeAsync Retrieves raw string/HTML safely. Retry · Logging · Cancellation support
AddBearerToken Adds a JWT Bearer token to the Authorization header. Simplifies authentication · Works with all Fetchy methods
Custom JSON Options Allows passing JsonSerializerOptions for flexible serialization. CamelCase · Naming policies · Custom converters
PostFileAsync Uploads a single file using multipart/form-data. File stream support · Optional headers · Retry · Logging
PostFilesAsync Uploads multiple files in one request using multipart/form-data. Multiple streams · Optional headers · Retry · Logging

Styles Quick Reference

Style Example Best For
HttpClient var post = await client.GetResultAsync(url); Small projects, SDKs, quick scripts
IHttpClientFactory var post = await _factory.GetResultAsync("JsonPlaceholder", "posts/1"); ASP.NET Core apps, microservices, enterprise projects

Ideal Project Types to Use Fetchy

Project Type Purpose Example Usage
Console App (.NET 8 or .NET 10) Perfect for quick tests and demos. Run ExampleUsage.Run() directly from Program.cs to validate HTTP calls and logging.
ASP.NET Core Web API Best for integrating Fetchy into controllers or services. Use IHttpClientFactory and test methods like GetResultAsync, PostResultAsync, etc., inside real endpoints.
Worker Service / Background Service Suitable for automated tasks or API polling. Fetchy handles retries and cancellations, ideal for background processes.
Blazor Server / Blazor WebAssembly For consuming APIs from client or server. Fetchy simplifies HTTP calls and error handling.
Unit Test Project (xUnit / NUnit) To validate integration with real or simulated APIs. Use HttpClient with TestServer or mocks to test behavior.

Differences by Project Type

Project Type How it varies
Console App You use new HttpClient() directly and call the extension methods. Ideal for quick tests.
ASP.NET Core Web API The recommended approach is to use IHttpClientFactory registered in Program.cs with services.AddHttpClient(). Fetchy provides specific extensions for IHttpClientFactory.
Worker Service / Background Service Similar to Web API: IHttpClientFactory is injected into the service. The difference is that Fetchy is used in background processes, where Polly retries are very useful.
Blazor WebAssembly Uses the HttpClient injected by the framework. Fetchy works the same way, but you must be careful with CORS and authentication.
Blazor Server Same as Web API: IHttpClientFactory is recommended.
Unit Test Project You don’t call real APIs here — instead, you use MockHttpMessageHandler or TestServer to simulate responses. Fetchy is tested the same way but with mock clients.

⚖️ License

This project is freely available under the MIT license.
You may use it without restrictions, as long as you retain the reference to the original license.

Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.2 42 6/4/2026
2.0.1 39 6/4/2026
2.0.0 39 6/4/2026
1.0.9 45 6/4/2026
1.0.8 40 6/3/2026
1.0.7 35 6/3/2026
1.0.6 43 6/3/2026
1.0.5 39 6/3/2026
1.0.4 39 6/3/2026
1.0.3 42 6/3/2026
1.0.2 40 6/3/2026
1.0.1 50 6/3/2026
1.0.0 43 6/3/2026