Created
January 2, 2014 09:14
-
-
Save feanz/2a5b8659a17531db0f86 to your computer and use it in GitHub Desktop.
Cache Infrastucture
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[AttributeUsage(AttributeTargets.Class,AllowMultiple = false, Inherited = false)] | |
public class CacheAttribute : Attribute | |
{ | |
/// <summary> | |
/// The name of the cache profile to be used | |
/// </summary> | |
public string ProfileName { get; set; } | |
/// <summary> | |
/// Lifespan of the response in the cache | |
/// </summary> | |
public TimeSpan Lifespan | |
{ | |
get { return new TimeSpan(Days, Hours, Minutes, Seconds); } | |
} | |
/// <summary> | |
/// Whether caching is enabled | |
/// </summary> | |
public bool Disabled { get; set; } | |
/// <summary> | |
/// Days the element to be cached should live in the cache | |
/// </summary> | |
public int Days { get; set; } | |
/// <summary> | |
/// Hours the element to be cached should live in the cache | |
/// </summary> | |
public int Hours { get; set; } | |
/// <summary> | |
/// Minutes the element to be cached should live in the cache | |
/// </summary> | |
public int Minutes { get; set; } | |
/// <summary> | |
/// Seconds the items should live in the cache | |
/// </summary> | |
public int Seconds { get; set; } | |
public bool NoDurationSet() | |
{ | |
return Days == 0 && Hours == 0 && Minutes == 0 && Seconds == 0; | |
} | |
/// <summary> | |
/// The type of cache required for the item | |
/// </summary> | |
public CacheType CacheType { get; set; } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Base class for <see cref="ICache" /> implementations | |
/// </summary> | |
public abstract class CacheBase : ICache | |
{ | |
protected readonly ICacheConfiguration _configuration; | |
protected CacheBase(ICacheConfiguration configuration) | |
{ | |
_configuration = configuration; | |
} | |
protected virtual bool ItemsNeedSerializing | |
{ | |
get | |
{ | |
return false; | |
} | |
} | |
public abstract CacheType CacheType { get; } | |
public void Initialise() | |
{ | |
try | |
{ | |
InitialiseInternal(); | |
} | |
catch (Exception ex) | |
{ | |
this.Log().Error(string.Format("CacheBase.Initialise - failed, NullCache will be used. CacheName: {0}", _configuration.DefaultCacheName), ex); | |
} | |
} | |
public void Set(string key, object value) | |
{ | |
try | |
{ | |
value = PreProcess(value); | |
SetInternal(key, value); | |
} | |
catch (Exception ex) | |
{ | |
this.Log().WarnFormat("CacheBase.Set - failed, item not cached. Message: {0}", ex.Message); | |
} | |
} | |
void ICache.Set(string key, object value, DateTime expiresAt) | |
{ | |
try | |
{ | |
value = PreProcess(value); | |
SetInternal(key, value, expiresAt); | |
} | |
catch (Exception ex) | |
{ | |
this.Log().WarnFormat("CacheBase.Set - failed, item not cached. Message: {0}", ex.Message); | |
} | |
} | |
public void Set(string key, object value, TimeSpan validFor) | |
{ | |
try | |
{ | |
value = PreProcess(value); | |
SetInternal(key, value, validFor); | |
} | |
catch (Exception ex) | |
{ | |
this.Log().WarnFormat("CacheBase.Set - failed, item not cached. Message: {0}", ex.Message); | |
} | |
} | |
public T Get<T>(string key) | |
{ | |
return (T) Get(typeof (T), key); | |
} | |
public object Get(Type type, string key) | |
{ | |
object item = null; | |
try | |
{ | |
item = GetInternal(key); | |
} | |
catch (Exception ex) | |
{ | |
this.Log().WarnFormat("CacheBase.Get - failed, item not cached. Message: {0}", ex.Message); | |
} | |
return PostProcess(type, item); | |
} | |
public void Remove(string key) | |
{ | |
try | |
{ | |
RemoveInternal(key); | |
} | |
catch (Exception ex) | |
{ | |
this.Log().WarnFormat("CacheBase.Remove - failed, item not cached. Message: {0}", ex.Message); | |
} | |
} | |
public bool Exists(string key) | |
{ | |
var exists = false; | |
try | |
{ | |
exists = ExistsInternal(key); | |
} | |
catch (Exception ex) | |
{ | |
this.Log().WarnFormat("CacheBase.Exists - failed, item not cached. Message: {0}", ex.Message); | |
} | |
return exists; | |
} | |
protected abstract bool ExistsInternal(string key); | |
protected abstract object GetInternal(string key); | |
protected abstract void InitialiseInternal(); | |
protected abstract void RemoveInternal(string key); | |
protected abstract void SetInternal(string key, object value); | |
protected abstract void SetInternal(string key, object value, DateTime expiresAt); | |
protected abstract void SetInternal(string key, object value, TimeSpan validFor); | |
private object PostProcess(Type type, object value) | |
{ | |
//not doing anything at the moment | |
return value; | |
} | |
private object PreProcess(object value) | |
{ | |
//not doing anything at the moment | |
return value; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CacheFactory : ICacheFactory | |
{ | |
private readonly IEnumerable<ICache> _caches; | |
private readonly ICacheConfiguration _cacheConfiguration; | |
public CacheFactory(IEnumerable<ICache> caches, ICacheConfiguration cacheConfiguration) | |
{ | |
_caches = caches; | |
_cacheConfiguration = cacheConfiguration; | |
} | |
public ICache GetCache(CacheType type) | |
{ | |
ICache cache = new NullCache(_cacheConfiguration); | |
try | |
{ | |
var match = (from c in _caches | |
where c.CacheType == type | |
select c).LastOrDefault(); | |
if (match != null) | |
{ | |
match.Initialise(); | |
cache = match; | |
} | |
} | |
catch (Exception ex) | |
{ | |
this.Log().WarnFormat("Failed to instantiate cache of type: {0}, using null cache. Exception: {1}", type, ex); | |
cache = new NullCache(_cacheConfiguration); | |
} | |
return cache; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CacheKeyBuilder | |
{ | |
private readonly ICacheConfiguration _cacheConfiguration; | |
public CacheKeyBuilder(ICacheConfiguration cacheConfiguration) | |
{ | |
_cacheConfiguration = cacheConfiguration; | |
} | |
public string GetCacheKeyPrefix(object query) | |
{ | |
var cacheKeyPrefix = query.GetType().Name; | |
this.Log().DebugFormat("CacheKeyBuilder.GetCacheKeyPrefix - returned {0}", cacheKeyPrefix); | |
return cacheKeyPrefix; | |
} | |
public string GetCacheKey(object query) | |
{ | |
var prefix = GetCacheKeyPrefix(query); | |
var key = JsonConvert.SerializeObject(query); | |
var hashedKey = HashCacheKey(key); | |
if (!_cacheConfiguration.HashPrefixInCacheKey) | |
{ | |
hashedKey = string.Format("{0}_{1}", prefix, hashedKey); | |
} | |
this.Log().DebugFormat("CacheKeyBuilder.GetCacheKey - returned {0}", hashedKey); | |
return hashedKey; | |
} | |
private static string HashCacheKey(string cacheKey) | |
{ | |
//hash the string as a GUID: | |
byte[] hashBytes; | |
using (var provider = new MD5CryptoServiceProvider()) | |
{ | |
var inputBytes = Encoding.Default.GetBytes(cacheKey); | |
hashBytes = provider.ComputeHash(inputBytes); | |
} | |
return new Guid(hashBytes).ToString(); | |
} | |
} | |
public interface ICacheFactory | |
{ | |
ICache GetCache(CacheType type); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CacheQueryHandlerDecorator<TQuery, TResult> : IQueryHandler<TQuery, TResult> where TResult : class where TQuery : IQuery<TResult> | |
{ | |
private readonly ICacheConfiguration _cacheConfiguration; | |
private readonly ICacheFactory _cacheFactory; | |
private readonly CacheKeyBuilder _cacheKeyBuilder; | |
private readonly IQueryHandler<TQuery, TResult> _handler; | |
public CacheQueryHandlerDecorator(IQueryHandler<TQuery, TResult> handler, ICacheConfiguration cacheConfiguration, CacheKeyBuilder cacheKeyBuilder, ICacheFactory cacheFactory) | |
{ | |
_cacheConfiguration = cacheConfiguration; | |
_cacheKeyBuilder = cacheKeyBuilder; | |
_cacheFactory = cacheFactory; | |
_handler = handler; | |
} | |
public TResult Handle(TQuery query) | |
{ | |
//if caching is disabled leave | |
if (!_cacheConfiguration.Enabled) | |
{ | |
return _handler.Handle(query); | |
} | |
//get the cache settings from the attribute & config: | |
var cacheAttribute = GetCacheSettings(query); | |
//if no cache attribute set leave | |
if (cacheAttribute == null) | |
{ | |
return _handler.Handle(query); | |
} | |
if (cacheAttribute.Disabled) | |
{ | |
return _handler.Handle(query); | |
} | |
//if there is no cache provider | |
var cache = _cacheFactory.GetCache(cacheAttribute.CacheType); | |
if (cache == null || cache.CacheType == CacheType.Null) | |
{ | |
return _handler.Handle(query); | |
} | |
InstrumentCacheRequest(query); | |
var cacheKey = _cacheKeyBuilder.GetCacheKey(query); | |
var cachedValue = cache.Get<TResult>(cacheKey); | |
if (cachedValue == null) | |
{ | |
InstrumentCacheMiss(query); | |
//call intended method | |
var result = _handler.Handle(query); | |
if (result != null) | |
{ | |
var lifespan = cacheAttribute.Lifespan; | |
if (lifespan.TotalSeconds > 0) | |
{ | |
cache.Set(cacheKey, result, lifespan); | |
} | |
else | |
{ | |
cache.Set(cacheKey, result); | |
} | |
} | |
return result; | |
} | |
InstrumentCacheHit(query); | |
return cachedValue; | |
} | |
private CacheAttribute GetCacheSettings(TQuery query) | |
{ | |
//get the cache attribute & check if overridden in config: | |
var cacheAttribute = query.GetType().GetCustomAttribute<CacheAttribute>(); | |
if (cacheAttribute != null) | |
{ | |
var cacheKeyPrefix = _cacheKeyBuilder.GetCacheKeyPrefix(query); | |
var targetConfig = _cacheConfiguration.Targets[cacheKeyPrefix]; | |
if (targetConfig != null) | |
{ | |
cacheAttribute.Disabled = !targetConfig.Enabled; | |
cacheAttribute.Days = targetConfig.Days; | |
cacheAttribute.Hours = targetConfig.Hours; | |
cacheAttribute.Minutes = targetConfig.Minutes; | |
cacheAttribute.Seconds = targetConfig.Seconds; | |
cacheAttribute.CacheType = targetConfig.CacheType; | |
} | |
else | |
{ | |
//if no target is set try and load the profile from configuration | |
if (cacheAttribute.ProfileName.IsNotNullOrEmpty()) | |
{ | |
var cacheProfile = _cacheConfiguration.Profiles[cacheAttribute.ProfileName]; | |
cacheAttribute.Days = cacheProfile.Days; | |
cacheAttribute.Hours = cacheProfile.Hours; | |
cacheAttribute.Minutes = cacheProfile.Minutes; | |
cacheAttribute.Seconds = cacheProfile.Seconds; | |
cacheAttribute.CacheType = cacheProfile.CacheType; | |
} | |
} | |
//if no duration is set get the default global value from configuration | |
if (cacheAttribute.NoDurationSet()) | |
{ | |
cacheAttribute.Seconds = _cacheConfiguration.DefaultDuration; | |
} | |
//if no cache type is set get the global default from configuration | |
if (cacheAttribute.CacheType == CacheType.Null) | |
{ | |
cacheAttribute.CacheType = _cacheConfiguration.DefaultCacheType; | |
} | |
return cacheAttribute; | |
} | |
return null; | |
} | |
private void InstrumentCacheHit(TQuery query) | |
{ | |
//add instrumentation for cache hit here | |
} | |
private void InstrumentCacheMiss(TQuery query) | |
{ | |
//add instrumentation for cache miss here | |
} | |
private void InstrumentCacheRequest(TQuery query) | |
{ | |
//add instrumentation for cache call here | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[Trait("CacheTests", "CacheQueryHandlerDecorator")] | |
public class CacheQueryHandlerDecoratorTests : TestBase | |
{ | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_call_cache_factory_getting_caheType_defined_on_cache_attribute( | |
[Frozen] Mock<IQueryHandler<FakeCacheQuery, FakeResult>> handlerMock, | |
[Frozen] Mock<ICacheFactory> cacheFactoryMock, | |
FakeCacheQuery query, | |
CacheQueryHandlerDecorator<FakeCacheQuery, FakeResult> sut) | |
{ | |
sut.Handle(query); | |
cacheFactoryMock.Verify(handler => handler.GetCache(CacheType.Http), Times.Once()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_call_get_on_cache_handler_if_cache_factory_returns_non_null_cache( | |
[Frozen] Mock<ICache> cacheMock, | |
[Frozen] Mock<IQueryHandler<FakeCacheQuery, FakeResult>> handlerMock, | |
[Frozen] Mock<ICacheFactory> cacheFactoryMock, | |
FakeCacheQuery query, | |
CacheQueryHandlerDecorator<FakeCacheQuery, FakeResult> sut) | |
{ | |
cacheMock.Setup(cache => cache.CacheType).Returns(() => CacheType.Memory); | |
cacheFactoryMock.Setup(factory => factory.GetCache(It.IsAny<CacheType>())).Returns(() => cacheMock.Object); | |
sut.Handle(query); | |
cacheMock.Verify(handler => handler.Get<FakeResult>(It.IsAny<string>()), Times.Once()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_call_handle_if_cache_factory_returns_null( | |
[Frozen] Mock<IQueryHandler<FakeCacheQuery, FakeResult>> handlerMock, | |
[Frozen] Mock<ICacheFactory> cacheFactoryMock, | |
FakeCacheQuery query, | |
CacheQueryHandlerDecorator<FakeCacheQuery, FakeResult> sut) | |
{ | |
cacheFactoryMock.Setup(factory => factory.GetCache(It.IsAny<CacheType>())).Returns(() => null); | |
sut.Handle(query); | |
handlerMock.Verify(handler => handler.Handle(query), Times.Once()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_call_handler_if_cache_factory_returns_null_cache_type( | |
[Frozen] Mock<ICache> cacheMock, | |
[Frozen] Mock<IQueryHandler<FakeCacheQuery, FakeResult>> handlerMock, | |
[Frozen] Mock<ICacheFactory> cacheFactoryMock, | |
FakeCacheQuery query, | |
CacheQueryHandlerDecorator<FakeCacheQuery, FakeResult> sut) | |
{ | |
cacheMock.Setup(cache => cache.CacheType).Returns(() => CacheType.Null); | |
cacheFactoryMock.Setup(factory => factory.GetCache(It.IsAny<CacheType>())).Returns(() => cacheMock.Object); | |
sut.Handle(query); | |
handlerMock.Verify(handler => handler.Handle(query), Times.Once()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_call_handler_if_cache_is_disabled_on_query_cache_attribute( | |
[Frozen] Mock<IQueryHandler<FakeCacheDisabledQuery, FakeResult>> handlerMock, | |
FakeCacheDisabledQuery query, | |
CacheQueryHandlerDecorator<FakeCacheDisabledQuery, FakeResult> sut) | |
{ | |
sut.Handle(query); | |
handlerMock.Verify(handler => handler.Handle(query), Times.Once()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_call_handler_if_cache_is_globally_disabled( | |
[Frozen] Mock<IQueryHandler<FakeQuery, FakeResult>> handlerMock, | |
[Frozen] Mock<ICacheConfiguration> cacheConfigMock, | |
FakeQuery query, | |
CacheQueryHandlerDecorator<FakeQuery, FakeResult> sut) | |
{ | |
cacheConfigMock.Setup(configuration => configuration.Enabled).Returns(false); | |
sut.Handle(query); | |
handlerMock.Verify(handler => handler.Handle(query), Times.Once()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_call_handler_if_cache_returns_null( | |
[Frozen] Mock<ICache> cacheMock, | |
[Frozen] Mock<IQueryHandler<FakeCacheQuery, FakeResult>> handlerMock, | |
FakeCacheQuery query, | |
CacheQueryHandlerDecorator<FakeCacheQuery, FakeResult> sut) | |
{ | |
cacheMock.Setup(cache => cache.Get<FakeResult>(It.IsAny<string>())).Returns(() => null); | |
sut.Handle(query); | |
handlerMock.Verify(handler => handler.Handle(query), Times.Once()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_call_handler_if_result_is_not_decorated_with_cache_attribute( | |
[Frozen] Mock<IQueryHandler<FakeQuery, FakeResult>> handlerMock, | |
[Frozen] Mock<ICacheConfiguration> cacheConfigMock, | |
FakeQuery query, | |
CacheQueryHandlerDecorator<FakeQuery, FakeResult> sut) | |
{ | |
sut.Handle(query); | |
handlerMock.Verify(handler => handler.Handle(query), Times.Once()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_not_call_cache_if_cache_factory_returns_null_cache_type( | |
[Frozen] Mock<ICache> cacheMock, | |
[Frozen] Mock<ICacheFactory> cacheFactoryMock, | |
FakeCacheQuery query, | |
CacheQueryHandlerDecorator<FakeCacheQuery, FakeResult> sut) | |
{ | |
cacheMock.Setup(cache => cache.CacheType).Returns(() => CacheType.Null); | |
cacheFactoryMock.Setup(factory => factory.GetCache(It.IsAny<CacheType>())).Returns(() => cacheMock.Object); | |
sut.Handle(query); | |
cacheMock.Verify(handler => handler.Get<FakeResult>(It.IsAny<string>()), Times.Never()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_not_call_cache_if_cache_is_diabled_on_query_cache_attribute( | |
[Frozen] Mock<ICacheFactory> cacheFactoryMock, | |
FakeCacheDisabledQuery query, | |
CacheQueryHandlerDecorator<FakeCacheDisabledQuery, FakeResult> sut) | |
{ | |
sut.Handle(query); | |
cacheFactoryMock.Verify(handler => handler.GetCache(It.IsAny<CacheType>()), Times.Never()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_not_call_cache_if_cache_is_globally_disabled( | |
[Frozen] Mock<IQueryHandler<FakeQuery, FakeResult>> handlerMock, | |
[Frozen] Mock<ICacheConfiguration> cacheConfigMock, | |
[Frozen] Mock<ICacheFactory> cacheFactoryMock, | |
FakeQuery query, | |
CacheQueryHandlerDecorator<FakeQuery, FakeResult> sut) | |
{ | |
cacheConfigMock.Setup(configuration => configuration.Enabled).Returns(false); | |
sut.Handle(query); | |
cacheFactoryMock.Verify(handler => handler.GetCache(It.IsAny<CacheType>()), Times.Never()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_not_call_handler_if_cache_returns_non_null_object( | |
[Frozen] Mock<ICache> cacheMock, | |
[Frozen] Mock<ICacheFactory> cacheFactoryMock, | |
[Frozen] Mock<IQueryHandler<FakeCacheQuery, FakeResult>> handlerMock, | |
FakeCacheQuery query, | |
CacheQueryHandlerDecorator<FakeCacheQuery, FakeResult> sut) | |
{ | |
cacheMock.Setup(cache => cache.CacheType).Returns(() => CacheType.Memory); | |
cacheMock.Setup(cache => cache.Get<FakeResult>(It.IsAny<string>())).Returns(() => new FakeResult()); | |
cacheFactoryMock.Setup(factory => factory.GetCache(It.IsAny<CacheType>())).Returns(() => cacheMock.Object); | |
sut.Handle(query); | |
handlerMock.Verify(handler => handler.Handle(query), Times.Never()); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_return_cached_object( | |
[Frozen] Mock<ICache> cacheMock, | |
[Frozen] Mock<ICacheFactory> cacheFactoryMock, | |
[Frozen] Mock<IQueryHandler<FakeCacheQuery, FakeResult>> handlerMock, | |
FakeCacheQuery query, | |
CacheQueryHandlerDecorator<FakeCacheQuery, FakeResult> sut) | |
{ | |
var expected = new FakeResult(); | |
cacheMock.Setup(cache => cache.CacheType).Returns(() => CacheType.Memory); | |
cacheMock.Setup(cache => cache.Get<FakeResult>(It.IsAny<string>())).Returns(() => expected); | |
cacheFactoryMock.Setup(factory => factory.GetCache(It.IsAny<CacheType>())).Returns(() => cacheMock.Object); | |
var actual = sut.Handle(query); | |
handlerMock.Verify(handler => handler.Handle(query), Times.Never()); | |
Assert.Equal(expected, actual); | |
} | |
[Theory, AutoMoqDataCacheConfig] | |
public void CacheDecorator_should_cache_handler_result_if_not_null( | |
[Frozen] Mock<ICache> cacheMock, | |
[Frozen] Mock<ICacheFactory> cacheFactoryMock, | |
[Frozen] Mock<IQueryHandler<FakeCacheQuery, FakeResult>> handlerMock, | |
FakeCacheQuery query, | |
CacheQueryHandlerDecorator<FakeCacheQuery, FakeResult> sut) | |
{ | |
var expected = new FakeResult(); | |
cacheMock.Setup(cache => cache.CacheType).Returns(() => CacheType.Memory); | |
cacheMock.Setup(cache => cache.Get<FakeResult>(It.IsAny<string>())).Returns(() => null); | |
cacheFactoryMock.Setup(factory => factory.GetCache(It.IsAny<CacheType>())).Returns(() => cacheMock.Object); | |
handlerMock.Setup(handler => handler.Handle(query)).Returns(expected); | |
sut.Handle(query); | |
cacheMock.Verify(cache => cache.Set(It.IsAny<string>(), expected, It.IsAny<TimeSpan>()), Times.Once()); | |
} | |
} | |
public class AutoMoqDataCacheConfigAttribute : AutoDataAttribute | |
{ | |
public AutoMoqDataCacheConfigAttribute() | |
: base(new Fixture() | |
.Customize(new MoqCacheConfigurationCustomization())) | |
{ | |
} | |
} | |
public class MoqCacheConfigurationCustomization : CompositeCustomization | |
{ | |
public MoqCacheConfigurationCustomization() | |
: base(new CacheConfigurationCustomization(), | |
new AutoMoqCustomization()) | |
{ | |
} | |
private class CacheConfigurationCustomization : ICustomization | |
{ | |
public void Customize(IFixture fixture) | |
{ | |
fixture.Customizations.Add(new CacheConfigurationSpecimenBuilder()); | |
} | |
} | |
} | |
public class CacheConfigurationSpecimenBuilder : ISpecimenBuilder | |
{ | |
public object Create(object request, ISpecimenContext context) | |
{ | |
var pi = request as ParameterInfo; | |
if (pi == null) | |
{ | |
return new NoSpecimen(request); | |
} | |
if (pi.ParameterType != typeof(ICacheConfiguration)) | |
{ | |
return new NoSpecimen(request); | |
} | |
var mockCacheConfig = new Mock<ICacheConfiguration>(); | |
mockCacheConfig.Setup(configuration => configuration.Enabled).Returns(true); | |
mockCacheConfig.Setup(configuration => configuration.Targets).Returns(new CacheTargetCollection()); | |
mockCacheConfig.Setup(configuration => configuration.Profiles).Returns(new CacheProfileCollection()); | |
mockCacheConfig.Setup(configuration => configuration.DefaultCacheType).Returns(CacheType.Null); | |
mockCacheConfig.Setup(configuration => configuration.DefaultCacheName).Returns("Think.Cache"); | |
mockCacheConfig.Setup(configuration => configuration.DefaultDuration).Returns(60); | |
return mockCacheConfig.Object; | |
} | |
} | |
[Cache(Disabled = true)] | |
public class FakeCacheDisabledQuery : IQuery<FakeResult> | |
{ | |
} | |
[Cache(CacheType = CacheType.Http)] | |
public class FakeCacheQuery : IQuery<FakeResult> | |
{ | |
} | |
public class FakeQuery : IQuery<FakeResult> | |
{ | |
} | |
public class FakeResult | |
{ | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public enum CacheType | |
{ | |
/// <summary> | |
/// No cache type set | |
/// </summary> | |
Null = 0, | |
Memory, | |
Http, | |
//AppFabric, | |
//Memcached, | |
//AzureTableStorage, | |
//Disk | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface ICache | |
{ | |
/// <summary> | |
/// Returns the type of the cache | |
/// </summary> | |
CacheType CacheType { get; } | |
/// <summary> | |
/// Performs initialisation tasks required for the cache implementation | |
/// </summary> | |
void Initialise(); | |
/// <summary> | |
/// Insert or update a cache value | |
/// </summary> | |
/// <param name="key"></param> | |
/// <param name="value"></param> | |
void Set(string key, object value); | |
/// <summary> | |
/// Insert or update a cache value with an expiry date | |
/// </summary> | |
/// <param name="key"></param> | |
/// <param name="value"></param> | |
/// <param name="expiresAt"></param> | |
void Set(string key, object value, DateTime expiresAt); | |
/// <summary> | |
/// Insert or update a cache value with a fixed lifetime | |
/// </summary> | |
/// <param name="key"></param> | |
/// <param name="value"></param> | |
/// <param name="validFor"></param> | |
void Set(string key, object value, TimeSpan validFor); | |
/// <summary> | |
/// Retrieve a value from cache | |
/// </summary> | |
/// <param name="key"></param> | |
/// <returns>Cached value or null</returns> | |
object Get(Type type, string key); | |
/// <summary> | |
/// Retrieve a typed value from cache | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="key"></param> | |
/// <returns></returns> | |
T Get<T>(string key); | |
/// <summary> | |
/// Removes the value for the given key from the cache | |
/// </summary> | |
/// <param name="key"></param> | |
void Remove(string key); | |
/// <summary> | |
/// Returns whether the cache contains a value for the given key | |
/// </summary> | |
/// <param name="key"></param> | |
/// <returns></returns> | |
bool Exists(string key); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// <see cref="ICache"/> implementation which does nothing | |
/// </summary> | |
/// <remarks> | |
/// Used when real caches are unavailable or disabled | |
/// </remarks> | |
public class NullCache : CacheBase | |
{ | |
public NullCache(ICacheConfiguration cacheConfiguration) | |
: base(cacheConfiguration) | |
{ | |
} | |
public override CacheType CacheType | |
{ | |
get { return CacheType.Null; } | |
} | |
protected override void InitialiseInternal() | |
{ | |
} | |
protected override void SetInternal(string key, object value) | |
{ | |
} | |
protected override void SetInternal(string key, object value, DateTime expiresAt) | |
{ | |
} | |
protected override void SetInternal(string key, object value, TimeSpan validFor) | |
{ | |
} | |
protected override object GetInternal(string key) | |
{ | |
return null; | |
} | |
protected override void RemoveInternal(string key) | |
{ | |
} | |
protected override bool ExistsInternal(string key) | |
{ | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment