U4-4931 - fixing & refactoring

Conflicts:
	src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs
	src/Umbraco.Core/Cache/DictionaryCacheWrapper.cs
	src/Umbraco.Core/Cache/DictionaryItemWrapper.cs
	src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs
	src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs
This commit is contained in:
Stephan
2014-05-27 12:16:41 +02:00
parent 72f53e4645
commit fcd7e9517d
7 changed files with 238 additions and 441 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
@@ -8,232 +9,169 @@ namespace Umbraco.Core.Cache
{
internal abstract class DictionaryCacheProviderBase : ICacheProvider
{
protected readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
protected abstract DictionaryCacheWrapper DictionaryCache { get; }
/// <summary>
/// Clears everything in umbraco's runtime cache
/// </summary>
/// <remarks>
/// Does not clear other stuff the user has put in httpruntime.cache!
/// </remarks>
public virtual void ClearAllCache()
{
using (new WriteLock(Locker))
{
var keysToRemove = DictionaryCache.Cast<object>()
.Select(item => new DictionaryItemWrapper(item))
.Where(c => c.Key is string && ((string)c.Key).StartsWith(CacheItemPrefix) && DictionaryCache[c.Key.ToString()] != null)
.Select(c => c.Key)
.ToList();
foreach (var k in keysToRemove)
{
DictionaryCache.Remove(k);
}
}
}
/// <summary>
/// Clears the item in umbraco's runtime cache with the given key
/// </summary>
/// <param name="key">Key</param>
public virtual void ClearCacheItem(string key)
{
using (new WriteLock(Locker))
{
var cacheKey = GetCacheKey(key);
if (DictionaryCache[cacheKey] == null) return;
DictionaryCache.Remove(cacheKey);
}
}
/// <summary>
/// Clears all objects in the System.Web.Cache with the System.Type name as the
/// input parameter. (using [object].GetType())
/// </summary>
/// <param name="typeName">The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument"</param>
public virtual void ClearCacheObjectTypes(string typeName)
{
using (new WriteLock(Locker))
{
var keysToRemove = DictionaryCache
.Cast<object>()
.Select(item => new DictionaryItemWrapper(item))
.Where(c =>
{
var k = c.Key.ToString();
var v = DictionaryCache[k];
return v != null && v.GetType().ToString().InvariantEquals(typeName);
})
.Select(c => c.Key)
.ToList();
foreach (var k in keysToRemove)
DictionaryCache.Remove(k);
}
}
public virtual void ClearCacheObjectTypes<T>()
{
using (new WriteLock(Locker))
{
var typeOfT = typeof(T);
var keysToRemove = DictionaryCache
.Cast<object>()
.Select(item => new DictionaryItemWrapper(item))
.Where(c =>
{
var k = c.Key.ToString();
var v = DictionaryCache[k];
return v != null && v.GetType() == typeOfT;
})
.Select(c => c.Key)
.ToList();
foreach (var k in keysToRemove)
DictionaryCache.Remove(k);
}
}
public virtual void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
{
using (new WriteLock(Locker))
{
var typeOfT = typeof(T);
var keysToRemove = DictionaryCache
.Cast<object>()
.Select(item => new DictionaryItemWrapper(item))
.Where(c =>
{
var k = c.Key.ToString();
var v = DictionaryCache[k];
return v != null && v.GetType() == typeOfT && predicate(k, (T)v);
})
.Select(c => c.Key)
.ToList();
foreach (var k in keysToRemove)
DictionaryCache.Remove(k);
}
}
/// <summary>
/// Clears all cache items that starts with the key passed.
/// </summary>
/// <param name="keyStartsWith">The start of the key</param>
public virtual void ClearCacheByKeySearch(string keyStartsWith)
{
using (new WriteLock(Locker))
{
var keysToRemove = DictionaryCache.Cast<object>()
.Select(item => new DictionaryItemWrapper(item))
.Where(
c =>
c.Key is string &&
((string) c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith)))
.Select(c => c.Key)
.ToList();
foreach (var k in keysToRemove)
{
DictionaryCache.Remove(k);
}
}
}
/// <summary>
/// Clears all cache items that have a key that matches the regular expression
/// </summary>
/// <param name="regexString"></param>
public virtual void ClearCacheByKeyExpression(string regexString)
{
using (new WriteLock(Locker))
{
var keysToRemove = new List<object>();
foreach (var item in DictionaryCache)
{
var c = new DictionaryItemWrapper(item);
var s = c.Key as string;
if (s != null)
{
var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix));
if (Regex.IsMatch(withoutPrefix, regexString))
{
keysToRemove.Add(c.Key);
}
}
}
foreach (var k in keysToRemove)
{
DictionaryCache.Remove(k);
}
}
}
public virtual IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
{
using (new ReadLock(Locker))
{
return (from object item in DictionaryCache
select new DictionaryItemWrapper(item)
into c
where
c.Key is string &&
((string) c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith))
select c.Value).ToList();
}
}
public virtual IEnumerable<object> GetCacheItemsByKeyExpression(string regexString)
{
using (new ReadLock(Locker))
{
var found = new List<object>();
foreach (var item in DictionaryCache)
{
var c = new DictionaryItemWrapper(item);
var s = c.Key as string;
if (s != null)
{
var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix));
if (Regex.IsMatch(withoutPrefix, regexString))
{
found.Add(c.Value);
}
}
}
return found;
}
}
/// <summary>
/// Returns a cache item by key, does not update the cache if it isn't there.
/// </summary>
/// <param name="cacheKey"></param>
/// <returns></returns>
public virtual object GetCacheItem(string cacheKey)
{
using (new ReadLock(Locker))
{
var result = DictionaryCache.Get(GetCacheKey(cacheKey));
return result;
}
}
public abstract object GetCacheItem(string cacheKey, Func<object> getCacheItem);
/// <summary>
/// We prefix all cache keys with this so that we know which ones this class has created when
/// using the HttpRuntime cache so that when we clear it we don't clear other entries we didn't create.
/// </summary>
// prefix cache keys so we know which one are ours
protected const string CacheItemPrefix = "umbrtmche";
protected readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
// manupulate the underlying cache entries
// these *must* be called from within the appropriate locks
// and use the full prefixed cache keys
protected abstract IEnumerable<DictionaryEntry> GetDictionaryEntries();
protected abstract void RemoveEntry(string key);
protected abstract object GetEntry(string key);
protected string GetCacheKey(string key)
{
return string.Format("{0}-{1}", CacheItemPrefix, key);
}
#region Clear
public virtual void ClearAllCache()
{
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.ToArray())
RemoveEntry((string) entry.Key);
}
}
public virtual void ClearCacheItem(string key)
{
using (new WriteLock(Locker))
{
var cacheKey = GetCacheKey(key);
RemoveEntry(cacheKey);
}
}
public virtual void ClearCacheObjectTypes(string typeName)
{
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.Where(x =>
{
// entry.Value is Lazy<object> and not null, its value may be null
// remove null values as well, does not hurt
var value = ((Lazy<object>) x.Value).Value;
return value == null || value.GetType().ToString().InvariantEquals(typeName);
})
.ToArray())
RemoveEntry((string) entry.Key);
}
}
public virtual void ClearCacheObjectTypes<T>()
{
var typeOfT = typeof(T);
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.Where(x =>
{
// entry.Value is Lazy<object> and not null, its value may be null
// remove null values as well, does not hurt
// compare on exact type, don't use "is"
var value = ((Lazy<object>)x.Value).Value;
return value == null || value.GetType() == typeOfT;
})
.ToArray())
RemoveEntry((string) entry.Key);
}
}
public virtual void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
{
var typeOfT = typeof(T);
var plen = CacheItemPrefix.Length + 1;
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.Where(x =>
{
// entry.Value is Lazy<object> and not null, its value may be null
// remove null values as well, does not hurt
// compare on exact type, don't use "is"
var value = ((Lazy<object>)x.Value).Value;
if (value == null) return true;
return value.GetType() == typeOfT
// run predicate on the 'public key' part only, ie without prefix
&& predicate(((string)x.Key).Substring(plen), (T)value);
}))
RemoveEntry((string) entry.Key);
}
}
public virtual void ClearCacheByKeySearch(string keyStartsWith)
{
var plen = CacheItemPrefix.Length + 1;
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
.ToArray())
RemoveEntry((string) entry.Key);
}
}
public virtual void ClearCacheByKeyExpression(string regexString)
{
var plen = CacheItemPrefix.Length + 1;
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
.ToArray())
RemoveEntry((string) entry.Key);
}
}
#endregion
#region Get
public virtual IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
{
var plen = CacheItemPrefix.Length + 1;
using (new ReadLock(Locker))
{
return GetDictionaryEntries()
.Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
.Select(x => ((Lazy<object>)x.Value).Value)
.Where(x => x != null) // backward compat, don't store null values in the cache
.ToList();
}
}
public virtual IEnumerable<object> GetCacheItemsByKeyExpression(string regexString)
{
const string prefix = CacheItemPrefix + "-";
var plen = prefix.Length;
using (new ReadLock(Locker))
{
return GetDictionaryEntries()
.Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
.Select(x => ((Lazy<object>)x.Value).Value)
.Where(x => x != null) // backward compat, don't store null values in the cache
.ToList();
}
}
public virtual object GetCacheItem(string cacheKey)
{
cacheKey = GetCacheKey(cacheKey);
using (new ReadLock(Locker))
{
var result = GetEntry(cacheKey) as Lazy<object>; // null if key not found
return result == null ? null : result.Value;
}
}
public abstract object GetCacheItem(string cacheKey, Func<object> getCacheItem);
#endregion
}
}

