mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-08 00:14:31 +08:00
Compare commits
4 Commits
issue/5848
...
feature/ca
Author | SHA1 | Date | |
---|---|---|---|
![]() |
dc3f407036 | ||
![]() |
7c9af76293 | ||
![]() |
2220e8b113 | ||
![]() |
f98f9dd3a9 |
47
src/Orchard.Tests.Modules/Caching/CachedTests.cs
Normal file
47
src/Orchard.Tests.Modules/Caching/CachedTests.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Caching.Services;
|
||||
|
||||
namespace Orchard.Tests.Modules.Email {
|
||||
[TestFixture]
|
||||
public class CachedTests {
|
||||
[Test]
|
||||
public void ShouldExplicitlyConvertToValueTypeT() {
|
||||
Cached<int> cached = 10;
|
||||
var raw = (int)cached;
|
||||
|
||||
Assert.That(raw, Is.EqualTo(10));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldConvertFromValueTypeT() {
|
||||
Cached<int> cached1 = 10;
|
||||
var cached2 = (Cached<int>)10;
|
||||
|
||||
Assert.That(cached1.Value, Is.EqualTo(10));
|
||||
Assert.That(cached2.Value, Is.EqualTo(10));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldImplicitlyConvertFromNull()
|
||||
{
|
||||
object x = null;
|
||||
Cached<object> cached = x;
|
||||
Assert.That(cached.HasValue, Is.EqualTo(false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldCompareWithObjectsOfTypeT() {
|
||||
int raw1 = 10;
|
||||
int raw2 = 20;
|
||||
Cached<int> cached = 10;
|
||||
|
||||
Assert.That(cached == raw1, Is.EqualTo(true));
|
||||
Assert.That(raw1 == cached, Is.EqualTo(true));
|
||||
|
||||
Assert.That(cached != raw2, Is.EqualTo(true));
|
||||
Assert.That(raw2 != cached, Is.EqualTo(true));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -141,6 +141,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Autoroute\DefaultSlugServiceTests.cs" />
|
||||
<Compile Include="Caching\CachedTests.cs" />
|
||||
<Compile Include="CodeGeneration\Commands\CodeGenerationCommandsTests.cs" />
|
||||
<Compile Include="Comments\Services\CommentServiceTests.cs" />
|
||||
<Compile Include="Email\EmailChannelTests.cs" />
|
||||
@@ -209,6 +210,10 @@
|
||||
<Project>{66fccd76-2761-47e3-8d11-b45d0001ddaa}</Project>
|
||||
<Name>Orchard.Autoroute</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.Caching\Orchard.Caching.csproj">
|
||||
<Project>{7528bf74-25c7-4abe-883a-443b4eec4776}</Project>
|
||||
<Name>Orchard.Caching</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.CodeGeneration\Orchard.CodeGeneration.csproj">
|
||||
<Project>{C0C45321-B51D-4D8D-9B7B-AA4C2E0B2962}</Project>
|
||||
<Name>Orchard.CodeGeneration</Name>
|
||||
|
@@ -16,7 +16,7 @@ namespace Orchard.Caching.Services {
|
||||
_prefix = shellSettings.Name;
|
||||
}
|
||||
|
||||
public object Get<T>(string key) {
|
||||
public Cached<T> Get<T>(string key) {
|
||||
return _cacheStorageProvider.Get<T>(BuildFullKey(key));
|
||||
}
|
||||
|
||||
|
@@ -22,11 +22,18 @@ namespace Orchard.Caching.Services {
|
||||
|
||||
public void Put<T>(string key, T value) {
|
||||
// Keys are already prefixed by DefaultCacheService so no need to do it here again.
|
||||
_cache.Set(key, value, GetCacheItemPolicy(MemoryCache.InfiniteAbsoluteExpiration));
|
||||
_cache.Set(
|
||||
key,
|
||||
BuildCacheItem(value, ObjectCache.InfiniteAbsoluteExpiration),
|
||||
GetCacheItemPolicy(ObjectCache.InfiniteAbsoluteExpiration));
|
||||
}
|
||||
|
||||
public void Put<T>(string key, T value, TimeSpan validFor) {
|
||||
_cache.Set(key, value, GetCacheItemPolicy(new DateTimeOffset(_clock.UtcNow).ToOffset(validFor)));
|
||||
var expiration = new DateTimeOffset(_clock.UtcNow).ToOffset(validFor);
|
||||
_cache.Set(
|
||||
key,
|
||||
BuildCacheItem(value, expiration),
|
||||
GetCacheItemPolicy(expiration));
|
||||
}
|
||||
|
||||
public void Remove(string key) {
|
||||
@@ -39,23 +46,35 @@ namespace Orchard.Caching.Services {
|
||||
}
|
||||
}
|
||||
|
||||
public object Get<T>(string key) {
|
||||
public Cached<T> Get<T>(string key) {
|
||||
var value = _cache.Get(key);
|
||||
|
||||
// if the provided expression is non-null, and the provided object can
|
||||
// be cast to the provided type without causing an exception to be thrown
|
||||
if(value is T) {
|
||||
if (value is Cached<T>) {
|
||||
return (Cached<T>)value;
|
||||
}
|
||||
|
||||
if (value is T) {
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
return null;
|
||||
value = null;
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
private Cached<T> BuildCacheItem<T>(T item, DateTimeOffset expiration) {
|
||||
Cached<T> cached = item;
|
||||
cached.Expires = expiration;
|
||||
|
||||
return cached;
|
||||
}
|
||||
|
||||
private CacheItemPolicy GetCacheItemPolicy(DateTimeOffset absoluteExpiration) {
|
||||
var cacheItemPolicy = new CacheItemPolicy();
|
||||
var cacheItemPolicy = new CacheItemPolicy {
|
||||
AbsoluteExpiration = absoluteExpiration,
|
||||
SlidingExpiration = ObjectCache.NoSlidingExpiration
|
||||
};
|
||||
|
||||
cacheItemPolicy.AbsoluteExpiration = absoluteExpiration;
|
||||
cacheItemPolicy.SlidingExpiration = MemoryCache.NoSlidingExpiration;
|
||||
cacheItemPolicy.ChangeMonitors.Add(new TenantCacheClearMonitor(this));
|
||||
|
||||
return cacheItemPolicy;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Orchard.Caching.Services {
|
||||
public interface ICacheService : IDependency {
|
||||
object Get<T>(string key);
|
||||
Cached<T> Get<T>(string key);
|
||||
|
||||
void Put<T>(string key, T value);
|
||||
void Put<T>(string key, T value, TimeSpan validFor);
|
||||
@@ -12,30 +12,106 @@ namespace Orchard.Caching.Services {
|
||||
}
|
||||
|
||||
public static class CachingExtensions {
|
||||
public static object Get<T>(this ICacheService cacheService, string key, Func<T> factory) {
|
||||
var result = cacheService.Get<T>(key);
|
||||
|
||||
if (result == null) {
|
||||
var computed = factory();
|
||||
cacheService.Put(key, computed);
|
||||
return computed;
|
||||
}
|
||||
|
||||
// try to convert to T
|
||||
return result;
|
||||
public static Cached<T> Get<T>(this ICacheService cacheService, string key, Func<T> factory) {
|
||||
return cacheService.GetOrPut(key, factory, value => cacheService.Put(key, value));
|
||||
}
|
||||
|
||||
public static object Get<T>(this ICacheService cacheService, string key, Func<T> factory, TimeSpan validFor) {
|
||||
public static Cached<T> Get<T>(this ICacheService cacheService, string key, Func<T> factory, TimeSpan validFor) {
|
||||
return cacheService.GetOrPut(key, factory, value => cacheService.Put(key, value, validFor));
|
||||
}
|
||||
|
||||
private static Cached<T> GetOrPut<T>(this ICacheService cacheService, string key, Func<T> factory, Action<T> putter) {
|
||||
var result = cacheService.Get<T>(key);
|
||||
|
||||
if (result == null) {
|
||||
|
||||
if (!result.HasValue && factory != null) {
|
||||
var computed = factory();
|
||||
cacheService.Put(key, computed, validFor);
|
||||
putter(computed);
|
||||
return computed;
|
||||
}
|
||||
|
||||
// try to convert to T
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper class for a cache entry.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the wrapped cache value.</typeparam>
|
||||
public class Cached<T> {
|
||||
/// <summary>
|
||||
/// Cache entry value.
|
||||
/// </summary>
|
||||
public T Value { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an entry is not empty.
|
||||
/// </summary>
|
||||
public bool HasValue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Expiration date of this entry.
|
||||
/// </summary>
|
||||
public DateTimeOffset Expires { get; set; }
|
||||
|
||||
protected Cached(object value) {
|
||||
if (value == null) {
|
||||
HasValue = false;
|
||||
Value = default(T);
|
||||
}
|
||||
else {
|
||||
HasValue = true;
|
||||
Value = (T)value;
|
||||
}
|
||||
|
||||
Expires = DateTimeOffset.MaxValue;
|
||||
}
|
||||
|
||||
public static implicit operator Cached<T>(T value) {
|
||||
return new Cached<T>(value);
|
||||
}
|
||||
|
||||
public static explicit operator T(Cached<T> value) {
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public static bool operator ==(Cached<T> a, T b) {
|
||||
if (a == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.HasValue) {
|
||||
return a.Value.Equals(b);
|
||||
}
|
||||
|
||||
if (typeof(T).IsValueType) {
|
||||
return b.Equals(default(T));
|
||||
}
|
||||
|
||||
return b == null;
|
||||
}
|
||||
|
||||
public static bool operator ==(T a, Cached<T> b) {
|
||||
return b == a;
|
||||
}
|
||||
|
||||
public static bool operator !=(T a, Cached<T> b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Cached<T> a, T b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
if (!HasValue) return other == null;
|
||||
if (other == null) return false;
|
||||
return Value.Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HasValue ? Value.GetHashCode() : 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Orchard.Caching.Services {
|
||||
public interface ICacheStorageProvider : IDependency {
|
||||
object Get<T>(string key);
|
||||
Cached<T> Get<T>(string key);
|
||||
void Put<T>(string key, T value);
|
||||
void Put<T>(string key, T value, TimeSpan validFor);
|
||||
void Remove(string key);
|
||||
|
@@ -1,10 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Orchard.Caching.Services;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.Environment.Extensions;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Redis.Configuration;
|
||||
using Orchard.Redis.Extensions;
|
||||
using Orchard.Services;
|
||||
using StackExchange.Redis;
|
||||
using System;
|
||||
|
||||
@@ -17,6 +19,7 @@ namespace Orchard.Redis.Caching {
|
||||
|
||||
private readonly ShellSettings _shellSettings;
|
||||
private readonly IRedisConnectionProvider _redisConnectionProvider;
|
||||
private readonly IClock _clock;
|
||||
private readonly string _connectionString;
|
||||
|
||||
public IDatabase Database {
|
||||
@@ -25,16 +28,35 @@ namespace Orchard.Redis.Caching {
|
||||
}
|
||||
}
|
||||
|
||||
public RedisCacheStorageProvider(ShellSettings shellSettings, IRedisConnectionProvider redisConnectionProvider) {
|
||||
public RedisCacheStorageProvider(ShellSettings shellSettings, IRedisConnectionProvider redisConnectionProvider, IClock clock) {
|
||||
_shellSettings = shellSettings;
|
||||
_redisConnectionProvider = redisConnectionProvider;
|
||||
_clock = clock;
|
||||
_connectionString = _redisConnectionProvider.GetConnectionString(ConnectionStringKey);
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public object Get<T>(string key) {
|
||||
var json = Database.StringGet(GetLocalizedKey(key));
|
||||
return JsonConvert.DeserializeObject<T>(json);
|
||||
public Cached<T> Get<T>(string key) {
|
||||
var jsonTask = Database.StringGetAsync(GetLocalizedKey(key));
|
||||
var timeToLiveTask = Database.KeyTimeToLiveAsync(GetLocalizedKey(key));
|
||||
|
||||
Task.WaitAll(jsonTask, timeToLiveTask);
|
||||
|
||||
var json = jsonTask.Result;
|
||||
var timeToLive = timeToLiveTask.Result;
|
||||
|
||||
if (json.IsNullOrEmpty) {
|
||||
object value = null;
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
Cached<T> entry = JsonConvert.DeserializeObject<T>(json);
|
||||
|
||||
if (timeToLive.HasValue) {
|
||||
entry.Expires = new DateTimeOffset(_clock.UtcNow).ToOffset(timeToLive.Value);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
public void Put<T>(string key, T value) {
|
||||
|
Reference in New Issue
Block a user