Skip to content

Commit 4756348

Browse files
authored
Merge pull request #8 from cnblogs/reimplement-with-latest-servicestack-redis
Reimplement with latest ServiceStack.Redis
2 parents 46e132b + 22d10eb commit 4756348

File tree

7 files changed

+246
-88
lines changed

7 files changed

+246
-88
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: CI
22

33
on:
44
push:
5+
branches: [ "main" ]
56
pull_request:
67
branches: [ "main" ]
78

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace Microsoft.Extensions.Caching.ServiceStackRedis
6+
{
7+
internal struct CacheEntry
8+
{
9+
public string Value { get; set; }
10+
public TimeSpan SlidingExpiration { get; set; }
11+
}
12+
}

src/Microsoft.Extensions.Caching.ServiceStackRedis/Microsoft.Extensions.Caching.ServiceStackRedis.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<PackageProjectUrl>https://github.com/cnblogs/ServiceStackRedisCache</PackageProjectUrl>
1010
<RepositoryType>git</RepositoryType>
1111
<RepositoryUrl>https://github.com/cnblogs/ServiceStackRedisCache</RepositoryUrl>
12+
<LangVersion>Latest</LangVersion>
1213
</PropertyGroup>
1314

1415
<ItemGroup>
Lines changed: 130 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,92 @@
1-
using Microsoft.Extensions.Caching.Distributed;
2-
using Microsoft.Extensions.Options;
3-
using ServiceStack.Redis;
41
using System;
52
using System.Text;
63
using System.Threading;
74
using System.Threading.Tasks;
5+
using Microsoft.Extensions.Caching.Distributed;
6+
using ServiceStack.Redis;
87

98
namespace Microsoft.Extensions.Caching.ServiceStackRedis
109
{
1110
public class ServiceStackRedisCache : IDistributedCache
1211
{
13-
private readonly IRedisClientsManager _redisManager;
12+
private readonly IRedisClientsManager _redisClientsManager;
1413
private readonly ServiceStackRedisCacheOptions _options;
1514

16-
public ServiceStackRedisCache(IOptions<ServiceStackRedisCacheOptions> optionsAccessor)
15+
public ServiceStackRedisCache(IRedisClientsManager redisClientsManager)
16+
{
17+
RedisConfig.VerifyMasterConnections = false;
18+
_redisClientsManager = redisClientsManager;
19+
}
20+
21+
public byte[] Get(string key)
1722
{
18-
if (optionsAccessor == null)
23+
if (key == null)
1924
{
20-
throw new ArgumentNullException(nameof(optionsAccessor));
25+
throw new ArgumentNullException(nameof(key));
2126
}
2227

23-
_options = optionsAccessor.Value;
28+
using var client = _redisClientsManager.GetClient();
29+
if (!client.ContainsKey(key))
30+
{
31+
return null;
32+
}
2433

25-
var host = $"{_options.Password}@{_options.Host}:{_options.Port}";
26-
RedisConfig.VerifyMasterConnections = false;
27-
_redisManager = new RedisManagerPool(host);
34+
var values = client.GetValuesFromHash(key, nameof(CacheEntry.Value), nameof(CacheEntry.SlidingExpiration));
35+
36+
if (TimeSpan.TryParse(values[1], out var sldExp))
37+
{
38+
Refresh(key, sldExp);
39+
}
40+
41+
return Encoding.UTF8.GetBytes(values[0]);
2842
}
2943

30-
public byte[] Get(string key)
44+
public async Task<byte[]> GetAsync(string key, CancellationToken token = default)
3145
{
3246
if (key == null)
3347
{
3448
throw new ArgumentNullException(nameof(key));
3549
}
3650

37-
using (var client = _redisManager.GetClient() as IRedisNativeClient)
51+
await using var client = await _redisClientsManager.GetClientAsync();
52+
if (!await client.ContainsKeyAsync(key))
3853
{
39-
if (client.Exists(key) == 1)
40-
{
41-
return client.Get(key);
42-
}
54+
return null;
55+
}
56+
57+
var values = await client.GetValuesFromHashAsync(key, nameof(CacheEntry.Value), nameof(CacheEntry.SlidingExpiration));
58+
59+
if (TimeSpan.TryParse(values[1], out var slbExp))
60+
{
61+
await RefreshAsync(key, slbExp);
4362
}
44-
return null;
63+
64+
return Encoding.UTF8.GetBytes(values[0]);
4565
}
4666

47-
public Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken))
67+
public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
4868
{
49-
return Task.FromResult(Get(key));
69+
if (key == null)
70+
{
71+
throw new ArgumentNullException(nameof(key));
72+
}
73+
74+
if (value == null)
75+
{
76+
throw new ArgumentNullException(nameof(value));
77+
}
78+
79+
if (options == null)
80+
{
81+
throw new ArgumentNullException(nameof(options));
82+
}
83+
84+
using var client = _redisClientsManager.GetClient();
85+
client.SetEntryInHash(key, nameof(CacheEntry.Value), Encoding.UTF8.GetString(value));
86+
SetExpiration(client, key, options);
5087
}
5188