View File

@@ -1,45 +0,0 @@
using System;
using System.Collections;
namespace Umbraco.Core.Cache
{
internal class DictionaryCacheWrapper : IEnumerable
{
private readonly IEnumerable _inner;
private readonly Func<object, object> _get;
private readonly Action<object> _remove;
public DictionaryCacheWrapper(
IEnumerable inner,
Func<object, object> get,
Action<object> remove)
{
_inner = inner;
_get = get;
_remove = remove;
}
public object this[object key]
{
get
{
return Get(key);
}
}
public object Get(object key)
{
return _get(key);
}
public void Remove(object key)
{
_remove(key);
}
public IEnumerator GetEnumerator()
{
return _inner.GetEnumerator();
}
}
}

View File

@@ -1,14 +0,0 @@
namespace Umbraco.Core.Cache
{
internal class DictionaryItemWrapper
{
public DictionaryItemWrapper(dynamic item)
{
Key = item.Key;
Value = item.Value;
}
public object Key { get; private set; }
public object Value { get; private set; }
}
}

View File

@@ -2,7 +2,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
namespace Umbraco.Core.Cache
@@ -16,7 +15,9 @@ namespace Umbraco.Core.Cache
public HttpRequestCacheProvider(HttpContext context)
{
_context = () => new HttpContextWrapper(context);
// create wrapper only once!
var wrapper = new HttpContextWrapper(context);
_context = () => wrapper;
}
public HttpRequestCacheProvider(Func<HttpContextBase> context)
@@ -24,24 +25,50 @@ namespace Umbraco.Core.Cache
_context = context;
}
protected override DictionaryCacheWrapper DictionaryCache
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
{
get
{
var ctx = _context();
return new DictionaryCacheWrapper(
ctx.Items,
o => ctx.Items[o],
o => ctx.Items.Remove(o));
}
const string prefix = CacheItemPrefix + "-";
return _context().Items.Cast<DictionaryEntry>()
.Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix));
}
protected override void RemoveEntry(string key)
{
_context().Items.Remove(key);
}
protected override object GetEntry(string key)
{
return _context().Items[key];
}
#region Get
public override object GetCacheItem(string cacheKey, Func<object> getCacheItem)
{
var ctx = _context();
var ck = GetCacheKey(cacheKey);
return ctx.Items[ck] ?? (ctx.Items[ck] = getCacheItem());
cacheKey = GetCacheKey(cacheKey);
Lazy<object> result;
using (var lck = new UpgradeableReadLock(Locker))
{
result = _context().Items[cacheKey] as Lazy<object>; // null if key not found
if (result == null || (result.IsValueCreated && result.Value == null))
{
lck.UpgradeToWriteLock();
result = new Lazy<object>(getCacheItem);
_context().Items[cacheKey] = result;
}
}
return result.Value;
}
#endregion
#region Insert
#endregion
}
}

