fusillade 3.0.1

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

NuGet Stats Build Code Coverage

<br /> <a href="https://github.com/reactiveui/fusillade"> <img width="120" heigth="120" src="https://raw.githubusercontent.com/reactiveui/styleguide/master/logo_fusillade/main.png"> </a>

Fusillade: An opinionated HTTP library for .NET apps

Fusillade helps you write efficient, resilient networked apps by composing HttpMessageHandlers for HttpClient. It focuses on:

  • Request de-duplication for relevant HTTP methods
  • Concurrency limiting via a priority-aware operation queue
  • Request prioritization for predictable UX
  • Speculative background fetching with byte-budget limits
  • Optional caching of responses and an offline replay handler

Design inspirations include Android's Volley and Picasso.

Supported targets: library is built for .NET Standard 2.0 and is used from modern .NET (e.g., .NET 8/9), Xamarin/Mono, and .NET for iOS/Android/Mac Catalyst apps.

Install

  • Package Manager: Install-Package fusillade
  • .NET CLI: dotnet add package fusillade

Optional (examples below): Akavache for caching.

  • .NET CLI: dotnet add package Akavache.SystemTextJson

Quick start

Create HttpClient instances by picking the right handler from NetCache:

using Fusillade;
using System.Net.Http;

// Highest priority: the user is waiting now
var client = new HttpClient(NetCache.UserInitiated);
var json = await client.GetStringAsync("https://httpbin.org/get");

Available built-ins:

  • NetCache.UserInitiated: foreground work the user is waiting for
  • NetCache.Background: background work that should not block UI work
  • NetCache.Speculative: background prefetching with a byte budget
  • NetCache.Offline: fetch from cache only (no network)

By default, requests are processed four at a time via an operation queue.

Core ideas

1) Request de-duplication

Fusillade de-duplicates concurrent requests for the same resource when the method is GET, HEAD, or OPTIONS. If multiple callers request the same URL concurrently, only one on-the-wire request is made; the others join the same in-flight response.

This happens transparently in RateLimitedHttpMessageHandler.

2) Concurrency limiting and prioritization

All work is scheduled through an OperationQueue (default parallelism is 4). Each handler has an effective priority:

  • Priority.UserInitiated (100)
  • Priority.Background (20)
  • Priority.Speculative (10)
  • Priority.Explicit (custom base with offset)

Higher numbers run before lower ones. You can set a custom base (Explicit) and an offset to fit your scenario.

using Fusillade;
using Punchclock;
using System.Net.Http;

// Custom queue with 2 concurrent slots
var queue = new OperationQueue(2);

var handler = new RateLimitedHttpMessageHandler(
    new HttpClientHandler(),
    basePriority: Priority.Explicit,
    priority: 500,           // higher runs earlier
    opQueue: queue);

var client = new HttpClient(handler);

3) Speculative background fetching with byte budgets

Use NetCache.Speculative for prefetching scenarios. Limit the total number of bytes fetched; once the limit is reached, further speculative requests are canceled.

// Reset byte budget to 5 MB (e.g., on app resume)
NetCache.Speculative.ResetLimit(5 * 1024 * 1024);

var prefetch = new HttpClient(NetCache.Speculative);
_ = prefetch.GetStringAsync("https://example.com/expensive-data");

To stop speculative fetching immediately:

NetCache.Speculative.ResetLimit(-1); // any further requests will be canceled

Caching and offline

Fusillade can optionally cache responses (body bytes + headers) and replay them when offline.

There are two ways to wire caching:

  1. Provide a cacheResultFunc to RateLimitedHttpMessageHandler, which gets called with the response and a unique request key when a response is received.

  2. Set NetCache.RequestCache with an implementation of IRequestCache. Fusillade will invoke Save and Fetch automatically.

IRequestCache

public interface IRequestCache
{
    Task Save(HttpRequestMessage request, HttpResponseMessage response, string key, CancellationToken ct);
    Task<byte[]> Fetch(HttpRequestMessage request, string key, CancellationToken ct);
}
  • Save is called once the handler has fully buffered the body (as ByteArrayContent) and cloned headers.
  • Fetch should return the previously saved body bytes for the key (or null if not found).

Keys are generated by RateLimitedHttpMessageHandler.UniqueKeyForRequest(request). Treat the key as an implementation detail; persist what you receive and return it during Fetch.

Simple Akavache-based cache

using Akavache;
using Akavache.SystemTextJson;
using Fusillade;
using System.Net.Http;

// Initialize a simple in-memory Akavache cache
var database = CacheDatabase.CreateBuilder().WithSerializerSystemTextJson().Build();
var blobCache = new InMemoryBlobCache(database.Serializer);

// Option A: Provide a cacheResultFunc directly
var cachingHandler = new RateLimitedHttpMessageHandler(
    new HttpClientHandler(),
    Priority.UserInitiated,
    cacheResultFunc: async (rq, resp, key, ct) =>
    {
        var data = await resp.Content.ReadAsByteArrayAsync(ct);
        await blobCache.Insert(key, data);
    });

var client = new HttpClient(cachingHandler);
var fresh = await client.GetStringAsync("https://httpbin.org/get");

// Option B: Implement IRequestCache and set NetCache.RequestCache
NetCache.RequestCache = new MyRequestCache(blobCache);
// Example IRequestCache wrapper over Akavache
class MyRequestCache : IRequestCache
{
    private readonly IBlobCache _cache;
    public MyRequestCache(IBlobCache cache) => _cache = cache;

