2013-08-12 15:06:12 +02:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Web;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Core.Cache
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// A cache provider that caches items in the HttpContext.Items
|
|
|
|
|
|
/// </summary>
|
2016-07-19 15:07:38 +02:00
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// If the Items collection is null, then this provider has no effect
|
|
|
|
|
|
/// </remarks>
|
2013-12-16 16:21:38 +11:00
|
|
|
|
internal class HttpRequestCacheProvider : DictionaryCacheProviderBase
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2014-06-28 12:09:54 +02:00
|
|
|
|
// context provider
|
|
|
|
|
|
// the idea is that there is only one, application-wide HttpRequestCacheProvider instance,
|
|
|
|
|
|
// that is initialized with a method that returns the "current" context.
|
|
|
|
|
|
// NOTE
|
|
|
|
|
|
// but then it is initialized with () => new HttpContextWrapper(HttpContent.Current)
|
|
|
|
|
|
// which is higly inefficient because it creates a new wrapper each time we refer to _context()
|
|
|
|
|
|
// so replace it with _context1 and _context2 below + a way to get context.Items.
|
|
|
|
|
|
//private readonly Func<HttpContextBase> _context;
|
2013-08-12 15:06:12 +02:00
|
|
|
|
|
2014-06-28 12:09:54 +02:00
|
|
|
|
// NOTE
|
|
|
|
|
|
// and then in almost 100% cases _context2 will be () => HttpContext.Current
|
|
|
|
|
|
// so why not bring that logic in here and fallback on to HttpContext.Current when
|
|
|
|
|
|
// _context1 is null?
|
|
|
|
|
|
//private readonly HttpContextBase _context1;
|
|
|
|
|
|
//private readonly Func<HttpContext> _context2;
|
|
|
|
|
|
private readonly HttpContextBase _context;
|
|
|
|
|
|
|
|
|
|
|
|
private IDictionary ContextItems
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2014-06-28 12:09:54 +02:00
|
|
|
|
//get { return _context1 != null ? _context1.Items : _context2().Items; }
|
|
|
|
|
|
get { return _context != null ? _context.Items : HttpContext.Current.Items; }
|
2013-08-12 15:06:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-07-19 15:37:55 +02:00
|
|
|
|
private bool HasContextItems
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return (_context != null && _context.Items != null) || HttpContext.Current != null; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-06-28 12:09:54 +02:00
|
|
|
|
// for unit tests
|
|
|
|
|
|
public HttpRequestCacheProvider(HttpContextBase context)
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
|
|
|
|
|
_context = context;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-06-28 12:09:54 +02:00
|
|
|
|
// main constructor
|
|
|
|
|
|
// will use HttpContext.Current
|
|
|
|
|
|
public HttpRequestCacheProvider(/*Func<HttpContext> context*/)
|
|
|
|
|
|
{
|
|
|
|
|
|
//_context2 = context;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-05-27 12:16:41 +02:00
|
|
|
|
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
|
2013-08-12 15:06:12 +02:00
|
|
|
|
{
|
2014-05-27 12:16:41 +02:00
|
|
|
|
const string prefix = CacheItemPrefix + "-";
|
2016-07-19 15:07:38 +02:00
|
|
|
|
|
2016-07-19 15:37:55 +02:00
|
|
|
|
if (HasContextItems == false) return Enumerable.Empty<DictionaryEntry>();
|
2016-07-19 15:07:38 +02:00
|
|
|
|
|
2014-06-28 12:09:54 +02:00
|
|
|
|
return ContextItems.Cast<DictionaryEntry>()
|
2014-05-27 12:16:41 +02:00
|
|
|
|
.Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void RemoveEntry(string key)
|
|
|
|
|
|
{
|
2016-07-19 15:37:55 +02:00
|
|
|
|
if (HasContextItems == false) return;
|
2016-07-19 15:07:38 +02:00
|
|
|
|
|
2014-06-28 12:09:54 +02:00
|
|
|
|
ContextItems.Remove(key);
|
2014-05-27 12:16:41 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override object GetEntry(string key)
|
|
|
|
|
|
{
|
2016-07-19 15:37:55 +02:00
|
|
|
|
return HasContextItems ? ContextItems[key] : null;
|
2013-08-12 15:06:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-06-28 12:09:54 +02:00
|
|
|
|
#region Lock
|
|
|
|
|
|
|
2018-05-03 14:50:21 +02:00
|
|
|
|
private bool _entered;
|
|
|
|
|
|
|
|
|
|
|
|
protected override void EnterReadLock() => EnterWriteLock();
|
|
|
|
|
|
|
|
|
|
|
|
protected override void EnterWriteLock()
|
2014-06-28 12:09:54 +02:00
|
|
|
|
{
|
2018-05-03 14:50:21 +02:00
|
|
|
|
if (HasContextItems)
|
|
|
|
|
|
{
|
|
|
|
|
|
System.Threading.Monitor.Enter(ContextItems.SyncRoot, ref _entered);
|
|
|
|
|
|
}
|
2014-06-28 12:09:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-05-03 14:50:21 +02:00
|
|
|
|
protected override void ExitReadLock() => ExitWriteLock();
|
2014-06-28 12:09:54 +02:00
|
|
|
|
|
2018-05-03 14:50:21 +02:00
|
|
|
|
protected override void ExitWriteLock()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_entered)
|
2014-06-28 12:09:54 +02:00
|
|
|
|
{
|
2018-05-03 14:50:21 +02:00
|
|
|
|
_entered = false;
|
|
|
|
|
|
System.Threading.Monitor.Exit(ContextItems.SyncRoot);
|
2014-06-28 12:09:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2014-05-27 12:16:41 +02:00
|
|
|
|
#region Get
|
|
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
public override object GetCacheItem(string cacheKey, Func<object> getCacheItem)
|
|
|
|
|
|
{
|
2016-07-19 15:07:38 +02:00
|
|
|
|
//no place to cache so just return the callback result
|
2016-07-19 15:37:55 +02:00
|
|
|
|
if (HasContextItems == false) return getCacheItem();
|
2016-07-19 15:07:38 +02:00
|
|
|
|
|
2014-05-27 12:16:41 +02:00
|
|
|
|
cacheKey = GetCacheKey(cacheKey);
|
|
|
|
|
|
|
|
|
|
|
|
Lazy<object> result;
|
|
|
|
|
|
|
2018-05-03 14:50:21 +02:00
|
|
|
|
try
|
2014-05-27 12:16:41 +02:00
|
|
|
|
{
|
2018-05-03 14:50:21 +02:00
|
|
|
|
EnterWriteLock();
|
2014-06-28 12:09:54 +02:00
|
|
|
|
result = ContextItems[cacheKey] as Lazy<object>; // null if key not found
|
2014-05-27 12:16:41 +02:00
|
|
|
|
|
2014-06-28 12:09:54 +02:00
|
|
|
|
// cannot create value within the lock, so if result.IsValueCreated is false, just
|
|
|
|
|
|
// do nothing here - means that if creation throws, a race condition could cause
|
|
|
|
|
|
// more than one thread to reach the return statement below and throw - accepted.
|
2015-07-08 18:14:31 +02:00
|
|
|
|
|
2014-06-28 12:09:54 +02:00
|
|
|
|
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
|
|
|
|
|
{
|
2015-05-19 09:16:05 +02:00
|
|
|
|
result = GetSafeLazy(getCacheItem);
|
2014-06-28 12:09:54 +02:00
|
|
|
|
ContextItems[cacheKey] = result;
|
2014-05-27 12:16:41 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-05-03 14:50:21 +02:00
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
ExitWriteLock();
|
|
|
|
|
|
}
|
2014-05-27 12:16:41 +02:00
|
|
|
|
|
2015-05-19 09:16:05 +02:00
|
|
|
|
// using GetSafeLazy and GetSafeLazyValue ensures that we don't cache
|
|
|
|
|
|
// exceptions (but try again and again) and silently eat them - however at
|
|
|
|
|
|
// some point we have to report them - so need to re-throw here
|
|
|
|
|
|
|
|
|
|
|
|
// this does not throw anymore
|
|
|
|
|
|
//return result.Value;
|
|
|
|
|
|
|
|
|
|
|
|
var value = result.Value; // will not throw (safe lazy)
|
2018-01-26 17:55:20 +01:00
|
|
|
|
if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once!
|
2015-05-19 09:16:05 +02:00
|
|
|
|
return value;
|
2013-08-12 15:06:12 +02:00
|
|
|
|
}
|
2015-07-08 18:14:31 +02:00
|
|
|
|
|
2014-05-27 12:16:41 +02:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Insert
|
|
|
|
|
|
#endregion
|
2013-08-12 15:06:12 +02:00
|
|
|
|
|
2018-03-20 15:48:18 +01:00
|
|
|
|
private class NoopLocker : DisposableObjectSlim
|
2016-07-19 15:07:38 +02:00
|
|
|
|
{
|
|
|
|
|
|
protected override void DisposeResources()
|
2016-07-19 15:37:55 +02:00
|
|
|
|
{ }
|
2016-07-19 15:07:38 +02:00
|
|
|
|
}
|
2013-08-12 15:06:12 +02:00
|
|
|
|
}
|
2017-07-20 11:21:28 +02:00
|
|
|
|
}
|