View File

@@ -2,11 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using System.Web.Caching;
using Umbraco.Core.Logging;
using CacheItemPriority = System.Web.Caching.CacheItemPriority;
namespace Umbraco.Core.Cache
@@ -17,148 +13,31 @@ namespace Umbraco.Core.Cache
internal class HttpRuntimeCacheProvider : DictionaryCacheProviderBase, IRuntimeCacheProvider
{
private readonly System.Web.Caching.Cache _cache;
private readonly DictionaryCacheWrapper _wrapper;
public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache)
{
_cache = cache;
_wrapper = new DictionaryCacheWrapper(_cache, s => _cache.Get(s.ToString()), o => _cache.Remove(o.ToString()));
}
protected override DictionaryCacheWrapper DictionaryCache
{
get { return _wrapper; }
}
public override void ClearAllCache()
{
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries())
_cache.Remove((string)entry.Key);
}
}
public override void ClearCacheItem(string key)
{
using (new WriteLock(Locker))
{
var cacheKey = GetCacheKey(key);
_cache.Remove(cacheKey);
}
}
public override void ClearCacheObjectTypes(string typeName)
{
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.Where(x =>
{
// entry.Value is Lazy<object> and not null, its value may be null
var value = ((Lazy<object>) x.Value).Value;
return value == null || value.GetType().ToString().InvariantEquals(typeName); // remove null values as well
}))
_cache.Remove((string)entry.Key);
}
}
public override void ClearCacheObjectTypes<T>()
{
// note: compare on exact type, don't use "is"
var typeOfT = typeof(T);
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.Where(x =>
{
// entry.Value is Lazy<object> and not null, its value may be null
var value = ((Lazy<object>)x.Value).Value;
return value == null || value.GetType() == typeOfT; // remove null values as well
}))
_cache.Remove((string)entry.Key);
}
}
public override void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
{
// note: compare on exact type, don't use "is"
var typeOfT = typeof(T);
var plen = CacheItemPrefix.Length + 1;
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.Where(x =>
{
// entry.Value is Lazy<object> and not null, its value may be null
var value = ((Lazy<object>)x.Value).Value;
if (value == null) return true; // remove null values as well
return value.GetType() == typeOfT
// run predicate on the 'public key' part only, ie without prefix
&& predicate(((string) x.Key).Substring(plen), (T) value);
}))
_cache.Remove((string)entry.Key);
}
}
public override void ClearCacheByKeySearch(string keyStartsWith)
{
var plen = CacheItemPrefix.Length + 1;
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith)))
_cache.Remove((string)entry.Key);
}
}
public override void ClearCacheByKeyExpression(string regexString)
{
var plen = CacheItemPrefix.Length + 1;
using (new WriteLock(Locker))
{
foreach (var entry in GetDictionaryEntries()
.Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString)))
_cache.Remove((string)entry.Key);
}
}
private IEnumerable<DictionaryEntry> GetDictionaryEntries()
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
{
const string prefix = CacheItemPrefix + "-";
return _cache.Cast<DictionaryEntry>()
.Where(x => x.Key is string && ((string) x.Key).StartsWith(prefix));
}
public override IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
protected override void RemoveEntry(string key)
{
var plen = CacheItemPrefix.Length + 1;
using (new ReadLock(Locker))
{
return GetDictionaryEntries()
.Where(x => ((string) x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
.Select(x => ((Lazy<object>)x.Value).Value)
.Where(x => x != null) // backward compat, don't store null values in the cache
.ToList();
}
_cache.Remove(key);
}
public override IEnumerable<object> GetCacheItemsByKeyExpression(string regexString)
protected override object GetEntry(string key)
{
const string prefix = CacheItemPrefix + "-";
var plen = prefix.Length;
using (new ReadLock(Locker))
{
return GetDictionaryEntries()
.Where(x => Regex.IsMatch(((string) x.Key).Substring(plen), regexString))
.Select(x => ((Lazy<object>)x.Value).Value)
.Where(x => x != null) // backward compat, don't store null values in the cache
.ToList();
}
return _cache.Get(key);
}
#region Get
/// <summary>
/// Gets (and adds if necessary) an item from the cache with all of the default parameters
/// </summary>
@@ -230,6 +109,10 @@ namespace Umbraco.Core.Cache
return GetCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency);
}
#endregion
#region Insert
/// <summary>
/// This overload is here for legacy purposes
/// </summary>
@@ -266,5 +149,7 @@ namespace Umbraco.Core.Cache
}
InsertCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency);
}
#endregion
}
}

