SkillAgentSearch skills...

Replicant

A wrapper for HttpClient that caches to disk. Cached files, over the max specified, are deleted based on the last access times.

Install / Use

/learn @SimonCropp/Replicant
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<img src="/src/icon.png" height="30px"> Replicant

Build status NuGet Status

A wrapper for HttpClient that caches to disk. Cached files, over the max specified, are deleted based on the last access times.

See Milestones for release notes.

Headers/Responses respected in caching decisions:

NuGet package

https://nuget.org/packages/Replicant/

Usage

Default instance

There is a default static instance:

<!-- snippet: DefaultInstance -->

<a id='snippet-DefaultInstance'></a>

var content = await HttpCache.Default.DownloadAsync("https://httpbin.org/status/200");

<sup><a href='/src/Tests/HttpCacheTests.cs#L87-L91' title='Snippet source file'>snippet source</a> | <a href='#snippet-DefaultInstance' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

This caches to {Temp}/Replicant.

Construction

An instance of HttpCache should be long running.

<!-- snippet: Construction -->

<a id='snippet-Construction'></a>

var httpCache = new HttpCache(
    cacheDirectory,
    // omit for default new HttpClient()
    new HttpClient
    {
        Timeout = TimeSpan.FromSeconds(30)
    },
    // omit for the default of 1000
    maxEntries: 10000);

// Dispose when finished
await httpCache.DisposeAsync();

<sup><a href='/src/Tests/HttpCacheTests.cs#L25-L40' title='Snippet source file'>snippet source</a> | <a href='#snippet-Construction' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Dependency injection

Add HttpCache as a singleton when using dependency injection.

<!-- snippet: DependencyInjection -->

<a id='snippet-DependencyInjection'></a>

var services = new ServiceCollection();
services.AddSingleton(_ => new HttpCache(cacheDirectory));

using var provider = services.BuildServiceProvider();
var httpCache = provider.GetRequiredService<HttpCache>();
NotNull(httpCache);

<sup><a href='/src/Tests/HttpCacheTests.cs#L48-L57' title='Snippet source file'>snippet source</a> | <a href='#snippet-DependencyInjection' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Using HttpClient with HttpClientFactory.

<!-- snippet: DependencyInjectionWithHttpFactory -->

<a id='snippet-DependencyInjectionWithHttpFactory'></a>

var services = new ServiceCollection();
services.AddHttpClient();
services.AddSingleton(_ =>
{
    var clientFactory = _.GetRequiredService<IHttpClientFactory>();
    return new HttpCache(cacheDirectory, clientFactory.CreateClient);
});

using var provider = services.BuildServiceProvider();
var httpCache = provider.GetRequiredService<HttpCache>();
NotNull(httpCache);

<sup><a href='/src/Tests/HttpCacheTests.cs#L65-L79' title='Snippet source file'>snippet source</a> | <a href='#snippet-DependencyInjectionWithHttpFactory' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

DelegatingHandler

ReplicantHandler can be used as a DelegatingHandler in the HttpClient pipeline:

<!-- snippet: ReplicantHandlerUsage -->

<a id='snippet-ReplicantHandlerUsage'></a>

var handler = new ReplicantHandler(cacheDirectory)
{
    InnerHandler = new HttpClientHandler()
};
using var client = new HttpClient(handler);
var response = await client.GetAsync("https://example.com");

<sup><a href='/src/Tests/CachingHandlerTests.cs#L26-L35' title='Snippet source file'>snippet source</a> | <a href='#snippet-ReplicantHandlerUsage' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

DelegatingHandler with HttpClientFactory

ReplicantHandler integrates with HttpClientFactory using AddHttpMessageHandler:

<!-- snippet: HttpClientFactoryUsage -->

<a id='snippet-HttpClientFactoryUsage'></a>

var services = new ServiceCollection();
services.AddHttpClient("CachedClient")
    .AddHttpMessageHandler(() => new ReplicantHandler(cacheDirectory));

<sup><a href='/src/Tests/CachingHandlerTests.cs#L40-L46' title='Snippet source file'>snippet source</a> | <a href='#snippet-HttpClientFactoryUsage' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

To share a single cache (and purge timer) across multiple named clients, register a ReplicantCache as a singleton:

<!-- snippet: HttpClientFactorySharedCacheUsage -->

<a id='snippet-HttpClientFactorySharedCacheUsage'></a>

