mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Compare commits
4 Commits
task/maste
...
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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Autoroute\DefaultSlugServiceTests.cs" />
|
<Compile Include="Autoroute\DefaultSlugServiceTests.cs" />
|
||||||
|
<Compile Include="Caching\CachedTests.cs" />
|
||||||
<Compile Include="CodeGeneration\Commands\CodeGenerationCommandsTests.cs" />
|
<Compile Include="CodeGeneration\Commands\CodeGenerationCommandsTests.cs" />
|
||||||
<Compile Include="Comments\Services\CommentServiceTests.cs" />
|
<Compile Include="Comments\Services\CommentServiceTests.cs" />
|
||||||
<Compile Include="Email\EmailChannelTests.cs" />
|
<Compile Include="Email\EmailChannelTests.cs" />
|
||||||
@@ -209,6 +210,10 @@
|
|||||||
<Project>{66fccd76-2761-47e3-8d11-b45d0001ddaa}</Project>
|
<Project>{66fccd76-2761-47e3-8d11-b45d0001ddaa}</Project>
|
||||||
<Name>Orchard.Autoroute</Name>
|
<Name>Orchard.Autoroute</Name>
|
||||||
</ProjectReference>
|
</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">
|
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.CodeGeneration\Orchard.CodeGeneration.csproj">
|
||||||
<Project>{C0C45321-B51D-4D8D-9B7B-AA4C2E0B2962}</Project>
|
<Project>{C0C45321-B51D-4D8D-9B7B-AA4C2E0B2962}</Project>
|
||||||
<Name>Orchard.CodeGeneration</Name>
|
<Name>Orchard.CodeGeneration</Name>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Orchard.Caching.Services {
|
|||||||
_prefix = shellSettings.Name;
|
_prefix = shellSettings.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get<T>(string key) {
|
public Cached<T> Get<T>(string key) {
|
||||||
return _cacheStorageProvider.Get<T>(BuildFullKey(key));
|
return _cacheStorageProvider.Get<T>(BuildFullKey(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,11 +22,18 @@ namespace Orchard.Caching.Services {
|
|||||||
|
|
||||||
public void Put<T>(string key, T value) {
|
public void Put<T>(string key, T value) {
|
||||||
// Keys are already prefixed by DefaultCacheService so no need to do it here again.
|
// 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) {
|
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) {
|
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);
|
var value = _cache.Get(key);
|
||||||
|
|
||||||
// if the provided expression is non-null, and the provided object can
|
// 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
|
// 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 (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) {
|
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));
|
cacheItemPolicy.ChangeMonitors.Add(new TenantCacheClearMonitor(this));
|
||||||
|
|
||||||
return cacheItemPolicy;
|
return cacheItemPolicy;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Orchard.Caching.Services {
|
namespace Orchard.Caching.Services {
|
||||||
public interface ICacheService : IDependency {
|
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);
|
||||||
void Put<T>(string key, T value, TimeSpan validFor);
|
void Put<T>(string key, T value, TimeSpan validFor);
|
||||||
@@ -12,30 +12,106 @@ namespace Orchard.Caching.Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class CachingExtensions {
|
public static class CachingExtensions {
|
||||||
public static object Get<T>(this ICacheService cacheService, string key, Func<T> factory) {
|
public static Cached<T> Get<T>(this ICacheService cacheService, string key, Func<T> factory) {
|
||||||
var result = cacheService.Get<T>(key);
|
return cacheService.GetOrPut(key, factory, value => cacheService.Put(key, value));
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
var computed = factory();
|
|
||||||
cacheService.Put(key, computed);
|
|
||||||
return computed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to convert to T
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
var result = cacheService.Get<T>(key);
|
||||||
|
|
||||||
if (result == null) {
|
if (!result.HasValue && factory != null) {
|
||||||
var computed = factory();
|
var computed = factory();
|
||||||
cacheService.Put(key, computed, validFor);
|
putter(computed);
|
||||||
return computed;
|
return computed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to convert to T
|
|
||||||
return result;
|
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 {
|
namespace Orchard.Caching.Services {
|
||||||
public interface ICacheStorageProvider : IDependency {
|
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);
|
||||||
void Put<T>(string key, T value, TimeSpan validFor);
|
void Put<T>(string key, T value, TimeSpan validFor);
|
||||||
void Remove(string key);
|
void Remove(string key);
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using Newtonsoft.Json;
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Orchard.Caching.Services;
|
using Orchard.Caching.Services;
|
||||||
using Orchard.Environment.Configuration;
|
using Orchard.Environment.Configuration;
|
||||||
using Orchard.Environment.Extensions;
|
using Orchard.Environment.Extensions;
|
||||||
using Orchard.Logging;
|
using Orchard.Logging;
|
||||||
using Orchard.Redis.Configuration;
|
using Orchard.Redis.Configuration;
|
||||||
using Orchard.Redis.Extensions;
|
using Orchard.Redis.Extensions;
|
||||||
|
using Orchard.Services;
|
||||||
using StackExchange.Redis;
|
using StackExchange.Redis;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
@@ -17,6 +19,7 @@ namespace Orchard.Redis.Caching {
|
|||||||
|
|
||||||
private readonly ShellSettings _shellSettings;
|
private readonly ShellSettings _shellSettings;
|
||||||
private readonly IRedisConnectionProvider _redisConnectionProvider;
|
private readonly IRedisConnectionProvider _redisConnectionProvider;
|
||||||
|
private readonly IClock _clock;
|
||||||
private readonly string _connectionString;
|
private readonly string _connectionString;
|
||||||
|
|
||||||
public IDatabase Database {
|
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;
|
_shellSettings = shellSettings;
|
||||||
_redisConnectionProvider = redisConnectionProvider;
|
_redisConnectionProvider = redisConnectionProvider;
|
||||||
|
_clock = clock;
|
||||||
_connectionString = _redisConnectionProvider.GetConnectionString(ConnectionStringKey);
|
_connectionString = _redisConnectionProvider.GetConnectionString(ConnectionStringKey);
|
||||||
Logger = NullLogger.Instance;
|
Logger = NullLogger.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get<T>(string key) {
|
public Cached<T> Get<T>(string key) {
|
||||||
var json = Database.StringGet(GetLocalizedKey(key));
|
var jsonTask = Database.StringGetAsync(GetLocalizedKey(key));
|
||||||
return JsonConvert.DeserializeObject<T>(json);
|
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) {
|
public void Put<T>(string key, T value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user