https://github.com/thomasgalliker/httpclient.caching
Caching extensions for .NET HttpClient
https://github.com/thomasgalliker/httpclient.caching
cache caching httpclient memorycache
Last synced: 10 months ago
JSON representation
Caching extensions for .NET HttpClient
- Host: GitHub
- URL: https://github.com/thomasgalliker/httpclient.caching
- Owner: thomasgalliker
- License: mit
- Created: 2018-11-18T08:19:22.000Z (about 7 years ago)
- Default Branch: develop
- Last Pushed: 2025-02-24T06:50:09.000Z (11 months ago)
- Last Synced: 2025-03-29T10:56:26.337Z (10 months ago)
- Topics: cache, caching, httpclient, memorycache
- Language: C#
- Homepage:
- Size: 160 KB
- Stars: 22
- Watchers: 2
- Forks: 7
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# HttpClient.Caching
[](https://www.nuget.org/packages/HttpClient.Caching) [](https://www.nuget.org/packages/HttpClient.Caching)
### Download and Install HttpClient.Caching
This library is available on NuGet: https://www.nuget.org/packages/HttpClient.Caching/
Use the following command to install HttpClient.Caching using NuGet package manager console:
PM> Install-Package HttpClient.Caching
You can use this library in any .Net project which is compatible to .Net Framework 4.5+ and .Net Standard 1.2+ (e.g. Xamarin Android, iOS, Universal Windows Platform, etc.)
### The Purpose of HTTP Caching
HTTP Caching affects both involved communication peers, the client and the server. On the server-side, caching is appropriate for improving throughput (scalability). HTTP caching doesn't make a single HTTP call faster but it can lead to better response performance in high-load scenarios. On the client-side, caching is used to avoid unnecessarily repetitiv HTTP calls. This leads to less waiting time on the client-side since cache reads have naturally a much better response performance than HTTP calls over relatively slow network links.
### API Usage
#### Using MemoryCache
Declare IMemoryCache in your API service, either by creating an instance manually or by injecting IMemoryCache into your API service class.
```C#
private readonly IMemoryCache memoryCache = new MemoryCache();
```
Following example show how IMemoryCache can be used to store an HTTP GET result in memory for a given time span (cacheExpirection):
```C#
public async Task GetAsync(string uri, TimeSpan? cacheExpiration = null)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
TResult result;
var caching = cacheExpiration.HasValue;
if (caching && this.memoryCache.TryGetValue(uri, out result))
{
stopwatch.Stop();
this.tracer.Debug($"{nameof(this.GetAsync)} for Uri '{uri}' finished in {stopwatch.Elapsed.ToSecondsString()} (caching=true)");
return result;
}
var httpResponseMessage = await this.HandleRequest(() => this.httpClient.GetAsync(uri));
var jsonResponse = await this.HandleResponse(httpResponseMessage);
result = await Task.Run(() => JsonConvert.DeserializeObject(jsonResponse, this.serializerSettings));
if (caching)
{
this.memoryCache.Set(uri, result, cacheExpiration.Value);
}
else
{
this.memoryCache.Remove(uri);
}
stopwatch.Stop();
this.tracer.Debug($"{nameof(this.GetAsync)} for Uri '{uri}' finished in {stopwatch.Elapsed.ToSecondsString()}");
return result;
}
```
#### Using InMemoryCacheHandler
HttpClient allows to inject a custom http handler. In the follwing example, we inject an HttpClientHandler which is nested into an InMemoryCacheHandler where the InMemoryCacheHandler is responsible for maintaining and reading the cache.
```C#
static void Main(string[] args)
{
const string url = "http://worldtimeapi.org/api/timezone/Europe/Zurich";
var httpClientHandler = new HttpClientHandler();
var cacheExpirationPerHttpResponseCode = CacheExpirationProvider.CreateSimple(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(5));
var handler = new InMemoryCacheHandler(httpClientHandler, cacheExpirationPerHttpResponseCode);
using (var client = new HttpClient(handler))
{
// HttpClient calls the same API endpoint five times:
// - The first attempt is called against the real API endpoint since no cache is available
// - Attempts 2 to 5 can be read from cache
for (var i = 1; i <= 5; i++)
{
Console.Write($"Attempt {i}: HTTP GET {url}...");
var stopwatch = Stopwatch.StartNew();
var result = client.GetAsync(url).GetAwaiter().GetResult();
// Do something useful with the returned content...
var content = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
Console.WriteLine($" completed in {stopwatch.ElapsedMilliseconds}ms");
// Artificial wait time...
Thread.Sleep(1000);
}
}
Console.WriteLine();
StatsResult stats = handler.StatsProvider.GetStatistics();
Console.WriteLine($"TotalRequests: {stats.Total.TotalRequests}");
Console.WriteLine($"-> CacheHit: {stats.Total.CacheHit}");
Console.WriteLine($"-> CacheMiss: {stats.Total.CacheMiss}");
Console.ReadKey();
}
```
Console output:
```
Attempt 1: HTTP GET http://worldtimeapi.org/api/timezone/Europe/Zurich... completed in 625ms
Attempt 2: HTTP GET http://worldtimeapi.org/api/timezone/Europe/Zurich... completed in 48ms
Attempt 3: HTTP GET http://worldtimeapi.org/api/timezone/Europe/Zurich... completed in 1ms
Attempt 4: HTTP GET http://worldtimeapi.org/api/timezone/Europe/Zurich... completed in 1ms
Attempt 5: HTTP GET http://worldtimeapi.org/api/timezone/Europe/Zurich... completed in 1ms
TotalRequests: 5
-> CacheHit: 4
-> CacheMiss: 1
```
### Cache keys
By default, requests will be cached by using a key which is composed with http method and url (only HEAD and GET http methods are supported).
If this default behavior isn't enough **you can implement your own ICacheKeyProvider** wich provides **cache key** starting **from HttpRequestMessage**.
The following example show how use a cache provider of type MethodUriHeadersCacheKeysProvider.
This cache key provider is already implemented and evaluates http method, specified headers and url to compose a cache key.
with InMemoryCacheHandler.
```C#
static void Main(string[] args)
{
const string url = "http://worldtimeapi.org/api/timezone/Europe/Zurich";
var httpClientHandler = new HttpClientHandler();
var cacheExpirationPerHttpResponseCode = CacheExpirationProvider.CreateSimple(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(5));
// this is a CacheKeyProvider which evaluates http method, specified headers and url to compose a key
var cacheKeyProvider = new MethodUriHeadersCacheKeysProvider(new string[] { "FIRST-HEADER", "SECOND-HEADER" });
var handler = new InMemoryCacheHandler(
innerHandler: httpClientHandler,
cacheExpirationPerHttpResponseCode: cacheExpirationPerHttpResponseCode,
cacheKeysProvider: cacheKeyProvider
);
using (var client = new HttpClient(handler))
{
// HttpClient calls the same API endpoint five times:
// - The first attempt is called against the real API endpoint since no cache is available
// - Attempts 2 to 5 can be read from cache
for (var i = 1; i <= 5; i++)
{
Console.Write($"Attempt {i}: HTTP GET {url}...");
var stopwatch = Stopwatch.StartNew();
var result = client.GetAsync(url).GetAwaiter().GetResult();
// Do something useful with the returned content...
var content = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
Console.WriteLine($" completed in {stopwatch.ElapsedMilliseconds}ms");
// Artificial wait time...
Thread.Sleep(1000);
}
}
Console.WriteLine();
StatsResult stats = handler.StatsProvider.GetStatistics();
Console.WriteLine($"TotalRequests: {stats.Total.TotalRequests}");
Console.WriteLine($"-> CacheHit: {stats.Total.CacheHit}");
Console.WriteLine($"-> CacheMiss: {stats.Total.CacheMiss}");
Console.ReadKey();
}
```
### Links
[How-to: HTTP Caching for RESTful & Hypermedia APIs](https://www.apiacademy.co/articles/2015/12/how-to-http-caching-for-restful-hypermedia-apis)
### License
This project is Copyright © 2022 [Thomas Galliker](https://ch.linkedin.com/in/thomasgalliker). Free for non-commercial use. For commercial use please contact the author.