Compare commits

...

4 Commits

Author SHA1 Message Date
Piotr Szmyd
dc3f407036 Unit tests for Cached<T> 2014-12-01 02:42:21 +01:00
Piotr Szmyd
7c9af76293 Ensuring Cached<T> is convertible and comparable to T.
Added expiration date to entry metadata.
2014-12-01 02:42:05 +01:00
Piotr Szmyd
2220e8b113 Updated Redis cache storage provider. 2014-11-19 07:37:55 +01:00
Piotr Szmyd
f98f9dd3a9 Business cache return value rework. Using Cached<T> instead of plain object, 2014-11-19 06:17:39 +01:00
7 changed files with 202 additions and 33 deletions

View 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));
}
}
}

View File

@@ -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>

View File

@@ -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));
}

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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) {