Skip to content

Reimplement with latest ServiceStack.Redis #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

Expand Down
12 changes: 12 additions & 0 deletions src/Microsoft.Extensions.Caching.ServiceStackRedis/CacheEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Microsoft.Extensions.Caching.ServiceStackRedis
{
internal struct CacheEntry
{
public string Value { get; set; }
public TimeSpan SlidingExpiration { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<PackageProjectUrl>https://github.com/cnblogs/ServiceStackRedisCache</PackageProjectUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/cnblogs/ServiceStackRedisCache</RepositoryUrl>
<LangVersion>Latest</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,92 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using ServiceStack.Redis;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using ServiceStack.Redis;

namespace Microsoft.Extensions.Caching.ServiceStackRedis
{
public class ServiceStackRedisCache : IDistributedCache
{
private readonly IRedisClientsManager _redisManager;
private readonly IRedisClientsManager _redisClientsManager;
private readonly ServiceStackRedisCacheOptions _options;

public ServiceStackRedisCache(IOptions<ServiceStackRedisCacheOptions> optionsAccessor)
public ServiceStackRedisCache(IRedisClientsManager redisClientsManager)
{
RedisConfig.VerifyMasterConnections = false;
_redisClientsManager = redisClientsManager;
}

public byte[] Get(string key)
{
if (optionsAccessor == null)
if (key == null)
{
throw new ArgumentNullException(nameof(optionsAccessor));
throw new ArgumentNullException(nameof(key));
}

_options = optionsAccessor.Value;
using var client = _redisClientsManager.GetClient();
if (!client.ContainsKey(key))
{
return null;
}

var host = $"{_options.Password}@{_options.Host}:{_options.Port}";
RedisConfig.VerifyMasterConnections = false;
_redisManager = new RedisManagerPool(host);
var values = client.GetValuesFromHash(key, nameof(CacheEntry.Value), nameof(CacheEntry.SlidingExpiration));

if (TimeSpan.TryParse(values[1], out var sldExp))
{
Refresh(key, sldExp);
}

return Encoding.UTF8.GetBytes(values[0]);
}

public byte[] Get(string key)
public async Task<byte[]> GetAsync(string key, CancellationToken token = default)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}

using (var client = _redisManager.GetClient() as IRedisNativeClient)
await using var client = await _redisClientsManager.GetClientAsync();
if (!await client.ContainsKeyAsync(key))
{
if (client.Exists(key) == 1)
{
return client.Get(key);
}
return null;
}

var values = await client.GetValuesFromHashAsync(key, nameof(CacheEntry.Value), nameof(CacheEntry.SlidingExpiration));

if (TimeSpan.TryParse(values[1], out var slbExp))
{
await RefreshAsync(key, slbExp);
}
return null;

return Encoding.UTF8.GetBytes(values[0]);
}

public Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken))
public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
{
return Task.FromResult(Get(key));
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}

if (value == null)
{
throw new ArgumentNullException(nameof(value));
}

if (options == null)
{
throw new ArgumentNullException(nameof(options));
}

using var client = _redisClientsManager.GetClient();
client.SetEntryInHash(key, nameof(CacheEntry.Value), Encoding.UTF8.GetString(value));
SetExpiration(client, key, options);
}

public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken))
{
if (key == null)
{
Expand All @@ -66,97 +103,131 @@ public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
throw new ArgumentNullException(nameof(options));
}

using (var client = _redisManager.GetClient() as IRedisNativeClient)
await using var client = await _redisClientsManager.GetClientAsync();
await client.SetEntryInHashAsync(key, nameof(CacheEntry.Value), Encoding.UTF8.GetString(value));
await SetExpirationAsync(client, key, options);
}

public void Refresh(string key)
{
Refresh(key, null);
}

public void Refresh(string key, TimeSpan? sldExp)
{
if (key == null)
{
var expireInSeconds = GetExpireInSeconds(options);
if (expireInSeconds > 0)
throw new ArgumentNullException(nameof(key));
}

using var client = _redisClientsManager.GetClient();
var ttl = client.GetTimeToLive(key);
if (ttl.HasValue)
{
if (!sldExp.HasValue)
{
client.SetEx(key, expireInSeconds, value);
client.SetEx(GetExpirationKey(key), expireInSeconds, Encoding.UTF8.GetBytes(expireInSeconds.ToString()));
var sldExpStr = client.GetValueFromHash(key, nameof(CacheEntry.SlidingExpiration));
if (TimeSpan.TryParse(sldExpStr, out var cachedSldExp))
{
sldExp = cachedSldExp;
}
}
else

if (sldExp.HasValue && ttl < sldExp)
{
client.Set(key, value);
client.ExpireEntryIn(key, sldExp.Value);
}
}
}

public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken))
public async Task RefreshAsync(string key, CancellationToken token)
{
return Task.Run(() => Set(key, value, options));
await RefreshAsync(key, null);
}

public void Refresh(string key)
public async Task RefreshAsync(string key, TimeSpan? sldExp)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}

