Skip to content

Commit 03baad4

Browse files
committed
Introducing default value factory option for ISimpleCache and adding overloads to receive an absolute expiration relative to now
1 parent 982a19d commit 03baad4

19 files changed

+510
-250
lines changed

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,15 @@ services.AddSimpleCache<Guid, WeatherForecast>(opt => opt
133133
// Set custom expiration options
134134
.WithAbsoluteExpirationRelativeToNow(TimeSpan.FromHours(1))
135135
);
136+
137+
// With value factory as fallback
138+
services.AddSimpleCache<DateTime, WeatherForecast>(opt => opt
139+
.WithAbsoluteExpirationRelativeToNow(TimeSpan.FromSeconds(15))
140+
141+
// Configure default value factory to be used when a requested key is not found on cache
142+
.WithValueFactory((date, provider, token) =>
143+
provider.GetRequiredService<IWeatherService>().FetchForecastAsync(date, token))
144+
);
136145
```
137146

138147
Then, inject the interface where you need it:
@@ -151,19 +160,12 @@ private async Task<IEnumerable<WeatherForecast>> FetchAllForecastsAsync(Cancella
151160
{
152161
var date = DateTime.Now.Date.AddDays(index);
153162

154-
// Get cached daily forecast if it exists and fetch if not.
155-
var forecast = await _dailyForecastCache
156-
.GetOrSetAsync(date, () => FetchSingleForecastAsync(date, cancellationToken), cancellationToken);
163+
// Get cached daily forecast from cache or from default value factory.
164+
var forecast = await _dailyForecastCache.GetAsync(date, cancellationToken);
157165

158166
forecasts.Add(forecast);
159167
}
160168

161169
return forecasts.AsEnumerable();
162170
}
163-
164-
private async Task<WeatherForecast> FetchSingleForecastAsync(DateTime date, CancellationToken cancellationToken)
165-
{
166-
// Logic for fetching key missing from cache
167-
// ...
168-
}
169171
```