var services = new ServiceCollection();
services.AddReplicantCache(cacheDirectory);
services.AddHttpClient("CachedClient")
    .AddReplicantCaching();

<sup><a href='/src/Tests/CachingHandlerTests.cs#L51-L58' title='Snippet source file'>snippet source</a> | <a href='#snippet-HttpClientFactorySharedCacheUsage' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

HybridCache support

Replicant can serve as a disk-based L2 cache for HybridCache. Register ReplicantDistributedCache as the IDistributedCache backend, and HybridCache will automatically use it for its L2 layer (with in-memory L1 handled by HybridCache itself):

<!-- snippet: DistributedCacheUsage -->

<a id='snippet-DistributedCacheUsage'></a>

var services = new ServiceCollection();
services.AddReplicantDistributedCache(cacheDirectory);
services.AddHybridCache();

<sup><a href='/src/Tests/DistributedCacheTests.cs#L25-L31' title='Snippet source file'>snippet source</a> | <a href='#snippet-DistributedCacheUsage' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Single cache per directory

Only one cache instance (HttpCache, ReplicantCache, or ReplicantHandler with its own directory) can exist per cache directory at any time. Creating a second instance for the same directory will throw an InvalidOperationException. This prevents multiple purge timers from running against the same files.

To share a cache across multiple handlers or consumers, use a single ReplicantCache instance (see above).

Get a string

<!-- snippet: string -->

<a id='snippet-string'></a>

var content = await httpCache.StringAsync("https://httpbin.org/json");

<sup><a href='/src/Tests/HttpCacheTests.cs#L287-L291' title='Snippet source file'>snippet source</a> | <a href='#snippet-string' title='Start of snippet'>anchor</a></sup> <a id='snippet-string-1'></a>

var lines = new List<string>();
await foreach (var line in httpCache.LinesAsync("https://httpbin.org/json"))
{
    lines.Add(line);
}

<sup><a href='/src/Tests/HttpCacheTests.cs#L302-L310' title='Snippet source file'>snippet source</a> | <a href='#snippet-string-1' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Get bytes

<!-- snippet: bytes -->

<a id='snippet-bytes'></a>

var bytes = await httpCache.BytesAsync("https://httpbin.org/json");

<sup><a href='/src/Tests/HttpCacheTests.cs#L321-L325' title='Snippet source file'>snippet source</a> | <a href='#snippet-bytes' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Get a stream

<!-- snippet: stream -->

<a id='snippet-stream'></a>

using var stream = await httpCache.StreamAsync("https://httpbin.org/json");

<sup><a href='/src/Tests/HttpCacheTests.cs#L336-L340' title='Snippet source file'>snippet source</a> | <a href='#snippet-stream' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Download to a file

<!-- snippet: ToFile -->

<a id='snippet-ToFile'></a>

await httpCache.ToFileAsync("https://httpbin.org/json", targetFile);

<sup><a href='/src/Tests/HttpCacheTests.cs#L353-L357' title='Snippet source file'>snippet source</a> | <a href='#snippet-ToFile' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Download to a stream

<!-- snippet: ToStream -->

<a id='snippet-ToStream'></a>

await httpCache.ToStreamAsync("https://httpbin.org/json", targetStream);

<sup><a href='/src/Tests/HttpCacheTests.cs#L374-L378' title='Snippet source file'>snippet source</a> | <a href='#snippet-ToStream' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Manually add an item to the cache

<!-- snippet: AddItem -->

<a id='snippet-AddItem'></a>

using var response = new HttpResponseMessage(HttpStatusCode.OK)
{
    Content = new StringContent("the content")
};
await httpCache.AddItemAsync(uri, response);

<sup><a href='/src/Tests/HttpCacheTests.cs#L439-L447' title='Snippet source file'>snippet source</a> | <a href='#snippet-AddItem' title='Start of snippet'>anchor</a></sup>

<!-- endSnippet -->

Use stale item on error

If an error occurs when re-validating a potentially stale item, then the cached item can be used as a fallback.

<!-- snippet: staleIfError -->

<a id='snippet-staleIfError'></a>

var content = httpCache.StringAsync(uri, staleIfError: true);

<sup><a href='/src/Tests/HttpCacheTests.cs#L524-L528' title='Snippet source file'>snippet source</a> | <a href='#snippet-staleIfError' title='Start of snippet'>an

View on GitHub
GitHub Stars595
CategoryProduct
Updated8h ago
Forks15

Languages

C#

Security Score

100/100

Audited on Apr 6, 2026

No findings