Files
Umbraco-CMS/src/Umbraco.Core/Cache/HttpRequestAppCache.cs

139 lines
4.4 KiB
C#
Raw Normal View History

2018-06-29 19:52:40 +02:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Umbraco.Core.Cache
{
/// <summary>
2019-01-17 11:01:23 +01:00
/// Implements a fast <see cref="IAppCache"/> on top of HttpContext.Items.
2018-06-29 19:52:40 +02:00
/// </summary>
/// <remarks>
2019-01-17 11:01:23 +01:00
/// <para>If no current HttpContext items can be found (no current HttpContext,
/// or no Items...) then this cache acts as a pass-through and does not cache
/// anything.</para>
2018-06-29 19:52:40 +02:00
/// </remarks>
2019-01-17 11:01:23 +01:00
internal class HttpRequestAppCache : FastDictionaryAppCacheBase
2018-06-29 19:52:40 +02:00
{
private readonly HttpContextBase _context;
2019-01-17 11:01:23 +01:00
/// <summary>
/// Initializes a new instance of the <see cref="HttpRequestAppCache"/> class with a context, for unit tests!
/// </summary>
public HttpRequestAppCache(HttpContextBase context)
2018-06-29 19:52:40 +02:00
{
2019-01-17 11:01:23 +01:00
_context = context;
2018-06-29 19:52:40 +02:00
}
2019-01-17 11:01:23 +01:00
/// <summary>
/// Initializes a new instance of the <see cref="HttpRequestAppCache"/> class.
/// </summary>
/// <remarks>
/// <para>Will use HttpContext.Current.</para>
/// TODO - https://github.com/umbraco/Umbraco-CMS/issues/4239 - use IHttpContextAccessor NOT HttpContext.Current
2019-01-17 11:01:23 +01:00
/// </remarks>
public HttpRequestAppCache()
{ }
2018-06-29 19:52:40 +02:00
2019-01-17 11:01:23 +01:00
private IDictionary ContextItems => _context?.Items ?? HttpContext.Current?.Items;
2018-06-29 19:52:40 +02:00
2019-01-17 11:01:23 +01:00
private bool HasContextItems => _context?.Items != null || HttpContext.Current != null;
/// <inheritdoc />
public override object Get(string key, Func<object> factory)
2018-06-29 19:52:40 +02:00
{
2019-01-17 11:01:23 +01:00
//no place to cache so just return the callback result
if (HasContextItems == false) return factory();
key = GetCacheKey(key);
Lazy<object> result;
try
{
EnterWriteLock();
result = ContextItems[key] as Lazy<object>; // null if key not found
// 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.
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
{
result = GetSafeLazy(factory);
ContextItems[key] = result;
}
}
finally
{
ExitWriteLock();
}
// 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)
if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once!
return value;
2018-06-29 19:52:40 +02:00
}
2019-01-17 11:01:23 +01:00
#region Entries
2018-06-29 19:52:40 +02:00
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
{
const string prefix = CacheItemPrefix + "-";
if (HasContextItems == false) return Enumerable.Empty<DictionaryEntry>();
return ContextItems.Cast<DictionaryEntry>()
2019-01-17 11:01:23 +01:00
.Where(x => x.Key is string s && s.StartsWith(prefix));
2018-06-29 19:52:40 +02:00
}
protected override void RemoveEntry(string key)
{
if (HasContextItems == false) return;
ContextItems.Remove(key);
}
protected override object GetEntry(string key)
{
return HasContextItems ? ContextItems[key] : null;
}
2019-01-17 11:01:23 +01:00
#endregion
2018-06-29 19:52:40 +02:00
#region Lock
private bool _entered;
2018-06-29 19:52:40 +02:00
protected override void EnterReadLock() => EnterWriteLock();
protected override void EnterWriteLock()
{
if (HasContextItems)
{
System.Threading.Monitor.Enter(ContextItems.SyncRoot, ref _entered);
}
}
protected override void ExitReadLock() => ExitWriteLock();
protected override void ExitWriteLock()
{
if (_entered)
{
_entered = false;
System.Threading.Monitor.Exit(ContextItems.SyncRoot);
}
2018-06-29 19:52:40 +02:00
}
#endregion
}
}