View File

@@ -24,6 +24,8 @@ namespace Umbraco.Core.Cache
MemoryCache = new MemoryCache("in-memory");
}
#region Clear
public virtual void ClearAllCache()
{
using (new WriteLock(_locker))
@@ -50,8 +52,9 @@ namespace Umbraco.Core.Cache
.Where(x =>
{
// x.Value is Lazy<object> and not null, its value may be null
// remove null values as well, does not hurt
var value = ((Lazy<object>) x.Value).Value;
return value == null || value.GetType().ToString().InvariantEquals(typeName); // remove null values as well
return value == null || value.GetType().ToString().InvariantEquals(typeName);
})
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
@@ -68,8 +71,9 @@ namespace Umbraco.Core.Cache
.Where(x =>
{
// x.Value is Lazy<object> and not null, its value may be null
var value = ((Lazy<object>) x.Value).Value;
return value == null || value.GetType() == typeOfT; // remove null values as well
// remove null values as well, does not hurt
var value = ((Lazy<object>)x.Value).Value;
return value == null || value.GetType() == typeOfT;
})
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
@@ -86,8 +90,9 @@ namespace Umbraco.Core.Cache
.Where(x =>
{
// x.Value is Lazy<object> and not null, its value may be null
var value = ((Lazy<object>) x.Value).Value;
if (value == null) return true; // remove null values as well
// remove null values as well, does not hurt
var value = ((Lazy<object>)x.Value).Value;
if (value == null) return true;
return value.GetType() == typeOfT
&& predicate(x.Key, (T) value);
})
@@ -121,7 +126,11 @@ namespace Umbraco.Core.Cache
}
}
public virtual IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
#endregion
#region Get
public IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
{
using (new ReadLock(_locker))
{
@@ -145,7 +154,7 @@ namespace Umbraco.Core.Cache
}
}
public virtual object GetCacheItem(string cacheKey)
public object GetCacheItem(string cacheKey)
{
using (new ReadLock(_locker))
{
@@ -154,19 +163,12 @@ namespace Umbraco.Core.Cache
}
}
public virtual object GetCacheItem(string cacheKey, Func<object> getCacheItem)
public object GetCacheItem(string cacheKey, Func<object> getCacheItem)
{
return GetCacheItem(cacheKey, getCacheItem, null);
}
public object GetCacheItem(
string cacheKey,
Func<object> getCacheItem,
TimeSpan? timeout,
bool isSliding = false,
CacheItemPriority priority = CacheItemPriority.Normal,
CacheItemRemovedCallback removedCallback = null,
string[] dependentFiles = null)
public object GetCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal,CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
// see notes in HttpRuntimeCacheProvider
@@ -188,6 +190,10 @@ namespace Umbraco.Core.Cache
return result.Value;
}
#endregion
#region Insert
public void InsertCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
// NOTE - here also we must insert a Lazy<object> but we can evaluate it right now
@@ -201,6 +207,8 @@ namespace Umbraco.Core.Cache
MemoryCache.Set(cacheKey, result, policy);
}
#endregion
private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
var absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : (timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value));

View File

@@ -146,8 +146,6 @@
<Compile Include="Cache\CacheRefresherBase.cs" />
<Compile Include="Cache\CacheRefresherEventArgs.cs" />
<Compile Include="Cache\DictionaryCacheProviderBase.cs" />
<Compile Include="Cache\DictionaryCacheWrapper.cs" />
<Compile Include="Cache\DictionaryItemWrapper.cs" />
<Compile Include="Cache\HttpRequestCacheProvider.cs" />
<Compile Include="Cache\ObjectCacheRuntimeCacheProvider.cs" />
<Compile Include="Cache\IRuntimeCacheProvider.cs" />