52-
public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
89+
public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken))
5390
{
5491
if (key == null)
5592
{
@@ -66,97 +103,131 @@ public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
66103
throw new ArgumentNullException(nameof(options));
67104
}
68105

69-
using (var client = _redisManager.GetClient() as IRedisNativeClient)
106+
await using var client = await _redisClientsManager.GetClientAsync();
107+
await client.SetEntryInHashAsync(key, nameof(CacheEntry.Value), Encoding.UTF8.GetString(value));
108+
await SetExpirationAsync(client, key, options);
109+
}
110+
111+
public void Refresh(string key)
112+
{
113+
Refresh(key, null);
114+
}
115+
116+
public void Refresh(string key, TimeSpan? sldExp)
117+
{
118+
if (key == null)
70119
{
71-
var expireInSeconds = GetExpireInSeconds(options);
72-
if (expireInSeconds > 0)
120+
throw new ArgumentNullException(nameof(key));
121+
}
122+
123+
using var client = _redisClientsManager.GetClient();
124+
var ttl = client.GetTimeToLive(key);
125+
if (ttl.HasValue)
126+
{
127+
if (!sldExp.HasValue)
73128
{
74-
client.SetEx(key, expireInSeconds, value);
75-
client.SetEx(GetExpirationKey(key), expireInSeconds, Encoding.UTF8.GetBytes(expireInSeconds.ToString()));
129+
var sldExpStr = client.GetValueFromHash(key, nameof(CacheEntry.SlidingExpiration));
130+
if (TimeSpan.TryParse(sldExpStr, out var cachedSldExp))
131+
{
132+
sldExp = cachedSldExp;
133+
}
76134
}
77-
else
135+
136+
if (sldExp.HasValue && ttl < sldExp)
78137
{
79-
client.Set(key, value);
138+
client.ExpireEntryIn(key, sldExp.Value);
80139
}
81140
}
82141
}
83142

84-
public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken))
143+
public async Task RefreshAsync(string key, CancellationToken token)
85144
{
86-
return Task.Run(() => Set(key, value, options));
145+
await RefreshAsync(key, null);
87146
}
88147

89-
public void Refresh(string key)
148+
public async Task RefreshAsync(string key, TimeSpan? sldExp)
90149
{
91150
if (key == null)
92151
{
93152
throw new ArgumentNullException(nameof(key));
94153
}
95154

96-
using (var client = _redisManager.GetClient() as IRedisNativeClient)
155+
await using var client = await _redisClientsManager.GetClientAsync();
156+
var ttl = await client.GetTimeToLiveAsync(key);
157+
if (ttl.HasValue)
97158
{
98-
if (client.Exists(key) == 1)
159+
if (!sldExp.HasValue)
99160
{
100-
var value = client.Get(key);
101-
if (value != null)
161+
var sldExpStr = await client.GetValueFromHashAsync(key, nameof(CacheEntry.SlidingExpiration));
162+
if (TimeSpan.TryParse(sldExpStr, out var cachedSldExp))
102163
{
103-
var expirationValue = client.Get(GetExpirationKey(key));
104-
if (expirationValue != null)
105-
{
106-
client.Expire(key, int.Parse(Encoding.UTF8.GetString(expirationValue)));
107-
}
164+
sldExp = cachedSldExp;
108165
}
109166
}
167+
168+
if (sldExp.HasValue && ttl < sldExp)
169+
{
170+
await client.ExpireEntryInAsync(key, sldExp.Value);
171+
}
110172
}
111173
}
112174

113-
public Task RefreshAsync(string key, CancellationToken token = default(CancellationToken))
175+
public void Remove(string key)
114176
{
115177
if (key == null)
116178
{
117179
throw new ArgumentNullException(nameof(key));
118180
}
119181

120-
return Task.Run(() => Refresh(key));
182+
using var client = _redisClientsManager.GetClient();
183+
client.Remove(key);
121184
}
122185

123-
public void Remove(string key)
186+
public async Task RemoveAsync(string key, CancellationToken token = default)
124187
{
125188
if (key == null)
126189
{
127190
throw new ArgumentNullException(nameof(key));
128191
}
129192

130-
using (var client = _redisManager.GetClient() as IRedisNativeClient)
131-
{
132-
client.Del(key);
133-
}
193+
await using var client = await _redisClientsManager.GetClientAsync();
194+
await client.RemoveAsync(key);
134195
}
135196