using (var client = _redisManager.GetClient() as IRedisNativeClient)
await using var client = await _redisClientsManager.GetClientAsync();
var ttl = await client.GetTimeToLiveAsync(key);
if (ttl.HasValue)
{
if (client.Exists(key) == 1)
if (!sldExp.HasValue)
{
var value = client.Get(key);
if (value != null)
var sldExpStr = await client.GetValueFromHashAsync(key, nameof(CacheEntry.SlidingExpiration));
if (TimeSpan.TryParse(sldExpStr, out var cachedSldExp))
{
var expirationValue = client.Get(GetExpirationKey(key));
if (expirationValue != null)
{
client.Expire(key, int.Parse(Encoding.UTF8.GetString(expirationValue)));
}
sldExp = cachedSldExp;
}
}

if (sldExp.HasValue && ttl < sldExp)
{
await client.ExpireEntryInAsync(key, sldExp.Value);
}
}
}

public Task RefreshAsync(string key, CancellationToken token = default(CancellationToken))
public void Remove(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}

return Task.Run(() => Refresh(key));
using var client = _redisClientsManager.GetClient();
client.Remove(key);
}

public void Remove(string key)
public async Task RemoveAsync(string key, CancellationToken token = default)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}

using (var client = _redisManager.GetClient() as IRedisNativeClient)
{
client.Del(key);
}
await using var client = await _redisClientsManager.GetClientAsync();
await client.RemoveAsync(key);
}

public Task RemoveAsync(string key, CancellationToken token = default(CancellationToken))
{
return Task.Run(() => Remove(key));
}

private int GetExpireInSeconds(DistributedCacheEntryOptions options)
private void SetExpiration(IRedisClient client, string key, DistributedCacheEntryOptions options)
{
if (options.SlidingExpiration.HasValue)
{
return (int)options.SlidingExpiration.Value.TotalSeconds;
var sldExp = options.SlidingExpiration.Value;
client.SetEntryInHash(key, nameof(CacheEntry.SlidingExpiration), sldExp.ToString());
client.ExpireEntryIn(key, sldExp);
}
else if (options.AbsoluteExpirationRelativeToNow.HasValue)
{
return (int)options.AbsoluteExpirationRelativeToNow.Value.TotalSeconds;
client.ExpireEntryAt(key, DateTime.Now + options.AbsoluteExpirationRelativeToNow.Value);
}
else
else if (options.AbsoluteExpiration.HasValue)
{
return 0;
client.ExpireEntryAt(key, options.AbsoluteExpiration.Value.DateTime);
}
}

private string GetExpirationKey(string key)
private async Task SetExpirationAsync(IRedisClientAsync client, string key, DistributedCacheEntryOptions options)
{
return key + $"-{nameof(DistributedCacheEntryOptions)}";
if (options.SlidingExpiration.HasValue)
{
var sldExp = options.SlidingExpiration.Value;
await client.SetEntryInHashAsync(key, nameof(CacheEntry.SlidingExpiration), sldExp.ToString());
await client.ExpireEntryInAsync(key, sldExp);
}
else if (options.AbsoluteExpirationRelativeToNow.HasValue)
{
await client.ExpireEntryAtAsync(key, DateTime.Now + options.AbsoluteExpirationRelativeToNow.Value);
}
else if (options.AbsoluteExpiration.HasValue)
{
await client.ExpireEntryAtAsync(key, options.AbsoluteExpiration.Value.DateTime);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using System;
using System.ComponentModel;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.ServiceStackRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using ServiceStack.Redis;

namespace Microsoft.Extensions.DependencyInjection
{
Expand Down Expand Up @@ -49,6 +52,14 @@ public static IServiceCollection AddDistributedServiceStackRedisCache(this IServ
}

services.Configure<ServiceStackRedisCacheOptions>(section);

services.TryAddSingleton<IRedisClientsManager>(provider =>
{
var options = provider.GetRequiredService<IOptions<ServiceStackRedisCacheOptions>>().Value;
var host = $"{options.Password}@{options.Host}:{options.Port}";
return new RedisManagerPool(host);
});

services.TryAddSingleton<IDistributedCache, ServiceStackRedisCache>();

return services;
Expand Down
12 changes: 7 additions & 5 deletions test/ServiceStackRedisCacheTests/DistributedCacheFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using ServiceStack.Redis;
using Xunit;
using Xunit.Abstractions;

Expand All @@ -16,22 +17,23 @@ namespace ServiceStackRedisCacheTests;
public class DistributedCacheFixture
{
public IDistributedCache DistributedCache { get; private set; }
public string KeyPostfix { get; private set; }
public IRedisClientsManager RedisClientManager { get; private set; }

public DistributedCacheFixture()
{
DistributedCache = GetInstance();
KeyPostfix = "-" + Guid.NewGuid();
using IServiceScope scope = GetServiceProvider().CreateScope();
DistributedCache = scope.ServiceProvider.GetRequiredService<IDistributedCache>();
RedisClientManager = scope.ServiceProvider.GetRequiredService<IRedisClientsManager>();
}

private IDistributedCache GetInstance()
private IServiceProvider GetServiceProvider()
{
IServiceCollection services = new ServiceCollection();
IConfiguration conf = new ConfigurationBuilder().
AddJsonFile("appsettings.json", optional: false)
.Build();
services.AddSingleton(conf);
services.AddDistributedServiceStackRedisCache("redis");
return services.BuildServiceProvider().GetRequiredService<IDistributedCache>();
return services.BuildServiceProvider();
}
}
Loading