samples/Sample.Application/DistributedWeatherService.cs

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,17 @@ namespace Sample.Application
99
{
1010
public class DistributedWeatherService : IDistributedWeatherService
1111
{
12-
private static readonly Random RNG = new Random();
13-
private static readonly string[] SUMMARIES = {
14-
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
15-
};
16-
12+
private readonly IWeatherService _weatherService;
1713
private readonly IDistributedCache _distributedCache;
1814

1915
private readonly DistributedCacheEntryOptions _entryOptions = new DistributedCacheEntryOptions
2016
{
2117
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(15)
2218
};
2319

24-
public DistributedWeatherService(IDistributedCache distributedCache)
20+
public DistributedWeatherService(IWeatherService weatherService, IDistributedCache distributedCache)
2521
{
22+
_weatherService = weatherService;
2623
_distributedCache = distributedCache;
2724
}
2825

@@ -37,7 +34,7 @@ public async Task<IEnumerable<WeatherForecast>> FetchAsync(CancellationToken can
3734
// Get cached daily forecast if it exists and fetch if not.
3835
var forecast = await _distributedCache
3936
.GetOrSetJsonObjectAsync($"single-weather-forecast:{date.ToShortDateString()}",
40-
() => FetchSingleAsync(date, cancellationToken),
37+
() => _weatherService.FetchForecastAsync(date, cancellationToken),
4138
_entryOptions,
4239
cancellationToken);
4340

@@ -46,19 +43,5 @@ public async Task<IEnumerable<WeatherForecast>> FetchAsync(CancellationToken can
4643

4744
return forecasts.AsEnumerable();
4845
}
49-
50-
private async Task<WeatherForecast> FetchSingleAsync(DateTime date, CancellationToken cancellationToken)
51-
{
52-
// Simulate access to a database or third party service.
53-
await Task.Delay(100, cancellationToken);
54-
55-
// Return mock result.
56-
return new WeatherForecast
57-
{
58-
Date = date,
59-
TemperatureC = RNG.Next(-20, 55),
60-
Summary = SUMMARIES[RNG.Next(SUMMARIES.Length)]
61-
};
62-
}
6346
}
6447
}

samples/Sample.Application/ISimpleWeatherService.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace Sample.Application
6+
{
7+
public interface IWeatherService
8+
{
9+
Task<WeatherForecast> FetchForecastAsync(DateTime date, CancellationToken cancellationToken);
10+
}
11+
}

samples/Sample.Application/SimpleWeatherService.cs

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace Sample.Application
6+
{
7+
public class WeatherService : IWeatherService
8+
{
9+
private static readonly Random RNG = new Random();
10+
private static readonly string[] SUMMARIES = {
11+
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
12+
};
13+
14+
public async Task<WeatherForecast> FetchForecastAsync(DateTime date, CancellationToken cancellationToken)
15+
{
16+
// Simulate access to a database or third party service.
17+
await Task.Delay(100, cancellationToken);
18+
19+
// Return mock result.
20+
return new WeatherForecast
21+
{
22+
Date = date,
23+
TemperatureC = RNG.Next(-20, 55),
24+
Summary = SUMMARIES[RNG.Next(SUMMARIES.Length)]
25+
};
26+
}
27+
}
28+
}

samples/Sample.Web/Controllers/SimpleCacheController.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
24
using System.Threading;
35
using System.Threading.Tasks;
46
using Microsoft.AspNetCore.Mvc;
@@ -12,23 +14,35 @@ namespace Sample.Web.Controllers
1214
public class SimpleCacheController : ControllerBase
1315
{
1416
private readonly ISimpleCache<IEnumerable<WeatherForecast>> _responseCache;
15-
private readonly ISimpleWeatherService _simpleWeatherService;
17+
private readonly ISimpleCache<DateTime, WeatherForecast> _weatherForecastCache;
1618

1719
public SimpleCacheController(
1820
ISimpleCache<IEnumerable<WeatherForecast>> responseCache,
19-
ISimpleWeatherService simpleWeatherService
21+
ISimpleCache<DateTime, WeatherForecast> weatherForecastCache
2022
)
2123
{
24+
// In this example, responseCache will use extension methods to get or set a value and
25+
// weatherForecastCache will use the configured value factory to provide values if none exist in cache
2226
_responseCache = responseCache;
23-
_simpleWeatherService = simpleWeatherService;
27+
_weatherForecastCache = weatherForecastCache;
2428
}
2529

2630
[HttpGet]
2731
public async Task<IEnumerable<WeatherForecast>> GetAsync(CancellationToken cancellationToken)
2832
{
2933
// Will get cached response if there is any and fetch otherwise.
3034
var response = await _responseCache
31-
.GetOrSetAsync(() => _simpleWeatherService.FetchAsync(cancellationToken), cancellationToken);
35+
.GetOrSetAsync(async () =>
36+
{
37+
var tasks = Enumerable
38+
.Range(1, 5)
39+
.Select(i => _weatherForecastCache.GetAsync(DateTime.Now.Date.AddDays(i), cancellationToken))
40+
.ToArray();
41+
42+
var forecasts = await Task.WhenAll(tasks);
43+
44+
return forecasts.AsEnumerable();
45+
}, cancellationToken);
3246

3347
return response;
3448
}

samples/Sample.Web/Startup.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,14 @@ public void ConfigureServices(IServiceCollection services)
4040
// Add SimpleCache for individual forecasts.
4141
services.AddSimpleCache<DateTime, WeatherForecast>(opt => opt
4242
.WithAbsoluteExpirationRelativeToNow(TimeSpan.FromSeconds(15))
43+
44+
// Configure default value factory to be used when a requested key is not found on cache
45+
.WithValueFactory((date, provider, token) =>
46+
provider.GetRequiredService<IWeatherService>().FetchForecastAsync(date, token))
4347
);
4448

4549
services.AddScoped<IDistributedWeatherService, DistributedWeatherService>();
46-
services.AddScoped<ISimpleWeatherService, SimpleWeatherService>();
50+
services.AddScoped<IWeatherService, WeatherService>();
4751
}
4852

4953
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

src/SimpleConcepts.Extensions.Caching.Abstractions/Distributed/DistributedCacheGetOrSetExtensions.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,31 @@ namespace Microsoft.Extensions.Caching.Distributed
77
{
88
public static class DistributedCacheGetOrSetExtensions
99
{
10+
public static void Set(this IDistributedCache cache, string key, byte[] value, TimeSpan absoluteExpirationRelativeToNow)
11+
{
12+
cache.Set(key, value,
13+
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow });
14+
}
15+
16+
public static Task SetAsync(this IDistributedCache cache, string key, byte[] value, TimeSpan absoluteExpirationRelativeToNow, CancellationToken token = default)
17+
{
18+
return cache.SetAsync(key, value,
19+
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow }, token);
20+
}
21+
22+
1023
public static byte[] GetOrSet(this IDistributedCache cache, string key, Func<byte[]> valueFactory)
1124
{
1225
return cache.GetOrSet(key, valueFactory, new DistributedCacheEntryOptions());
1326
}
1427

28+
public static byte[] GetOrSet(this IDistributedCache cache, string key, Func<byte[]> valueFactory,
29+
TimeSpan absoluteExpirationRelativeToNow)
30+
{
31+
return cache.GetOrSet(key, valueFactory,
32+
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow });
33+
}
34+
1535
public static byte[] GetOrSet(this IDistributedCache cache, string key, Func<byte[]> valueFactory,
1636
DistributedCacheEntryOptions options)
1737
{
@@ -29,12 +49,20 @@ public static byte[] GetOrSet(this IDistributedCache cache, string key, Func<byt
2949
return value;
3050
}
3151

52+
3253
public static Task<byte[]> GetOrSetAsync(this IDistributedCache cache, string key, Func<Task<byte[]>> valueFactory,
3354
CancellationToken token = default)
3455
{
3556
return cache.GetOrSetAsync(key, valueFactory, new DistributedCacheEntryOptions(), token);
3657
}
3758

59+
public static Task<byte[]> GetOrSetAsync(this IDistributedCache cache, string key, Func<Task<byte[]>> valueFactory,
60+
TimeSpan absoluteExpirationRelativeToNow, CancellationToken token = default)
61+
{
62+
return cache.GetOrSetAsync(key, valueFactory,
63+
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow }, token);
64+
}
65+
3866
public static async Task<byte[]> GetOrSetAsync(this IDistributedCache cache, string key, Func<Task<byte[]>> valueFactory,
3967
DistributedCacheEntryOptions options, CancellationToken token = default)
4068
{
@@ -52,11 +80,19 @@ public static async Task<byte[]> GetOrSetAsync(this IDistributedCache cache, str
5280
return value;
5381
}
5482

83+
5584
public static string GetOrSetString(this IDistributedCache cache, string key, Func<string> valueFactory)
5685
{
5786
return cache.GetOrSetString(key, valueFactory, new DistributedCacheEntryOptions());
5887
}
5988

89+
public static string GetOrSetString(this IDistributedCache cache, string key, Func<string> valueFactory,
90+
TimeSpan absoluteExpirationRelativeToNow)
91+
{
92+
return cache.GetOrSetString(key, valueFactory,
93+
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow });
94+
}
95+
6096
public static string GetOrSetString(this IDistributedCache cache, string key, Func<string> valueFactory,
6197
DistributedCacheEntryOptions options)
6298
{
@@ -74,12 +110,20 @@ public static string GetOrSetString(this IDistributedCache cache, string key, Fu
74110
return value;
75111
}
76112

113+
77114
public static Task<string> GetOrSetStringAsync(this IDistributedCache cache, string key,
78115
Func<Task<string>> valueFactory, CancellationToken token = default)
79116
{
80117
return cache.GetOrSetStringAsync(key, valueFactory, new DistributedCacheEntryOptions(), token);
81118
}
82119

120+
public static Task<string> GetOrSetStringAsync(this IDistributedCache cache, string key,
121+
Func<Task<string>> valueFactory, TimeSpan absoluteExpirationRelativeToNow, CancellationToken token = default)
122+
{
123+
return cache.GetOrSetStringAsync(key, valueFactory,
124+
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow }, token);
125+
}
126+
83127
public static async Task<string> GetOrSetStringAsync(this IDistributedCache cache, string key,
84128
Func<Task<string>> valueFactory, DistributedCacheEntryOptions options, CancellationToken token = default)
85129
{

0 commit comments

Comments
 (0)