136-
public Task RemoveAsync(string key, CancellationToken token = default(CancellationToken))
137-
{
138-
return Task.Run(() => Remove(key));
139-
}
140-
141-
private int GetExpireInSeconds(DistributedCacheEntryOptions options)
197+
private void SetExpiration(IRedisClient client, string key, DistributedCacheEntryOptions options)
142198
{
143199
if (options.SlidingExpiration.HasValue)
144200
{
145-
return (int)options.SlidingExpiration.Value.TotalSeconds;
201+
var sldExp = options.SlidingExpiration.Value;
202+
client.SetEntryInHash(key, nameof(CacheEntry.SlidingExpiration), sldExp.ToString());
203+
client.ExpireEntryIn(key, sldExp);
146204
}
147205
else if (options.AbsoluteExpirationRelativeToNow.HasValue)
148206
{
149-
return (int)options.AbsoluteExpirationRelativeToNow.Value.TotalSeconds;
207+
client.ExpireEntryAt(key, DateTime.Now + options.AbsoluteExpirationRelativeToNow.Value);
150208
}
151-
else
209+
else if (options.AbsoluteExpiration.HasValue)
152210
{
153-
return 0;
211+
client.ExpireEntryAt(key, options.AbsoluteExpiration.Value.DateTime);
154212
}
155213
}
156214

157-
private string GetExpirationKey(string key)
215+
private async Task SetExpirationAsync(IRedisClientAsync client, string key, DistributedCacheEntryOptions options)
158216
{
159-
return key + $"-{nameof(DistributedCacheEntryOptions)}";
217+
if (options.SlidingExpiration.HasValue)
218+
{
219+
var sldExp = options.SlidingExpiration.Value;
220+
await client.SetEntryInHashAsync(key, nameof(CacheEntry.SlidingExpiration), sldExp.ToString());
221+
await client.ExpireEntryInAsync(key, sldExp);
222+
}
223+
else if (options.AbsoluteExpirationRelativeToNow.HasValue)
224+
{
225+
await client.ExpireEntryAtAsync(key, DateTime.Now + options.AbsoluteExpirationRelativeToNow.Value);
226+
}
227+
else if (options.AbsoluteExpiration.HasValue)
228+
{
229+
await client.ExpireEntryAtAsync(key, options.AbsoluteExpiration.Value.DateTime);
230+
}
160231
}
161232
}
162233
}

src/Microsoft.Extensions.Caching.ServiceStackRedis/ServiceStackRedisCacheServiceCollectionExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
using System;
2+
using System.ComponentModel;
23
using Microsoft.Extensions.Caching.Distributed;
34
using Microsoft.Extensions.Caching.ServiceStackRedis;
45
using Microsoft.Extensions.Configuration;
56
using Microsoft.Extensions.DependencyInjection.Extensions;
7+
using Microsoft.Extensions.Options;
8+
using ServiceStack.Redis;
69

710
namespace Microsoft.Extensions.DependencyInjection
811
{
@@ -49,6 +52,14 @@ public static IServiceCollection AddDistributedServiceStackRedisCache(this IServ
4952
}
5053

5154
services.Configure<ServiceStackRedisCacheOptions>(section);
55+
56+
services.TryAddSingleton<IRedisClientsManager>(provider =>
57+
{
58+
var options = provider.GetRequiredService<IOptions<ServiceStackRedisCacheOptions>>().Value;
59+
var host = $"{options.Password}@{options.Host}:{options.Port}";
60+
return new RedisManagerPool(host);
61+
});
62+
5263
services.TryAddSingleton<IDistributedCache, ServiceStackRedisCache>();
5364

5465
return services;

test/ServiceStackRedisCacheTests/DistributedCacheFixture.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.Extensions.Configuration;
99
using Microsoft.Extensions.DependencyInjection;
1010
using Microsoft.Extensions.DependencyInjection.Extensions;
11+
using ServiceStack.Redis;
1112
using Xunit;
1213
using Xunit.Abstractions;
1314

@@ -16,22 +17,23 @@ namespace ServiceStackRedisCacheTests;
1617
public class DistributedCacheFixture
1718
{
1819
public IDistributedCache DistributedCache { get; private set; }
19-
public string KeyPostfix { get; private set; }
20+
public IRedisClientsManager RedisClientManager { get; private set; }
2021

2122
public DistributedCacheFixture()
2223
{
23-
DistributedCache = GetInstance();
24-
KeyPostfix = "-" + Guid.NewGuid();
24+
using IServiceScope scope = GetServiceProvider().CreateScope();
25+
DistributedCache = scope.ServiceProvider.GetRequiredService<IDistributedCache>();
26+
RedisClientManager = scope.ServiceProvider.GetRequiredService<IRedisClientsManager>();
2527
}
2628

27-
private IDistributedCache GetInstance()
29+
private IServiceProvider GetServiceProvider()
2830
{
2931
IServiceCollection services = new ServiceCollection();
3032
IConfiguration conf = new ConfigurationBuilder().
3133
AddJsonFile("appsettings.json", optional: false)
3234
.Build();
3335
services.AddSingleton(conf);
3436
services.AddDistributedServiceStackRedisCache("redis");
35-
return services.BuildServiceProvider().GetRequiredService<IDistributedCache>();
37+
return services.BuildServiceProvider();
3638
}
3739
}

0 commit comments

Comments
 (0)