    public async Task Save(HttpRequestMessage request, HttpResponseMessage response, string key, CancellationToken ct)
    {
        var bytes = await response.Content.ReadAsByteArrayAsync(ct);
        await _cache.Insert(key, bytes);
    }

    public Task<byte[]> Fetch(HttpRequestMessage request, string key, CancellationToken ct)
        => _cache.Get(key);
}

Offline replay

Use OfflineHttpMessageHandler to serve cached data only (no network). This handler asks IRequestCache (or your custom retrieveBodyFunc) for the cached body and returns:

  • 200 OK with the cached body, or
  • 503 Service Unavailable if not found
// Use NetCache.Offline after setting NetCache.RequestCache
var offline = new HttpClient(NetCache.Offline);
var data = await offline.GetStringAsync("https://httpbin.org/get");

// Or construct explicitly
var offlineExplicit = new HttpClient(new OfflineHttpMessageHandler(
    async (rq, key, ct) => await blobCache.Get(key)));

Dependency injection and Splat integration

If you use Splat, you can initialize NetCache to use your container’s services via the provided extension:

using Splat.Builder;

var app = AppBuilder.CreateSplatBuilder().Build();
app.CreateFusilladeNetCache();

You can also register a platform-specific HttpMessageHandler (e.g., NSUrlSessionHandler on iOS, AndroidMessageHandler on Android) in your container beforehand; NetCache will pick it up as the inner HTTP handler.

Advanced configuration

  • Custom OperationQueue: override NetCache.OperationQueue with your own queue to control concurrency for the entire app.
using Punchclock;
NetCache.OperationQueue = new OperationQueue(maxConcurrency: 6);
  • Custom priorities: compose RateLimitedHttpMessageHandler with Priority.Explicit and an offset to place certain pipelines ahead or behind the defaults.
var urgent = new RateLimitedHttpMessageHandler(new HttpClientHandler(), Priority.Explicit, priority: 1_000);
var slow   = new RateLimitedHttpMessageHandler(new HttpClientHandler(), Priority.Explicit, priority: -50);
  • Deduplication scope: deduplication is per-HttpMessageHandler instance via an in-memory in-flight map. Multiple handlers mean multiple scopes.

Usage recipes

  • Image gallery / avatars

    • Use RateLimitedHttpMessageHandler for GETs
    • De-dup prevents duplicate downloads for the same URL
    • Use Background for preloading next images; switch to UserInitiated for visible images
  • Boot-time warmup

    • On app start/resume, set NetCache.Speculative.ResetLimit to a sensible budget
    • Queue speculative GETs for likely-next screens to reduce perceived latency
  • Offline-first data views

    • Populate cache during online sessions using cacheResultFunc or IRequestCache
    • When network is unavailable, point HttpClient to NetCache.Offline

FAQ

  • How many requests run at once?

    • Default is 4 (OperationQueue with concurrency 4). Override via NetCache.OperationQueue or pass a custom queue to a handler.
  • Which methods are de-duplicated?

    • GET, HEAD, and OPTIONS.
  • How are cache keys generated?

    • Via RateLimitedHttpMessageHandler.UniqueKeyForRequest(request). Treat this as an implementation detail; persist and reuse as given.
  • Can I cancel a request?

    • Use CancellationToken in HttpClient APIs; dedup ensures the underlying request cancels only when all dependents cancel.

Contribute

Fusillade is developed under an OSI-approved open source license, making it freely usable and distributable, even for commercial use. We ❤ our contributors and welcome new contributors of all experience levels.

What’s with the name?

“Fusillade” is a synonym for Volley 🙂

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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 is compatible.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (10)

Showing the top 5 NuGet packages that depend on fusillade:

Package Downloads
ModSink.Common

This library contains most of the logic for ModSink.

BD.Common.Mvvm

次元超越 Mvvm 库

XamBasePacket

Includes all needed classes models and libs to start developing Xamarin application.

Xablu.WebApiClient

The Xablu WebApiClient is a C# HTTP library which aims to simplify consuming of Web API services in .NET projects.

Apizr.Integrations.Fusillade

Refit based web api client management, but resilient (retry, connectivity, cache, auth, log, priority...)

GitHub repositories (4)

Showing the top 4 popular GitHub repositories that depend on fusillade:

Repository Stars
BeyondDimension/SteamTools
🛠「Watt Toolkit」是一个开源跨平台的多功能 Steam 工具箱。
reactiveui/ReactiveUI.Samples
This repository contains ReactiveUI samples.
HTBox/crisischeckin
Crisischeckin Humanitarian Toolbox repository
Respawnsive/Apizr
Refit based web api client management, but resilient (retry, connectivity, cache, auth, log, priority, etc...)
Version Downloads Last Updated
3.0.1 162 9/3/2025
2.6.30 13,649 5/2/2024
2.6.1 9,708 9/21/2023
2.4.67 22,116 2/1/2023
2.4.62 3,038 11/24/2022
2.4.47 4,385 6/22/2022
2.4.1 13,779 12/13/2021
2.3.1 33,069 1/22/2021
2.2.9 3,111 11/27/2020
2.2.1 2,592 10/23/2020
2.1.9 13,162 7/29/2020
2.1.1 27,892 5/10/2020
2.0.5 100,619 4/2/2019
2.0.2 3,897 2/19/2019
2.0.1 5,690 2/5/2019
1.0.0 52,559 9/5/2017
0.7.0 44,077 11/10/2016
0.6.0 97,430 5/1/2014
0.5.0 2,002 4/25/2014