Merge branch 'v10/dev' into v10/contrib
This commit is contained in:
@@ -4,14 +4,11 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Collections;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Extensions;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace Umbraco.Cms.Core.Composing
|
||||
{
|
||||
@@ -21,112 +18,58 @@ namespace Umbraco.Cms.Core.Composing
|
||||
/// <remarks>
|
||||
/// <para>This class should be used to get all types, the <see cref="ITypeFinder"/> class should never be used directly.</para>
|
||||
/// <para>In most cases this class is not used directly but through extension methods that retrieve specific types.</para>
|
||||
/// <para>This class caches the types it knows to avoid excessive assembly scanning and shorten startup times, relying
|
||||
/// on a hash of the DLLs in the ~/bin folder to check for cache expiration.</para>
|
||||
/// </remarks>
|
||||
public sealed class TypeLoader
|
||||
{
|
||||
private readonly IRuntimeHash _runtimeHash;
|
||||
private readonly IAppPolicyCache _runtimeCache;
|
||||
private readonly ILogger<TypeLoader> _logger;
|
||||
private readonly IProfilingLogger _profilingLogger;
|
||||
|
||||
private readonly Dictionary<CompositeTypeTypeKey, TypeList> _types = new Dictionary<CompositeTypeTypeKey, TypeList>();
|
||||
private readonly object _locko = new object();
|
||||
private readonly object _timerLock = new object();
|
||||
private readonly Dictionary<CompositeTypeTypeKey, TypeList> _types = new ();
|
||||
private readonly object _locko = new ();
|
||||
|
||||
private Timer? _timer;
|
||||
private bool _timing;
|
||||
private string? _cachedAssembliesHash;
|
||||
private string? _currentAssembliesHash;
|
||||
private IEnumerable<Assembly>? _assemblies;
|
||||
private bool _reportedChange;
|
||||
private readonly DirectoryInfo _localTempPath;
|
||||
private readonly Lazy<string?> _fileBasePath;
|
||||
private readonly Dictionary<(string, string), IEnumerable<string>> _emptyCache = new Dictionary<(string, string), IEnumerable<string>>();
|
||||
private string? _typesListFilePath;
|
||||
private string? _typesHashFilePath;
|
||||
private IEnumerable<Assembly> _assemblies;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypeLoader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="typeFinder"></param>
|
||||
/// <param name="runtimeCache">The application runtime cache.</param>
|
||||
/// <param name="localTempPath">Files storage location.</param>
|
||||
/// <param name="logger">A profiling logger.</param>
|
||||
/// <param name="assembliesToScan"></param>
|
||||
public TypeLoader(ITypeFinder typeFinder, IRuntimeHash runtimeHash, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, ILogger<TypeLoader> logger, IProfiler profiler, IEnumerable<Assembly>? assembliesToScan = null)
|
||||
: this(typeFinder, runtimeHash, runtimeCache, localTempPath, logger, profiler, true, assembliesToScan)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypeLoader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="typeFinder"></param>
|
||||
/// <param name="runtimeCache">The application runtime cache.</param>
|
||||
/// <param name="localTempPath">Files storage location.</param>
|
||||
/// <param name="logger">A profiling logger.</param>
|
||||
/// <param name="detectChanges">Whether to detect changes using hashes.</param>
|
||||
/// <param name="assembliesToScan"></param>
|
||||
public TypeLoader(ITypeFinder typeFinder, IRuntimeHash runtimeHash, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, ILogger<TypeLoader> logger, IProfiler profiler, bool detectChanges, IEnumerable<Assembly>? assembliesToScan = null)
|
||||
[Obsolete("Please use an alternative constructor.")]
|
||||
public TypeLoader(
|
||||
ITypeFinder typeFinder,
|
||||
IRuntimeHash runtimeHash,
|
||||
IAppPolicyCache runtimeCache,
|
||||
DirectoryInfo localTempPath,
|
||||
ILogger<TypeLoader> logger,
|
||||
IProfiler profiler,
|
||||
IEnumerable<Assembly>? assembliesToScan = null)
|
||||
: this(typeFinder, logger, assembliesToScan)
|
||||
{
|
||||
if (profiler is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(profiler));
|
||||
}
|
||||
|
||||
var runtimeHashValue = runtimeHash.GetHashValue();
|
||||
CacheKey = runtimeHashValue + "umbraco-types.list";
|
||||
|
||||
TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder));
|
||||
_runtimeHash = runtimeHash;
|
||||
_runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache));
|
||||
_localTempPath = localTempPath;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_profilingLogger = new ProfilingLogger(logger, profiler);
|
||||
_assemblies = assembliesToScan;
|
||||
|
||||
_fileBasePath = new Lazy<string?>(GetFileBasePath);
|
||||
|
||||
if (detectChanges)
|
||||
{
|
||||
//first check if the cached hash is string.Empty, if it is then we need
|
||||
//do the check if they've changed
|
||||
RequiresRescanning = CachedAssembliesHash != CurrentAssembliesHash || CachedAssembliesHash == string.Empty;
|
||||
//if they have changed, we need to write the new file
|
||||
if (RequiresRescanning)
|
||||
{
|
||||
_logger.LogDebug("Plugin types are being re-scanned. Cached hash value: {CachedHash}, Current hash value: {CurrentHash}", CachedAssembliesHash, CurrentAssembliesHash);
|
||||
|
||||
// if the hash has changed, clear out the persisted list no matter what, this will force
|
||||
// rescanning of all types including lazy ones.
|
||||
// http://issues.umbraco.org/issue/U4-4789
|
||||
var typesListFilePath = GetTypesListFilePath();
|
||||
if (typesListFilePath != null)
|
||||
{
|
||||
DeleteFile(typesListFilePath, FileDeleteTimeout);
|
||||
}
|
||||
|
||||
WriteCacheTypesHash();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the hash has changed, clear out the persisted list no matter what, this will force
|
||||
// rescanning of all types including lazy ones.
|
||||
// http://issues.umbraco.org/issue/U4-4789
|
||||
var typesListFilePath = GetTypesListFilePath();
|
||||
if (typesListFilePath != null)
|
||||
{
|
||||
DeleteFile(typesListFilePath, FileDeleteTimeout);
|
||||
}
|
||||
|
||||
// always set to true if we're not detecting (generally only for testing)
|
||||
RequiresRescanning = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal string CacheKey { get; }
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TypeLoader"/> class.
|
||||
/// </summary>
|
||||
[Obsolete("Please use an alternative constructor.")]
|
||||
public TypeLoader(
|
||||
ITypeFinder typeFinder,
|
||||
IRuntimeHash runtimeHash,
|
||||
IAppPolicyCache runtimeCache,
|
||||
DirectoryInfo localTempPath,
|
||||
ILogger<TypeLoader> logger,
|
||||
IProfiler profiler,
|
||||
bool detectChanges,
|
||||
IEnumerable<Assembly>? assembliesToScan = null)
|
||||
: this(typeFinder, logger, assembliesToScan)
|
||||
{
|
||||
}
|
||||
|
||||
public TypeLoader(
|
||||
ITypeFinder typeFinder,
|
||||
ILogger<TypeLoader> logger,
|
||||
IEnumerable<Assembly>? assembliesToScan = null)
|
||||
{
|
||||
TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_assemblies = assembliesToScan;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the underlying <see cref="ITypeFinder"/>
|
||||
@@ -144,6 +87,7 @@ namespace Umbraco.Cms.Core.Composing
|
||||
/// <para>This is for unit tests.</para>
|
||||
/// </remarks>
|
||||
// internal for tests
|
||||
[Obsolete("This will be removed in a future version.")]
|
||||
public IEnumerable<Assembly> AssembliesToScan => _assemblies ??= TypeFinder.AssembliesToScan;
|
||||
|
||||
/// <summary>
|
||||
@@ -151,6 +95,7 @@ namespace Umbraco.Cms.Core.Composing
|
||||
/// </summary>
|
||||
/// <remarks>For unit tests.</remarks>
|
||||
// internal for tests
|
||||
[Obsolete("This will be removed in a future version.")]
|
||||
public IEnumerable<TypeList> TypeLists => _types.Values;
|
||||
|
||||
/// <summary>
|
||||
@@ -158,378 +103,49 @@ namespace Umbraco.Cms.Core.Composing
|
||||
/// </summary>
|
||||
/// <remarks>For unit tests.</remarks>
|
||||
// internal for tests
|
||||
[Obsolete("This will be removed in a future version.")]
|
||||
public void AddTypeList(TypeList typeList)
|
||||
{
|
||||
var tobject = typeof(object); // CompositeTypeTypeKey does not support null values
|
||||
_types[new CompositeTypeTypeKey(typeList.BaseType ?? tobject, typeList.AttributeType ?? tobject)] = typeList;
|
||||
}
|
||||
|
||||
#region Hashing
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the assemblies in bin, app_code, global.asax, etc... have changed since they were last hashed.
|
||||
/// </summary>
|
||||
private bool RequiresRescanning { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently cached hash value of the scanned assemblies.
|
||||
/// </summary>
|
||||
/// <value>The cached hash value, or string.Empty if no cache is found.</value>
|
||||
internal string CachedAssembliesHash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cachedAssembliesHash != null)
|
||||
{
|
||||
return _cachedAssembliesHash;
|
||||
}
|
||||
|
||||
var typesHashFilePath = GetTypesHashFilePath();
|
||||
if (typesHashFilePath == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (!File.Exists(typesHashFilePath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var hash = File.ReadAllText(typesHashFilePath, Encoding.UTF8);
|
||||
|
||||
_cachedAssembliesHash = hash;
|
||||
return _cachedAssembliesHash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current assemblies hash based on creating a hash from the assemblies in various places.
|
||||
/// </summary>
|
||||
/// <value>The current hash.</value>
|
||||
private string CurrentAssembliesHash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_currentAssembliesHash != null)
|
||||
{
|
||||
return _currentAssembliesHash;
|
||||
}
|
||||
|
||||
_currentAssembliesHash = _runtimeHash.GetHashValue();
|
||||
|
||||
return _currentAssembliesHash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the assembly hash file.
|
||||
/// </summary>
|
||||
private void WriteCacheTypesHash()
|
||||
{
|
||||
var typesHashFilePath = GetTypesHashFilePath();
|
||||
if (typesHashFilePath != null)
|
||||
{
|
||||
File.WriteAllText(typesHashFilePath, CurrentAssembliesHash, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cache
|
||||
|
||||
private const int ListFileOpenReadTimeout = 4000; // milliseconds
|
||||
private const int ListFileOpenWriteTimeout = 2000; // milliseconds
|
||||
private const int ListFileWriteThrottle = 500; // milliseconds - throttle before writing
|
||||
private const int ListFileCacheDuration = 2 * 60; // seconds - duration we cache the entire list
|
||||
private const int FileDeleteTimeout = 4000; // milliseconds
|
||||
|
||||
// internal for tests
|
||||
public Attempt<IEnumerable<string>?> TryGetCached(Type? baseType, Type? attributeType)
|
||||
[Obsolete("This will be removed in a future version.")]
|
||||
public Attempt<IEnumerable<string>> TryGetCached(Type baseType, Type attributeType)
|
||||
{
|
||||
var cache =
|
||||
_runtimeCache.GetCacheItem(CacheKey, ReadCacheSafe, TimeSpan.FromSeconds(ListFileCacheDuration))!;
|
||||
|
||||
cache.TryGetValue(
|
||||
(baseType == null ? string.Empty : baseType.FullName ?? string.Empty, attributeType == null ? string.Empty : attributeType.FullName ?? string.Empty),
|
||||
out IEnumerable<string>? types);
|
||||
|
||||
return types == null
|
||||
? Attempt<IEnumerable<string>?>.Fail()
|
||||
: Attempt.Succeed(types);
|
||||
}
|
||||
|
||||
private Dictionary<(string, string), IEnumerable<string>> ReadCacheSafe()
|
||||
{
|
||||
try
|
||||
{
|
||||
return ReadCache();
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var typesListFilePath = GetTypesListFilePath();
|
||||
if (typesListFilePath != null)
|
||||
{
|
||||
DeleteFile(typesListFilePath, FileDeleteTimeout);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// on-purpose, does not matter
|
||||
}
|
||||
}
|
||||
|
||||
return _emptyCache;
|
||||
return Attempt<IEnumerable<string>>.Fail();
|
||||
}
|
||||
|
||||
// internal for tests
|
||||
public Dictionary<(string, string), IEnumerable<string>> ReadCache()
|
||||
{
|
||||
var typesListFilePath = GetTypesListFilePath();
|
||||
if (typesListFilePath == null || File.Exists(typesListFilePath) == false)
|
||||
{
|
||||
return _emptyCache;
|
||||
}
|
||||
|
||||
var cache = new Dictionary<(string, string), IEnumerable<string>>();
|
||||
using (var stream = GetFileStream(typesListFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, ListFileOpenReadTimeout))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var baseType = reader.ReadLine();
|
||||
if (baseType == null)
|
||||
{
|
||||
return cache; // exit
|
||||
}
|
||||
|
||||
if (baseType.StartsWith("<"))
|
||||
{
|
||||
break; // old xml
|
||||
}
|
||||
|
||||
var attributeType = reader.ReadLine();
|
||||
if (attributeType == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var types = new List<string>();
|
||||
while (true)
|
||||
{
|
||||
var type = reader.ReadLine();
|
||||
if (type == null)
|
||||
{
|
||||
types = null; // break 2 levels
|
||||
break;
|
||||
}
|
||||
if (type == string.Empty)
|
||||
{
|
||||
cache[(baseType, attributeType)] = types;
|
||||
break;
|
||||
}
|
||||
types.Add(type);
|
||||
}
|
||||
|
||||
if (types == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache.Clear();
|
||||
return cache;
|
||||
}
|
||||
[Obsolete("This will be removed in a future version.")]
|
||||
public Dictionary<(string, string), IEnumerable<string>> ReadCache() => null;
|
||||
|
||||
// internal for tests
|
||||
public string? GetTypesListFilePath() => _typesListFilePath ??= _fileBasePath.Value == null ? null : _fileBasePath.Value + ".list";
|
||||
|
||||
private string? GetTypesHashFilePath() => _typesHashFilePath ??= _fileBasePath.Value == null ? null : _fileBasePath.Value + ".hash";
|
||||
|
||||
/// <summary>
|
||||
/// Used to produce the Lazy value of _fileBasePath
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private string? GetFileBasePath()
|
||||
{
|
||||
if (_localTempPath == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileBasePath = Path.Combine(_localTempPath.FullName, "TypesCache", "umbraco-types." + EnvironmentHelper.FileSafeMachineName);
|
||||
|
||||
// ensure that the folder exists
|
||||
var directory = Path.GetDirectoryName(fileBasePath);
|
||||
if (directory == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not determine folder for path \"{fileBasePath}\".");
|
||||
}
|
||||
|
||||
if (Directory.Exists(directory) == false)
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
return fileBasePath;
|
||||
}
|
||||
[Obsolete("This will be removed in a future version.")]
|
||||
public string GetTypesListFilePath() => null;
|
||||
|
||||
// internal for tests
|
||||
[Obsolete("This will be removed in a future version.")]
|
||||
public void WriteCache()
|
||||
{
|
||||
_logger.LogDebug("Writing cache file.");
|
||||
var typesListFilePath = GetTypesListFilePath();
|
||||
if (typesListFilePath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (var stream = GetFileStream(typesListFilePath, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout))
|
||||
using (var writer = new StreamWriter(stream))
|
||||
{
|
||||
foreach (var typeList in _types.Values)
|
||||
{
|
||||
writer.WriteLine(typeList.BaseType == null ? string.Empty : typeList.BaseType.FullName);
|
||||
writer.WriteLine(typeList.AttributeType == null ? string.Empty : typeList.AttributeType.FullName);
|
||||
foreach (var type in typeList.Types)
|
||||
{
|
||||
writer.WriteLine(type.AssemblyQualifiedName);
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// internal for tests
|
||||
internal void UpdateCache()
|
||||
{
|
||||
void TimerRelease(object? o)
|
||||
{
|
||||
lock (_timerLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
WriteCache();
|
||||
}
|
||||
catch { /* bah - just don't die */ }
|
||||
if (!_timing)
|
||||
_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
lock (_timerLock)
|
||||
{
|
||||
if (_timer == null)
|
||||
{
|
||||
_timer = new Timer(TimerRelease, null, ListFileWriteThrottle, Timeout.Infinite);
|
||||
}
|
||||
else
|
||||
{
|
||||
_timer.Change(ListFileWriteThrottle, Timeout.Infinite);
|
||||
}
|
||||
|
||||
_timing = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes cache files and internal cache.
|
||||
/// Clears cache.
|
||||
/// </summary>
|
||||
/// <remarks>Generally only used for resetting cache, for example during the install process.</remarks>
|
||||
[Obsolete("This will be removed in a future version.")]
|
||||
public void ClearTypesCache()
|
||||
{
|
||||
var typesListFilePath = GetTypesListFilePath();
|
||||
if (typesListFilePath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DeleteFile(typesListFilePath, FileDeleteTimeout);
|
||||
|
||||
var typesHashFilePath = GetTypesHashFilePath();
|
||||
if (typesHashFilePath != null)
|
||||
{
|
||||
DeleteFile(typesHashFilePath, FileDeleteTimeout);
|
||||
}
|
||||
|
||||
_runtimeCache.Clear(CacheKey);
|
||||
}
|
||||
|
||||
private Stream GetFileStream(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, int timeoutMilliseconds)
|
||||
{
|
||||
const int pauseMilliseconds = 250;
|
||||
var attempts = timeoutMilliseconds / pauseMilliseconds;
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new FileStream(path, fileMode, fileAccess, fileShare);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (--attempts == 0)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Attempted to get filestream for file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds);
|
||||
Thread.Sleep(pauseMilliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteFile(string path, int timeoutMilliseconds)
|
||||
{
|
||||
const int pauseMilliseconds = 250;
|
||||
var attempts = timeoutMilliseconds / pauseMilliseconds;
|
||||
while (File.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (--attempts == 0)
|
||||
throw;
|
||||
|
||||
_logger.LogDebug("Attempted to delete file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds);
|
||||
Thread.Sleep(pauseMilliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Get Assembly Attributes
|
||||
|
||||
///// <summary>
|
||||
///// Gets the assembly attributes of the specified type <typeparamref name="T" />.
|
||||
///// </summary>
|
||||
///// <typeparam name="T">The attribute type.</typeparam>
|
||||
///// <returns>
|
||||
///// The assembly attributes of the specified type <typeparamref name="T" />.
|
||||
///// </returns>
|
||||
//public IEnumerable<T> GetAssemblyAttributes<T>()
|
||||
// where T : Attribute
|
||||
//{
|
||||
// return AssembliesToScan.SelectMany(a => a.GetCustomAttributes<T>()).ToList();
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// Gets all the assembly attributes.
|
||||
///// </summary>
|
||||
///// <returns>
|
||||
///// All assembly attributes.
|
||||
///// </returns>
|
||||
//public IEnumerable<Attribute> GetAssemblyAttributes()
|
||||
//{
|
||||
// return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList();
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly attributes of the specified <paramref name="attributeTypes" />.
|
||||
/// </summary>
|
||||
@@ -701,17 +317,9 @@ namespace Umbraco.Cms.Core.Composing
|
||||
// loader is mostly not going to be used in any kind of massively multi-threaded scenario - so,
|
||||
// a plain lock is enough
|
||||
|
||||
var name = GetName(baseType, attributeType);
|
||||
|
||||
lock (_locko)
|
||||
{
|
||||
using (_profilingLogger.DebugDuration<TypeLoader>(
|
||||
"Getting " + name,
|
||||
"Got " + name)) // cannot contain typesFound.Count as it's evaluated before the find
|
||||
{
|
||||
// get within a lock & timer
|
||||
return GetTypesInternalLocked(baseType, attributeType, finder, action, cache);
|
||||
}
|
||||
return GetTypesInternalLocked(baseType, attributeType, finder, action, cache);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,9 +339,9 @@ namespace Umbraco.Cms.Core.Composing
|
||||
{
|
||||
// check if the TypeList already exists, if so return it, if not we'll create it
|
||||
var tobject = typeof(object); // CompositeTypeTypeKey does not support null values
|
||||
|
||||
var listKey = new CompositeTypeTypeKey(baseType ?? tobject, attributeType ?? tobject);
|
||||
TypeList? typeList = null;
|
||||
TypeList typeList = null;
|
||||
|
||||
if (cache)
|
||||
{
|
||||
_types.TryGetValue(listKey, out typeList); // else null
|
||||
@@ -750,68 +358,12 @@ namespace Umbraco.Cms.Core.Composing
|
||||
// else proceed,
|
||||
typeList = new TypeList(baseType, attributeType);
|
||||
|
||||
var typesListFilePath = GetTypesListFilePath();
|
||||
var scan = RequiresRescanning || typesListFilePath == null || File.Exists(typesListFilePath) == false;
|
||||
// either we had to scan, or we could not get the types from the cache file - scan now
|
||||
_logger.LogDebug("Getting {TypeName}: " + action + ".", GetName(baseType, attributeType));
|
||||
|
||||
if (scan)
|
||||
foreach (var t in finder())
|
||||
{
|
||||
// either we have to rescan, or we could not find the cache file:
|
||||
// report (only once) and scan and update the cache file
|
||||
if (_reportedChange == false)
|
||||
{
|
||||
_logger.LogDebug("Assemblies changes detected, need to rescan everything.");
|
||||
_reportedChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (scan == false)
|
||||
{
|
||||
// if we don't have to scan, try the cache
|
||||
var cacheResult = TryGetCached(baseType, attributeType);
|
||||
|
||||
// here we need to identify if the CachedTypeNotFoundInFile was the exception, if it was then we need to re-scan
|
||||
// in some cases the type will not have been scanned for on application startup, but the assemblies haven't changed
|
||||
// so in this instance there will never be a result.
|
||||
if (cacheResult.Exception is CachedTypeNotFoundInFileException || cacheResult.Success == false)
|
||||
{
|
||||
_logger.LogDebug("Getting {TypeName}: failed to load from cache file, must scan assemblies.", GetName(baseType, attributeType));
|
||||
scan = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// successfully retrieved types from the file cache: load
|
||||
foreach (var type in cacheResult.Result!)
|
||||
{
|
||||
var resolvedType = TypeFinder.GetTypeByName(type);
|
||||
if (resolvedType != null)
|
||||
{
|
||||
typeList.Add(resolvedType);
|
||||
}
|
||||
else
|
||||
{
|
||||
// in case of any exception, we have to exit, and revert to scanning
|
||||
_logger.LogWarning("Getting {TypeName}: failed to load cache file type {CacheType}, reverting to scanning assemblies.", GetName(baseType, attributeType), type);
|
||||
scan = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (scan == false)
|
||||
{
|
||||
_logger.LogDebug("Getting {TypeName}: loaded types from cache file.", GetName(baseType, attributeType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scan)
|
||||
{
|
||||
// either we had to scan, or we could not get the types from the cache file - scan now
|
||||
_logger.LogDebug("Getting {TypeName}: " + action + ".", GetName(baseType, attributeType));
|
||||
|
||||
foreach (var t in finder())
|
||||
{
|
||||
typeList.Add(t);
|
||||
}
|
||||
typeList.Add(t);
|
||||
}
|
||||
|
||||
// if we are to cache the results, do so
|
||||
@@ -821,11 +373,6 @@ namespace Umbraco.Cms.Core.Composing
|
||||
if (added)
|
||||
{
|
||||
_types[listKey] = typeList;
|
||||
//if we are scanning then update the cache file
|
||||
if (scan)
|
||||
{
|
||||
UpdateCache();
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug("Got {TypeName}, caching ({CacheType}).", GetName(baseType, attributeType), added.ToString().ToLowerInvariant());
|
||||
@@ -864,7 +411,7 @@ namespace Umbraco.Cms.Core.Composing
|
||||
/// </summary>
|
||||
public void Add(Type type)
|
||||
{
|
||||
if (BaseType?.IsAssignableFrom(type) == false)
|
||||
if (BaseType.IsAssignableFrom(type) == false)
|
||||
throw new ArgumentException("Base type " + BaseType + " is not assignable from type " + type + ".", nameof(type));
|
||||
_types.Add(type);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -27,6 +28,7 @@ namespace Umbraco.Cms.Core.DependencyInjection
|
||||
/// <remarks>
|
||||
/// This may be null.
|
||||
/// </remarks>
|
||||
[Obsolete("This property will be removed in a future version, please find an alternative approach.")]
|
||||
IHostingEnvironment? BuilderHostingEnvironment { get; }
|
||||
|
||||
IProfiler Profiler { get; }
|
||||
|
||||
53
src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs
Normal file
53
src/Umbraco.Core/Extensions/HostEnvironmentExtensions.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="IHostEnvironment" /> interface.
|
||||
/// </summary>
|
||||
public static class HostEnvironmentExtensions
|
||||
{
|
||||
private static string s_temporaryApplicationId;
|
||||
|
||||
/// <summary>
|
||||
/// Maps a virtual path to a physical path to the application's content root.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally the content root is the parent directory of the web root directory.
|
||||
/// </remarks>
|
||||
public static string MapPathContentRoot(this IHostEnvironment hostEnvironment, string path)
|
||||
{
|
||||
var root = hostEnvironment.ContentRootPath;
|
||||
|
||||
var newPath = path.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
|
||||
|
||||
// TODO: This is a temporary error because we switched from IOHelper.MapPath to HostingEnvironment.MapPathXXX
|
||||
// IOHelper would check if the path passed in started with the root, and not prepend the root again if it did,
|
||||
// however if you are requesting a path be mapped, it should always assume the path is relative to the root, not
|
||||
// absolute in the file system. This error will help us find and fix improper uses, and should be removed once
|
||||
// all those uses have been found and fixed
|
||||
if (newPath.StartsWith(root))
|
||||
{
|
||||
throw new ArgumentException("The path appears to already be fully qualified. Please remove the call to MapPathContentRoot");
|
||||
}
|
||||
|
||||
return Path.Combine(root, newPath.TrimStart(Constants.CharArrays.TildeForwardSlashBackSlash));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a temporary application id for use before the ioc container is built.
|
||||
/// </summary>
|
||||
public static string GetTemporaryApplicationId(this IHostEnvironment hostEnvironment)
|
||||
{
|
||||
if (s_temporaryApplicationId != null)
|
||||
{
|
||||
return s_temporaryApplicationId;
|
||||
}
|
||||
|
||||
return s_temporaryApplicationId = hostEnvironment.ContentRootPath.GenerateHash();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,21 @@ namespace Umbraco.Cms.Core.Hosting
|
||||
/// between restarts of that Umbraco website/application on that specific server.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The value of this does not necesarily distinguish between unique workers/servers for this Umbraco application.
|
||||
/// The value of this does not distinguish between unique workers/servers for this Umbraco application.
|
||||
/// Usage of this must take into account that the same <see cref="ApplicationId"/> may be returned for the same
|
||||
/// Umbraco website hosted on different servers. Similarly the usage of this must take into account that a different
|
||||
/// Umbraco website hosted on different servers.<br/>
|
||||
/// Similarly the usage of this must take into account that a different
|
||||
/// <see cref="ApplicationId"/> may be returned for the same Umbraco website hosted on different servers.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This returns a hash of the value of IApplicationDiscriminator.Discriminator (which is most likely just the value of <see cref=" Microsoft.Extensions.Hosting.IHostEnvironment.ContentRootPath"/> unless an alternative implementation of IApplicationDiscriminator has been registered).<br/>
|
||||
/// However during ConfigureServices a temporary instance of IHostingEnvironment is constructed which guarantees that this will be the hash of <see cref=" Microsoft.Extensions.Hosting.IHostEnvironment.ContentRootPath"/>, so the value may differ depend on when the property is used.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If you require this value during ConfigureServices it is probably a code smell.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Obsolete("Please use IApplicationDiscriminator.Discriminator instead.")]
|
||||
string ApplicationId { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -58,6 +67,7 @@ namespace Umbraco.Cms.Core.Hosting
|
||||
/// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the content root are the same, however
|
||||
/// in netcore the web root is /www therefore this will Map to a physical path within www.
|
||||
/// </remarks>
|
||||
[Obsolete("Please use the MapPathWebRoot extension method on an instance of IWebHostEnvironment instead")]
|
||||
string MapPathWebRoot(string path);
|
||||
|
||||
/// <summary>
|
||||
@@ -67,6 +77,7 @@ namespace Umbraco.Cms.Core.Hosting
|
||||
/// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the content root are the same, however
|
||||
/// in netcore the web root is /www therefore this will Map to a physical path within www.
|
||||
/// </remarks>
|
||||
[Obsolete("Please use the MapPathContentRoot extension method on an instance of IHostEnvironment (or IWebHostEnvironment) instead")]
|
||||
string MapPathContentRoot(string path);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,13 +2,14 @@ using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using Serilog.Configuration;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting;
|
||||
using Serilog.Formatting.Compact;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Logging.Serilog.Enrichers;
|
||||
using Umbraco.Cms.Infrastructure.Logging.Serilog;
|
||||
@@ -17,16 +18,15 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
public static class LoggerConfigExtensions
|
||||
{
|
||||
private const string AppDomainId = "AppDomainId";
|
||||
|
||||
/// <summary>
|
||||
/// This configures Serilog with some defaults
|
||||
/// Such as adding ProcessID, Thread, AppDomain etc
|
||||
/// It is highly recommended that you keep/use this default in your own logging config customizations
|
||||
/// </summary>
|
||||
[Obsolete("Please use an alternative method.")]
|
||||
public static LoggerConfiguration MinimalConfiguration(
|
||||
this LoggerConfiguration logConfig,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
@@ -38,9 +38,10 @@ namespace Umbraco.Extensions
|
||||
/// Such as adding ProcessID, Thread, AppDomain etc
|
||||
/// It is highly recommended that you keep/use this default in your own logging config customizations
|
||||
/// </summary>
|
||||
[Obsolete("Please use an alternative method.")]
|
||||
public static LoggerConfiguration MinimalConfiguration(
|
||||
this LoggerConfiguration logConfig,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration,
|
||||
out UmbracoFileConfiguration umbFileConfiguration)
|
||||
@@ -49,7 +50,7 @@ namespace Umbraco.Extensions
|
||||
|
||||
//Set this environment variable - so that it can be used in external config file
|
||||
//add key="serilog:write-to:RollingFile.pathFormat" value="%BASEDIR%\logs\log.txt" />
|
||||
Environment.SetEnvironmentVariable("BASEDIR", hostingEnvironment.MapPathContentRoot("/").TrimEnd("\\"), EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("BASEDIR", hostingEnvironment.MapPathContentRoot("/").TrimEnd(Path.DirectorySeparatorChar), EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("UMBLOGDIR", loggingConfiguration.LogDirectory, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("MACHINENAME", Environment.MachineName, EnvironmentVariableTarget.Process);
|
||||
|
||||
@@ -57,8 +58,7 @@ namespace Umbraco.Extensions
|
||||
.Enrich.WithProcessId()
|
||||
.Enrich.WithProcessName()
|
||||
.Enrich.WithThreadId()
|
||||
.Enrich.WithProperty(AppDomainId, AppDomain.CurrentDomain.Id)
|
||||
.Enrich.WithProperty("AppDomainAppId", hostingEnvironment.ApplicationId.ReplaceNonAlphanumericChars(string.Empty))
|
||||
.Enrich.WithProperty("ApplicationId", hostingEnvironment.ApplicationId) // Updated later by ApplicationIdEnricher
|
||||
.Enrich.WithProperty("MachineName", Environment.MachineName)
|
||||
.Enrich.With<Log4NetLevelMapperEnricher>()
|
||||
.Enrich.FromLogContext(); // allows us to dynamically enrich
|
||||
@@ -81,6 +81,48 @@ namespace Umbraco.Extensions
|
||||
return logConfig;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This configures Serilog with some defaults
|
||||
/// Such as adding ProcessID, Thread, AppDomain etc
|
||||
/// It is highly recommended that you keep/use this default in your own logging config customizations
|
||||
/// </summary>
|
||||
public static LoggerConfiguration MinimalConfiguration(
|
||||
this LoggerConfiguration logConfig,
|
||||
IHostEnvironment hostEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
UmbracoFileConfiguration umbracoFileConfiguration)
|
||||
{
|
||||
global::Serilog.Debugging.SelfLog.Enable(msg => System.Diagnostics.Debug.WriteLine(msg));
|
||||
|
||||
//Set this environment variable - so that it can be used in external config file
|
||||
//add key="serilog:write-to:RollingFile.pathFormat" value="%BASEDIR%\logs\log.txt" />
|
||||
Environment.SetEnvironmentVariable("BASEDIR", hostEnvironment.MapPathContentRoot("/").TrimEnd("\\"), EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("UMBLOGDIR", loggingConfiguration.LogDirectory, EnvironmentVariableTarget.Process);
|
||||
Environment.SetEnvironmentVariable("MACHINENAME", Environment.MachineName, EnvironmentVariableTarget.Process);
|
||||
|
||||
logConfig.MinimumLevel.Verbose() //Set to highest level of logging (as any sinks may want to restrict it to Errors only)
|
||||
.Enrich.WithProcessId()
|
||||
.Enrich.WithProcessName()
|
||||
.Enrich.WithThreadId()
|
||||
.Enrich.WithProperty("ApplicationId", hostEnvironment.GetTemporaryApplicationId()) // Updated later by ApplicationIdEnricher
|
||||
.Enrich.WithProperty("MachineName", Environment.MachineName)
|
||||
.Enrich.With<Log4NetLevelMapperEnricher>()
|
||||
.Enrich.FromLogContext(); // allows us to dynamically enrich
|
||||
|
||||
logConfig.WriteTo.UmbracoFile(
|
||||
path: umbracoFileConfiguration.GetPath(loggingConfiguration.LogDirectory),
|
||||
fileSizeLimitBytes: umbracoFileConfiguration.FileSizeLimitBytes,
|
||||
restrictedToMinimumLevel: umbracoFileConfiguration.RestrictedToMinimumLevel,
|
||||
rollingInterval: umbracoFileConfiguration.RollingInterval,
|
||||
flushToDiskInterval: umbracoFileConfiguration.FlushToDiskInterval,
|
||||
rollOnFileSizeLimit: umbracoFileConfiguration.RollOnFileSizeLimit,
|
||||
retainedFileCountLimit: umbracoFileConfiguration.RetainedFileCountLimit
|
||||
);
|
||||
|
||||
return logConfig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a .txt format log at /App_Data/Logs/
|
||||
/// </summary>
|
||||
@@ -90,7 +132,7 @@ namespace Umbraco.Extensions
|
||||
/// <param name="retainedFileCount">The number of days to keep log files. Default is set to null which means all logs are kept</param>
|
||||
public static LoggerConfiguration OutputDefaultTextFile(
|
||||
this LoggerConfiguration logConfig,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
LogEventLevel minimumLevel = LogEventLevel.Verbose)
|
||||
{
|
||||
//Main .txt logfile - in similar format to older Log4Net output
|
||||
@@ -109,7 +151,8 @@ namespace Umbraco.Extensions
|
||||
/// <remarks>
|
||||
/// Used in config - If renamed or moved to other assembly the config file also has be updated.
|
||||
/// </remarks>
|
||||
public static LoggerConfiguration UmbracoFile(this LoggerSinkConfiguration configuration,
|
||||
public static LoggerConfiguration UmbracoFile(
|
||||
this LoggerSinkConfiguration configuration,
|
||||
string path,
|
||||
ITextFormatter? formatter = null,
|
||||
LogEventLevel restrictedToMinimumLevel = LogEventLevel.Verbose,
|
||||
@@ -122,30 +165,29 @@ namespace Umbraco.Extensions
|
||||
Encoding? encoding = null
|
||||
)
|
||||
{
|
||||
formatter ??= new CompactJsonFormatter();
|
||||
|
||||
if (formatter is null)
|
||||
{
|
||||
formatter = new CompactJsonFormatter();
|
||||
}
|
||||
|
||||
/* Async sink has an event buffer of 10k events (by default) so we're not constantly thrashing the disk.
|
||||
* I noticed that with File buffered + large number of log entries (global minimum Debug)
|
||||
* an ungraceful shutdown would consistently result in output that just stops halfway through an entry.
|
||||
* with buffered false on the inner sink ungraceful shutdowns still don't seem to wreck the file.
|
||||
*/
|
||||
return configuration.Async(
|
||||
asyncConfiguration => asyncConfiguration.Map(AppDomainId, (_,mapConfiguration) =>
|
||||
mapConfiguration.File(
|
||||
formatter,
|
||||
path,
|
||||
restrictedToMinimumLevel,
|
||||
fileSizeLimitBytes,
|
||||
levelSwitch,
|
||||
buffered:true,
|
||||
shared:false,
|
||||
flushToDiskInterval,
|
||||
rollingInterval,
|
||||
rollOnFileSizeLimit,
|
||||
retainedFileCountLimit,
|
||||
encoding,
|
||||
null),
|
||||
sinkMapCountLimit:0)
|
||||
);
|
||||
cfg =>
|
||||
cfg.File(
|
||||
formatter,
|
||||
path,
|
||||
restrictedToMinimumLevel,
|
||||
fileSizeLimitBytes,
|
||||
levelSwitch,
|
||||
buffered: false, // see notes above.
|
||||
shared: false,
|
||||
flushToDiskInterval,
|
||||
rollingInterval,
|
||||
rollOnFileSizeLimit,
|
||||
retainedFileCountLimit,
|
||||
encoding,
|
||||
null));
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +200,7 @@ namespace Umbraco.Extensions
|
||||
/// <param name="retainedFileCount">The number of days to keep log files. Default is set to null which means all logs are kept</param>
|
||||
public static LoggerConfiguration OutputDefaultJsonFile(
|
||||
this LoggerConfiguration logConfig,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration, LogEventLevel minimumLevel = LogEventLevel.Verbose, int? retainedFileCount = null)
|
||||
{
|
||||
// .clef format (Compact log event format, that can be imported into local SEQ & will make searching/filtering logs easier)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Infrastructure.Logging.Serilog;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -21,8 +21,9 @@ namespace Umbraco.Cms.Core.Logging.Serilog
|
||||
SerilogLog = logConfig.CreateLogger();
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static SerilogLogger CreateWithDefaultConfiguration(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
@@ -33,8 +34,9 @@ namespace Umbraco.Cms.Core.Logging.Serilog
|
||||
/// Creates a logger with some pre-defined configuration and remainder from config file
|
||||
/// </summary>
|
||||
/// <remarks>Used by UmbracoApplicationBase to get its logger.</remarks>
|
||||
[Obsolete]
|
||||
public static SerilogLogger CreateWithDefaultConfiguration(
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
Umbraco.Cms.Core.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
ILoggingConfiguration loggingConfiguration,
|
||||
IConfiguration configuration,
|
||||
out UmbracoFileConfiguration umbracoFileConfig)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -34,8 +35,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IUmbracoVersion _umbracoVersion;
|
||||
private readonly IServiceProvider? _serviceProvider;
|
||||
private readonly IHostApplicationLifetime? _hostApplicationLifetime;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IHostApplicationLifetime _hostApplicationLifetime;
|
||||
private readonly ILogger<CoreRuntime> _logger;
|
||||
private CancellationToken _cancellationToken;
|
||||
|
||||
@@ -53,8 +54,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
IEventAggregator eventAggregator,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IUmbracoVersion umbracoVersion,
|
||||
IServiceProvider? serviceProvider,
|
||||
IHostApplicationLifetime? hostApplicationLifetime)
|
||||
IServiceProvider serviceProvider,
|
||||
IHostApplicationLifetime hostApplicationLifetime)
|
||||
{
|
||||
State = state;
|
||||
|
||||
@@ -85,7 +86,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
IEventAggregator eventAggregator,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IUmbracoVersion umbracoVersion,
|
||||
IServiceProvider? serviceProvider)
|
||||
IServiceProvider serviceProvider)
|
||||
: this(
|
||||
state,
|
||||
loggerFactory,
|
||||
@@ -153,14 +154,14 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
// Store token, so we can re-use this during restart
|
||||
_cancellationToken = cancellationToken;
|
||||
|
||||
// Just in-case HostBuilder.ConfigureUmbracoDefaults() isn't used (e.g. upgrade from 9 and ignored advice).
|
||||
if (StaticServiceProvider.Instance == null!)
|
||||
{
|
||||
StaticServiceProvider.Instance = _serviceProvider;
|
||||
}
|
||||
|
||||
if (isRestarting == false)
|
||||
{
|
||||
StaticApplicationLogging.Initialize(_loggerFactory);
|
||||
if (_serviceProvider is not null)
|
||||
{
|
||||
StaticServiceProvider.Instance = _serviceProvider;
|
||||
}
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, args)
|
||||
=> _logger.LogError(args.ExceptionObject as Exception, $"Unhandled exception in AppDomain{(args.IsTerminating ? " (terminating)" : null)}.");
|
||||
}
|
||||
@@ -220,8 +221,8 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
if (isRestarting == false)
|
||||
{
|
||||
// Add application started and stopped notifications last (to ensure they're always published after starting)
|
||||
_hostApplicationLifetime?.ApplicationStarted.Register(() => _eventAggregator.Publish(new UmbracoApplicationStartedNotification(false)));
|
||||
_hostApplicationLifetime?.ApplicationStopped.Register(() => _eventAggregator.Publish(new UmbracoApplicationStoppedNotification(false)));
|
||||
_hostApplicationLifetime.ApplicationStarted.Register(() => _eventAggregator.Publish(new UmbracoApplicationStartedNotification(false)));
|
||||
_hostApplicationLifetime.ApplicationStopped.Register(() => _eventAggregator.Publish(new UmbracoApplicationStoppedNotification(false)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +234,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
|
||||
private void AcquireMainDom()
|
||||
{
|
||||
using DisposableTimer? timer = _profilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired.");
|
||||
using DisposableTimer timer = _profilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired.");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -254,7 +255,7 @@ namespace Umbraco.Cms.Infrastructure.Runtime
|
||||
return;
|
||||
}
|
||||
|
||||
using DisposableTimer? timer = _profilingLogger.DebugDuration<CoreRuntime>("Determining runtime level.", "Determined.");
|
||||
using DisposableTimer timer = _profilingLogger.DebugDuration<CoreRuntime>("Determining runtime level.", "Determined.");
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.DataProtection.Infrastructure;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Collections;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Web.Common.Extensions;
|
||||
using Umbraco.Extensions;
|
||||
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
||||
|
||||
@@ -17,21 +18,39 @@ namespace Umbraco.Cms.Web.Common.AspNetCore
|
||||
public class AspNetCoreHostingEnvironment : IHostingEnvironment
|
||||
{
|
||||
private readonly ConcurrentHashSet<Uri> _applicationUrls = new ConcurrentHashSet<Uri>();
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IOptionsMonitor<HostingSettings> _hostingSettings;
|
||||
private readonly IOptionsMonitor<WebRoutingSettings> _webRoutingSettings;
|
||||
private readonly IWebHostEnvironment _webHostEnvironment;
|
||||
private readonly IApplicationDiscriminator? _applicationDiscriminator;
|
||||
|
||||
private string? _applicationId;
|
||||
private string? _localTempPath;
|
||||
|
||||
private UrlMode _urlProviderMode;
|
||||
|
||||
[Obsolete("Please use an alternative constructor.")]
|
||||
public AspNetCoreHostingEnvironment(
|
||||
IServiceProvider serviceProvider,
|
||||
IOptionsMonitor<HostingSettings> hostingSettings,
|
||||
IOptionsMonitor<WebRoutingSettings> webRoutingSettings,
|
||||
IWebHostEnvironment webHostEnvironment)
|
||||
: this(hostingSettings, webRoutingSettings, webHostEnvironment, serviceProvider.GetService<IApplicationDiscriminator>())
|
||||
{
|
||||
}
|
||||
|
||||
public AspNetCoreHostingEnvironment(
|
||||
IOptionsMonitor<HostingSettings> hostingSettings,
|
||||
IOptionsMonitor<WebRoutingSettings> webRoutingSettings,
|
||||
IWebHostEnvironment webHostEnvironment,
|
||||
IApplicationDiscriminator applicationDiscriminator)
|
||||
: this(hostingSettings, webRoutingSettings, webHostEnvironment) =>
|
||||
_applicationDiscriminator = applicationDiscriminator;
|
||||
|
||||
public AspNetCoreHostingEnvironment(
|
||||
IOptionsMonitor<HostingSettings> hostingSettings,
|
||||
IOptionsMonitor<WebRoutingSettings> webRoutingSettings,
|
||||
IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_hostingSettings = hostingSettings ?? throw new ArgumentNullException(nameof(hostingSettings));
|
||||
_webRoutingSettings = webRoutingSettings ?? throw new ArgumentNullException(nameof(webRoutingSettings));
|
||||
_webHostEnvironment = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment));
|
||||
@@ -74,16 +93,7 @@ namespace Umbraco.Cms.Web.Common.AspNetCore
|
||||
return _applicationId;
|
||||
}
|
||||
|
||||
var appId = _serviceProvider.GetApplicationUniqueIdentifier();
|
||||
if (appId == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not acquire an ApplicationId, ensure DataProtection services and an IHostEnvironment are registered");
|
||||
}
|
||||
|
||||
// Hash this value because it can really be anything. By default this will be the application's path.
|
||||
// TODO: Test on IIS, hopefully this would be equivalent to the IIS unique ID.
|
||||
// This could also contain sensitive information (i.e. like the physical path) which we don't want to expose in logs.
|
||||
_applicationId = appId.GenerateHash();
|
||||
_applicationId = _applicationDiscriminator?.GetApplicationId() ?? _webHostEnvironment.GetTemporaryApplicationId();
|
||||
|
||||
return _applicationId;
|
||||
}
|
||||
@@ -136,27 +146,10 @@ namespace Umbraco.Cms.Web.Common.AspNetCore
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string MapPathWebRoot(string path) => MapPath(_webHostEnvironment.WebRootPath, path);
|
||||
public string MapPathWebRoot(string path) => _webHostEnvironment.MapPathWebRoot(path);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string MapPathContentRoot(string path) => MapPath(_webHostEnvironment.ContentRootPath, path);
|
||||
|
||||
private string MapPath(string root, string path)
|
||||
{
|
||||
var newPath = path.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
|
||||
|
||||
// TODO: This is a temporary error because we switched from IOHelper.MapPath to HostingEnvironment.MapPathXXX
|
||||
// IOHelper would check if the path passed in started with the root, and not prepend the root again if it did,
|
||||
// however if you are requesting a path be mapped, it should always assume the path is relative to the root, not
|
||||
// absolute in the file system. This error will help us find and fix improper uses, and should be removed once
|
||||
// all those uses have been found and fixed
|
||||
if (newPath.StartsWith(root))
|
||||
{
|
||||
throw new ArgumentException("The path appears to already be fully qualified. Please remove the call to MapPath");
|
||||
}
|
||||
|
||||
return Path.Combine(root, newPath.TrimStart(Core.Constants.CharArrays.TildeForwardSlashBackSlash));
|
||||
}
|
||||
public string MapPathContentRoot(string path) => _webHostEnvironment.MapPathContentRoot(path);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string ToAbsolute(string virtualPath)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using SixLabors.ImageSharp.Web.Caching;
|
||||
@@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Web.Middleware;
|
||||
using SixLabors.ImageSharp.Web.Processors;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
using Umbraco.Cms.Web.Common.ImageProcessors;
|
||||
|
||||
@@ -73,9 +74,13 @@ namespace Umbraco.Extensions
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
})
|
||||
.Configure<PhysicalFileSystemCacheOptions>(options => options.CacheFolder = builder.BuilderHostingEnvironment?.MapPathContentRoot(imagingSettings.Cache.CacheFolder))
|
||||
.AddProcessor<CropWebProcessor>();
|
||||
}).AddProcessor<CropWebProcessor>();
|
||||
|
||||
builder.Services.AddOptions<PhysicalFileSystemCacheOptions>()
|
||||
.Configure<IHostEnvironment>((opt, hostEnvironment) =>
|
||||
{
|
||||
opt.CacheFolder = hostEnvironment.MapPathContentRoot(imagingSettings.Cache.CacheFolder);
|
||||
});
|
||||
|
||||
// Configure middleware to use the registered/shared ImageSharp configuration
|
||||
builder.Services.AddTransient<IConfigureOptions<ImageSharpMiddlewareOptions>, ImageSharpConfigurationOptions>();
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using Dazinator.Extensions.FileProviders.GlobPatternFilter;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.DataProtection.Infrastructure;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Compilation;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Hosting;
|
||||
using Serilog.Extensions.Logging;
|
||||
using Smidge;
|
||||
using Smidge.Cache;
|
||||
using Smidge.FileProcessors;
|
||||
@@ -29,6 +29,7 @@ using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Diagnostics;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Macros;
|
||||
@@ -90,15 +91,12 @@ namespace Umbraco.Extensions
|
||||
throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
IHostingEnvironment tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config);
|
||||
|
||||
var loggingDir = tempHostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.LogFiles);
|
||||
var loggingConfig = new LoggingConfiguration(loggingDir);
|
||||
|
||||
services.AddLogger(tempHostingEnvironment, loggingConfig, config);
|
||||
// Setup static application logging ASAP (e.g. during configure services).
|
||||
// Will log to SilentLogger until Serilog.Log.Logger is setup.
|
||||
StaticApplicationLogging.Initialize(new SerilogLoggerFactory());
|
||||
|
||||
// The DataDirectory is used to resolve database file paths (directly supported by SQL CE and manually replaced for LocalDB)
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", tempHostingEnvironment?.MapPathContentRoot(Constants.SystemDirectories.Data));
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", webHostEnvironment?.MapPathContentRoot(Constants.SystemDirectories.Data));
|
||||
|
||||
// Manually create and register the HttpContextAccessor. In theory this should not be registered
|
||||
// again by the user but if that is the case it's not the end of the world since HttpContextAccessor
|
||||
@@ -114,19 +112,17 @@ namespace Umbraco.Extensions
|
||||
|
||||
IProfiler profiler = GetWebProfiler(config);
|
||||
|
||||
ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false));
|
||||
services.AddLogger(webHostEnvironment, config);
|
||||
|
||||
TypeLoader typeLoader = services.AddTypeLoader(
|
||||
Assembly.GetEntryAssembly(),
|
||||
tempHostingEnvironment,
|
||||
loggerFactory,
|
||||
appCaches,
|
||||
config,
|
||||
profiler);
|
||||
ILoggerFactory loggerFactory = new SerilogLoggerFactory();
|
||||
|
||||
TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), loggerFactory, config);
|
||||
|
||||
IHostingEnvironment tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config);
|
||||
return new UmbracoBuilder(services, config, typeLoader, loggerFactory, profiler, appCaches, tempHostingEnvironment);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds core Umbraco services
|
||||
/// </summary>
|
||||
@@ -142,7 +138,8 @@ namespace Umbraco.Extensions
|
||||
|
||||
// Add ASP.NET specific services
|
||||
builder.Services.AddUnique<IBackOfficeInfo, AspNetCoreBackOfficeInfo>();
|
||||
builder.Services.AddUnique<IHostingEnvironment, AspNetCoreHostingEnvironment>();
|
||||
builder.Services.AddUnique<IHostingEnvironment>(sp => ActivatorUtilities.CreateInstance<AspNetCoreHostingEnvironment>(sp, sp.GetRequiredService<IApplicationDiscriminator>()));
|
||||
|
||||
builder.Services.AddHostedService(factory => factory.GetRequiredService<IRuntime>());
|
||||
|
||||
builder.Services.AddSingleton<DatabaseSchemaCreatorFactory>();
|
||||
@@ -413,22 +410,15 @@ namespace Umbraco.Extensions
|
||||
private static IHostingEnvironment GetTemporaryHostingEnvironment(IWebHostEnvironment webHostEnvironment, IConfiguration config)
|
||||
{
|
||||
var hostingSettings = config.GetSection(Cms.Core.Constants.Configuration.ConfigHosting).Get<HostingSettings>() ?? new HostingSettings();
|
||||
var webRoutingSettings = config.GetSection(Cms.Core.Constants.Configuration.ConfigWebRouting).Get<WebRoutingSettings>() ?? new WebRoutingSettings();
|
||||
var wrappedHostingSettings = new OptionsMonitorAdapter<HostingSettings>(hostingSettings);
|
||||
|
||||
var webRoutingSettings = config.GetSection(Cms.Core.Constants.Configuration.ConfigWebRouting).Get<WebRoutingSettings>() ?? new WebRoutingSettings();
|
||||
var wrappedWebRoutingSettings = new OptionsMonitorAdapter<WebRoutingSettings>(webRoutingSettings);
|
||||
|
||||
// This is needed in order to create a unique Application Id
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddDataProtection();
|
||||
serviceCollection.AddSingleton<IHostEnvironment>(s => webHostEnvironment);
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
|
||||
return new AspNetCoreHostingEnvironment(
|
||||
serviceProvider,
|
||||
wrappedHostingSettings,
|
||||
wrappedWebRoutingSettings,
|
||||
webHostEnvironment);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.DataProtection.Infrastructure;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="IApplicationDiscriminator" /> interface.
|
||||
/// </summary>
|
||||
public static class ApplicationDiscriminatorExtensions
|
||||
{
|
||||
private static string s_applicationId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an application id which respects downstream customizations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Hashed to obscure any unintended infrastructure details e.g. the default value is ContentRootPath.
|
||||
/// </remarks>
|
||||
public static string GetApplicationId(this IApplicationDiscriminator applicationDiscriminator)
|
||||
{
|
||||
if (s_applicationId != null)
|
||||
{
|
||||
return s_applicationId;
|
||||
}
|
||||
|
||||
if (applicationDiscriminator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationDiscriminator));
|
||||
}
|
||||
|
||||
return s_applicationId = applicationDiscriminator.Discriminator.GenerateHash();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,28 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Hosting;
|
||||
using Serilog.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Extensions;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Logging.Serilog;
|
||||
using Umbraco.Cms.Web.Common.Hosting;
|
||||
using Umbraco.Cms.Infrastructure.Logging.Serilog;
|
||||
using Umbraco.Cms.Web.Common.Logging.Enrichers;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Web.Common.Logging;
|
||||
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
@@ -23,6 +31,7 @@ namespace Umbraco.Extensions
|
||||
/// <summary>
|
||||
/// Create and configure the logger
|
||||
/// </summary>
|
||||
[Obsolete("Use the extension method that takes an IHostEnvironment instance instead.")]
|
||||
public static IServiceCollection AddLogger(
|
||||
this IServiceCollection services,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
@@ -63,17 +72,103 @@ namespace Umbraco.Extensions
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create and configure the logger.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Additional Serilog services are registered during <see cref="HostBuilderExtensions.ConfigureUmbracoDefaults"/>.
|
||||
/// </remarks>
|
||||
public static IServiceCollection AddLogger(
|
||||
this IServiceCollection services,
|
||||
IHostEnvironment hostEnvironment,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
// TODO: WEBSITE_RUN_FROM_PACKAGE - can't assume this DIR is writable - we have an IConfiguration instance so a later refactor should be easy enough.
|
||||
var loggingDir = hostEnvironment.MapPathContentRoot(Constants.SystemDirectories.LogFiles);
|
||||
ILoggingConfiguration loggingConfig = new LoggingConfiguration(loggingDir);
|
||||
|
||||
var umbracoFileConfiguration = new UmbracoFileConfiguration(configuration);
|
||||
|
||||
services.TryAddSingleton(umbracoFileConfiguration);
|
||||
services.TryAddSingleton(loggingConfig);
|
||||
services.TryAddSingleton<Serilog.Core.ILogEventEnricher, ApplicationIdEnricher>();
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Bootstrap logger setup
|
||||
///////////////////////////////////////////////
|
||||
|
||||
LoggerConfiguration serilogConfig = new LoggerConfiguration()
|
||||
.MinimalConfiguration(hostEnvironment, loggingConfig, umbracoFileConfiguration)
|
||||
.ReadFrom.Configuration(configuration);
|
||||
|
||||
Log.Logger = serilogConfig.CreateBootstrapLogger();
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Runtime logger setup
|
||||
///////////////////////////////////////////////
|
||||
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
var logger = new RegisteredReloadableLogger(Log.Logger as ReloadableLogger);
|
||||
|
||||
logger.Reload(cfg =>
|
||||
{
|
||||
cfg.MinimalConfiguration(hostEnvironment, loggingConfig, umbracoFileConfiguration)
|
||||
.ReadFrom.Configuration(configuration)
|
||||
.ReadFrom.Services(sp);
|
||||
|
||||
return cfg;
|
||||
});
|
||||
|
||||
return logger;
|
||||
});
|
||||
|
||||
services.AddSingleton<Serilog.ILogger>(sp =>
|
||||
{
|
||||
ILogger logger = sp.GetRequiredService<RegisteredReloadableLogger>().Logger;
|
||||
return logger.ForContext(new NoopEnricher());
|
||||
});
|
||||
|
||||
services.AddSingleton<ILoggerFactory>(sp =>
|
||||
{
|
||||
ILogger logger = sp.GetRequiredService<RegisteredReloadableLogger>().Logger;
|
||||
return new SerilogLoggerFactory(logger, false);
|
||||
});
|
||||
|
||||
// Registered to provide two services...
|
||||
var diagnosticContext = new DiagnosticContext(Log.Logger);
|
||||
|
||||
// Consumed by e.g. middleware
|
||||
services.TryAddSingleton(diagnosticContext);
|
||||
|
||||
// Consumed by user code
|
||||
services.TryAddSingleton<IDiagnosticContext>(diagnosticContext);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to create the <see cref="TypeLoader"/> to assign to the <see cref="IUmbracoBuilder"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should never be called in a web project. It is used internally by Umbraco but could be used in unit tests.
|
||||
/// If called in a web project it will have no affect except to create and return a new TypeLoader but this will not
|
||||
/// be the instance in DI.
|
||||
/// </remarks>
|
||||
[Obsolete("Please use alternative extension method.")]
|
||||
public static TypeLoader AddTypeLoader(
|
||||
this IServiceCollection services,
|
||||
Assembly entryAssembly,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
ILoggerFactory loggerFactory,
|
||||
AppCaches appCaches,
|
||||
IConfiguration configuration,
|
||||
IProfiler profiler) =>
|
||||
services.AddTypeLoader(entryAssembly, loggerFactory, configuration);
|
||||
|
||||
/// <summary>
|
||||
/// Called to create the <see cref="TypeLoader"/> to assign to the <see cref="IUmbracoBuilder"/>
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="entryAssembly"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="loggerFactory"></param>
|
||||
/// <param name="appCaches"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="profiler"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This should never be called in a web project. It is used internally by Umbraco but could be used in unit tests.
|
||||
/// If called in a web project it will have no affect except to create and return a new TypeLoader but this will not
|
||||
@@ -81,12 +176,9 @@ namespace Umbraco.Extensions
|
||||
/// </remarks>
|
||||
public static TypeLoader AddTypeLoader(
|
||||
this IServiceCollection services,
|
||||
Assembly? entryAssembly,
|
||||
IHostingEnvironment? hostingEnvironment,
|
||||
Assembly entryAssembly,
|
||||
ILoggerFactory loggerFactory,
|
||||
AppCaches appCaches,
|
||||
IConfiguration configuration,
|
||||
IProfiler profiler)
|
||||
IConfiguration configuration)
|
||||
{
|
||||
TypeFinderSettings typeFinderSettings = configuration.GetSection(Cms.Core.Constants.Configuration.ConfigTypeFinder).Get<TypeFinderSettings>() ?? new TypeFinderSettings();
|
||||
|
||||
@@ -95,30 +187,14 @@ namespace Umbraco.Extensions
|
||||
loggerFactory,
|
||||
typeFinderSettings.AdditionalEntryAssemblies);
|
||||
|
||||
RuntimeHashPaths runtimeHashPaths = new RuntimeHashPaths().AddAssemblies(assemblyProvider);
|
||||
|
||||
var runtimeHash = new RuntimeHash(
|
||||
new ProfilingLogger(
|
||||
loggerFactory.CreateLogger<RuntimeHash>(),
|
||||
profiler),
|
||||
runtimeHashPaths);
|
||||
|
||||
var typeFinderConfig = new TypeFinderConfig(Options.Create(typeFinderSettings));
|
||||
|
||||
var typeFinder = new TypeFinder(
|
||||
loggerFactory.CreateLogger<TypeFinder>(),
|
||||
assemblyProvider,
|
||||
typeFinderConfig
|
||||
);
|
||||
typeFinderConfig);
|
||||
|
||||
var typeLoader = new TypeLoader(
|
||||
typeFinder,
|
||||
runtimeHash,
|
||||
appCaches.RuntimeCache,
|
||||
new DirectoryInfo(hostingEnvironment?.LocalTempPath ?? string.Empty),
|
||||
loggerFactory.CreateLogger<TypeLoader>(),
|
||||
profiler
|
||||
);
|
||||
var typeLoader = new TypeLoader(typeFinder, loggerFactory.CreateLogger<TypeLoader>());
|
||||
|
||||
// This will add it ONCE and not again which is what we want since we don't actually want people to call this method
|
||||
// in the web project.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains extension methods for the <see cref="IWebHostEnvironment" /> interface.
|
||||
/// </summary>
|
||||
public static class WebHostEnvironmentExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps a virtual path to a physical path to the application's web root
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Depending on the runtime 'web root', this result can vary. For example in Net Framework the web root and the content root are the same, however
|
||||
/// in netcore the web root is /wwwroot therefore this will Map to a physical path within wwwroot.
|
||||
/// </remarks>
|
||||
public static string MapPathWebRoot(this IWebHostEnvironment webHostEnvironment, string path)
|
||||
{
|
||||
var root = webHostEnvironment.WebRootPath;
|
||||
|
||||
var newPath = path.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
|
||||
|
||||
// TODO: This is a temporary error because we switched from IOHelper.MapPath to HostingEnvironment.MapPathXXX
|
||||
// IOHelper would check if the path passed in started with the root, and not prepend the root again if it did,
|
||||
// however if you are requesting a path be mapped, it should always assume the path is relative to the root, not
|
||||
// absolute in the file system. This error will help us find and fix improper uses, and should be removed once
|
||||
// all those uses have been found and fixed
|
||||
if (newPath.StartsWith(root))
|
||||
{
|
||||
throw new ArgumentException("The path appears to already be fully qualified. Please remove the call to MapPathWebRoot");
|
||||
}
|
||||
|
||||
return Path.Combine(root, newPath.TrimStart(Core.Constants.CharArrays.TildeForwardSlashBackSlash));
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/Umbraco.Web.Common/Hosting/HostBuilderExtensions.cs
Normal file
34
src/Umbraco.Web.Common/Hosting/HostBuilderExtensions.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Web.Common.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Umbraco specific extensions for the <see cref="IHostBuilder"/> interface.
|
||||
/// </summary>
|
||||
public static class HostBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures an existing <see cref="IHostBuilder"/> with defaults for an Umbraco application.
|
||||
/// </summary>
|
||||
public static IHostBuilder ConfigureUmbracoDefaults(this IHostBuilder builder)
|
||||
{
|
||||
#if DEBUG
|
||||
builder.ConfigureAppConfiguration(config
|
||||
=> config.AddJsonFile(
|
||||
"appsettings.Local.json",
|
||||
optional: true,
|
||||
reloadOnChange: true));
|
||||
|
||||
#endif
|
||||
builder.ConfigureLogging(x => x.ClearProviders());
|
||||
|
||||
return new UmbracoHostBuilderDecorator(builder, OnHostBuilt);
|
||||
}
|
||||
|
||||
// Runs before any IHostedService starts (including generic web host).
|
||||
private static void OnHostBuilt(IHost host) =>
|
||||
StaticServiceProvider.Instance = host.Services;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Hosting;
|
||||
|
||||
internal class UmbracoHostBuilderDecorator : IHostBuilder
|
||||
{
|
||||
private readonly IHostBuilder _inner;
|
||||
private readonly Action<IHost> _onBuild;
|
||||
|
||||
public UmbracoHostBuilderDecorator(IHostBuilder inner, Action<IHost> onBuild = null)
|
||||
{
|
||||
_inner = inner;
|
||||
_onBuild = onBuild;
|
||||
}
|
||||
|
||||
public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate) =>
|
||||
_inner.ConfigureAppConfiguration(configureDelegate);
|
||||
|
||||
public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate) =>
|
||||
_inner.ConfigureContainer(configureDelegate);
|
||||
|
||||
public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate) =>
|
||||
_inner.ConfigureHostConfiguration(configureDelegate);
|
||||
|
||||
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate) =>
|
||||
_inner.ConfigureServices(configureDelegate);
|
||||
|
||||
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory) =>
|
||||
_inner.UseServiceProviderFactory(factory);
|
||||
|
||||
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory) =>
|
||||
_inner.UseServiceProviderFactory(factory);
|
||||
|
||||
public IDictionary<object, object> Properties => _inner.Properties;
|
||||
|
||||
public IHost Build()
|
||||
{
|
||||
IHost host = _inner.Build();
|
||||
|
||||
_onBuild?.Invoke(host);
|
||||
|
||||
return host;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.AspNetCore.DataProtection.Infrastructure;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Umbraco.Cms.Web.Common.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Logging.Enrichers;
|
||||
|
||||
internal class ApplicationIdEnricher : ILogEventEnricher
|
||||
{
|
||||
private readonly IApplicationDiscriminator _applicationDiscriminator;
|
||||
public const string ApplicationIdProperty = "ApplicationId";
|
||||
|
||||
public ApplicationIdEnricher(IApplicationDiscriminator applicationDiscriminator) =>
|
||||
_applicationDiscriminator = applicationDiscriminator;
|
||||
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) =>
|
||||
logEvent.AddOrUpdateProperty(propertyFactory.CreateProperty(ApplicationIdProperty, _applicationDiscriminator.GetApplicationId()));
|
||||
}
|
||||
14
src/Umbraco.Web.Common/Logging/Enrichers/NoopEnricher.cs
Normal file
14
src/Umbraco.Web.Common/Logging/Enrichers/NoopEnricher.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Logging.Enrichers;
|
||||
|
||||
/// <summary>
|
||||
/// NoOp but useful for tricks to avoid disposal of the global logger.
|
||||
/// </summary>
|
||||
internal class NoopEnricher : ILogEventEnricher
|
||||
{
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
40
src/Umbraco.Web.Common/Logging/RegisteredReloadableLogger.cs
Normal file
40
src/Umbraco.Web.Common/Logging/RegisteredReloadableLogger.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using Serilog;
|
||||
using Serilog.Extensions.Hosting;
|
||||
|
||||
namespace Umbraco.Cms.Web.Common.Logging;
|
||||
|
||||
/// <remarks>
|
||||
/// HACK:
|
||||
/// Ensures freeze is only called a single time even when resolving a logger from the snapshot container
|
||||
/// built for <see cref="ModelsBuilder.RefreshingRazorViewEngine"/>.
|
||||
/// </remarks>
|
||||
internal class RegisteredReloadableLogger
|
||||
{
|
||||
private static bool s_frozen;
|
||||
private static object s_frozenLock = new();
|
||||
private readonly ReloadableLogger _logger;
|
||||
|
||||
public RegisteredReloadableLogger(ReloadableLogger logger) =>
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
public ILogger Logger => _logger;
|
||||
|
||||
public void Reload(Func<LoggerConfiguration, LoggerConfiguration> cfg)
|
||||
{
|
||||
lock (s_frozenLock)
|
||||
{
|
||||
if (s_frozen)
|
||||
{
|
||||
Logger.Debug("ReloadableLogger has already been frozen, unable to reload, NOOP.");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Reload(cfg);
|
||||
_logger.Freeze();
|
||||
|
||||
s_frozen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Web.Common.Hosting;
|
||||
|
||||
namespace Umbraco.Cms.Web.UI
|
||||
{
|
||||
@@ -12,12 +11,9 @@ namespace Umbraco.Cms.Web.UI
|
||||
.Build()
|
||||
.Run();
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args)
|
||||
=> Host.CreateDefaultBuilder(args)
|
||||
#if DEBUG
|
||||
.ConfigureAppConfiguration(config => config.AddJsonFile("appsettings.Local.json", optional: true, reloadOnChange: true))
|
||||
#endif
|
||||
.ConfigureLogging(x => x.ClearProviders())
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureUmbracoDefaults()
|
||||
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,28 +23,8 @@ namespace Umbraco.Tests.Benchmarks
|
||||
new NullLogger<TypeFinder>(),
|
||||
new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance));
|
||||
|
||||
var cache = new ObjectCacheAppCache();
|
||||
_typeLoader1 = new TypeLoader(
|
||||
typeFinder1,
|
||||
new VaryingRuntimeHash(),
|
||||
cache,
|
||||
null,
|
||||
new NullLogger<TypeLoader>(),
|
||||
new NoopProfiler());
|
||||
|
||||
// populate the cache
|
||||
cache.Insert(
|
||||
_typeLoader1.CacheKey,
|
||||
GetCache,
|
||||
TimeSpan.FromDays(1));
|
||||
|
||||
_typeLoader2 = new TypeLoader(
|
||||
typeFinder1,
|
||||
new VaryingRuntimeHash(),
|
||||
NoAppCache.Instance,
|
||||
null,
|
||||
new NullLogger<TypeLoader>(),
|
||||
new NoopProfiler());
|
||||
_typeLoader1 = new TypeLoader(typeFinder1, NullLogger<TypeLoader>.Instance);
|
||||
_typeLoader2 = new TypeLoader(typeFinder1, NullLogger<TypeLoader>.Instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Umbraco.Cms.Tests.Common.Testing
|
||||
IOptionsMonitor<HostingSettings> hostingSettings,
|
||||
IOptionsMonitor<WebRoutingSettings> webRoutingSettings,
|
||||
IWebHostEnvironment webHostEnvironment)
|
||||
: base(null, hostingSettings, webRoutingSettings, webHostEnvironment)
|
||||
: base(hostingSettings, webRoutingSettings, webHostEnvironment)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ using Umbraco.Cms.Tests.Integration.DependencyInjection;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
using Umbraco.Cms.Web.BackOffice.Controllers;
|
||||
using Umbraco.Cms.Web.Common.Controllers;
|
||||
using Umbraco.Cms.Web.Common.Hosting;
|
||||
using Umbraco.Cms.Web.Website.Controllers;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -136,6 +137,7 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
|
||||
private IHostBuilder CreateHostBuilder()
|
||||
{
|
||||
IHostBuilder hostBuilder = Host.CreateDefaultBuilder()
|
||||
.ConfigureUmbracoDefaults()
|
||||
.ConfigureAppConfiguration((context, configBuilder) =>
|
||||
{
|
||||
context.HostingEnvironment = TestHelper.GetWebHostEnvironment();
|
||||
@@ -197,6 +199,8 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest
|
||||
Configuration,
|
||||
TestHelper.Profiler);
|
||||
|
||||
services.AddLogger(TestHelper.GetWebHostEnvironment(), Configuration);
|
||||
|
||||
var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory, TestHelper.Profiler, AppCaches.NoCache, hostingEnvironment);
|
||||
|
||||
builder
|
||||
|
||||
@@ -25,6 +25,7 @@ using Umbraco.Cms.Persistence.SqlServer;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
using Umbraco.Cms.Tests.Integration.DependencyInjection;
|
||||
using Umbraco.Cms.Tests.Integration.Extensions;
|
||||
using Umbraco.Cms.Web.Common.Hosting;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Testing
|
||||
@@ -66,6 +67,7 @@ namespace Umbraco.Cms.Tests.Integration.Testing
|
||||
private IHostBuilder CreateHostBuilder()
|
||||
{
|
||||
IHostBuilder hostBuilder = Host.CreateDefaultBuilder()
|
||||
.ConfigureUmbracoDefaults()
|
||||
|
||||
// IMPORTANT: We Cannot use UseStartup, there's all sorts of threads about this with testing. Although this can work
|
||||
// if you want to setup your tests this way, it is a bit annoying to do that as the WebApplicationFactory will
|
||||
@@ -106,6 +108,8 @@ namespace Umbraco.Cms.Tests.Integration.Testing
|
||||
// We register this service because we need it for IRuntimeState, if we don't this breaks 900 tests
|
||||
services.AddSingleton<IConflictingRouteService, TestConflictingRouteService>();
|
||||
|
||||
services.AddLogger(webHostEnvironment, Configuration);
|
||||
|
||||
// Add it!
|
||||
Core.Hosting.IHostingEnvironment hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
TypeLoader typeLoader = services.AddTypeLoader(
|
||||
@@ -117,8 +121,6 @@ namespace Umbraco.Cms.Tests.Integration.Testing
|
||||
TestHelper.Profiler);
|
||||
var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory, TestHelper.Profiler, AppCaches.NoCache, hostingEnvironment);
|
||||
|
||||
builder.Services.AddLogger(hostingEnvironment, TestHelper.GetLoggingConfiguration(), Configuration);
|
||||
|
||||
builder.AddConfiguration()
|
||||
.AddUmbracoCore()
|
||||
.AddWebComponents()
|
||||
|
||||
@@ -115,118 +115,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Composing
|
||||
//// Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds);
|
||||
////}
|
||||
|
||||
//////NOTE: This test shows that Type.GetType is 100% faster than Assembly.Load(..).GetType(...) so we'll use that :)
|
||||
////[Test]
|
||||
////public void Load_Type_Benchmark()
|
||||
////{
|
||||
//// var watch = new Stopwatch();
|
||||
//// watch.Start();
|
||||
//// for (var i = 0; i < 1000; i++)
|
||||
//// {
|
||||
//// var type2 = Type.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
//// var type3 = Type.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
//// var type4 = Type.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
//// var type5 = Type.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null");
|
||||
//// }
|
||||
//// watch.Stop();
|
||||
//// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
|
||||
//// watch.Reset();
|
||||
//// watch.Start();
|
||||
//// for (var i = 0; i < 1000; i++)
|
||||
//// {
|
||||
//// var type2 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
|
||||
//// .GetType("umbraco.macroCacheRefresh");
|
||||
//// var type3 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
|
||||
//// .GetType("umbraco.templateCacheRefresh");
|
||||
//// var type4 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
|
||||
//// .GetType("umbraco.presentation.cache.MediaLibraryRefreshers");
|
||||
//// var type5 = Assembly.Load("umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null")
|
||||
//// .GetType("umbraco.presentation.cache.pageRefresher");
|
||||
//// }
|
||||
//// watch.Stop();
|
||||
//// Debug.WriteLine("TOTAL TIME (2nd round): " + watch.ElapsedMilliseconds);
|
||||
//// watch.Reset();
|
||||
//// watch.Start();
|
||||
//// for (var i = 0; i < 1000; i++)
|
||||
//// {
|
||||
//// var type2 = BuildManager.GetType("umbraco.macroCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
//// var type3 = BuildManager.GetType("umbraco.templateCacheRefresh, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
//// var type4 = BuildManager.GetType("umbraco.presentation.cache.MediaLibraryRefreshers, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
//// var type5 = BuildManager.GetType("umbraco.presentation.cache.pageRefresher, umbraco, Version=1.0.4698.259, Culture=neutral, PublicKeyToken=null", true);
|
||||
//// }
|
||||
//// watch.Stop();
|
||||
//// Debug.WriteLine("TOTAL TIME (1st round): " + watch.ElapsedMilliseconds);
|
||||
////}
|
||||
|
||||
[Test]
|
||||
[Retry(5)] // TODO make this test non-flaky.
|
||||
public void Detect_Legacy_Plugin_File_List()
|
||||
{
|
||||
string filePath = _typeLoader.GetTypesListFilePath();
|
||||
string fileDir = Path.GetDirectoryName(filePath);
|
||||
Directory.CreateDirectory(fileDir);
|
||||
|
||||
File.WriteAllText(filePath, @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<plugins>
|
||||
<baseType type=""umbraco.interfaces.ICacheRefresher"">
|
||||
<add type=""umbraco.macroCacheRefresh, umbraco, Version=6.0.0.0, Culture=neutral, PublicKeyToken=null"" />
|
||||
</baseType>
|
||||
</plugins>");
|
||||
|
||||
Assert.IsEmpty(_typeLoader.ReadCache()); // uber-legacy cannot be read
|
||||
|
||||
File.Delete(filePath);
|
||||
|
||||
File.WriteAllText(filePath, @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<plugins>
|
||||
<baseType type=""umbraco.interfaces.ICacheRefresher"" resolutionType=""FindAllTypes"">
|
||||
<add type=""umbraco.macroCacheRefresh, umbraco, Version=6.0.0.0, Culture=neutral, PublicKeyToken=null"" />
|
||||
</baseType>
|
||||
</plugins>");
|
||||
|
||||
Assert.IsEmpty(_typeLoader.ReadCache()); // legacy cannot be read
|
||||
|
||||
File.Delete(filePath);
|
||||
|
||||
File.WriteAllText(filePath, @"IContentFinder
|
||||
|
||||
MyContentFinder
|
||||
AnotherContentFinder
|
||||
|
||||
");
|
||||
|
||||
Assert.IsNotNull(_typeLoader.ReadCache()); // works
|
||||
}
|
||||
|
||||
[Retry(5)] // TODO make this test non-flaky.
|
||||
[Test]
|
||||
public void Create_Cached_Plugin_File()
|
||||
{
|
||||
Type[] types = new[] { typeof(TypeLoader), typeof(TypeLoaderTests), typeof(IUmbracoContext) };
|
||||
|
||||
var typeList1 = new TypeLoader.TypeList(typeof(object), null);
|
||||
foreach (Type type in types)
|
||||
{
|
||||
typeList1.Add(type);
|
||||
}
|
||||
|
||||
_typeLoader.AddTypeList(typeList1);
|
||||
_typeLoader.WriteCache();
|
||||
|
||||
Attempt<IEnumerable<string>> plugins = _typeLoader.TryGetCached(typeof(object), null);
|
||||
Attempt<IEnumerable<string>> diffType = _typeLoader.TryGetCached(typeof(object), typeof(ObsoleteAttribute));
|
||||
|
||||
Assert.IsTrue(plugins.Success);
|
||||
|
||||
// This will be false since there is no cache of that type resolution kind
|
||||
Assert.IsFalse(diffType.Success);
|
||||
|
||||
Assert.AreEqual(3, plugins.Result.Count());
|
||||
IEnumerable<string> shouldContain = types.Select(x => x.AssemblyQualifiedName);
|
||||
|
||||
// Ensure they are all found
|
||||
Assert.IsTrue(plugins.Result.ContainsAll(shouldContain));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Plugins_Hash_With_Hash_Generator()
|
||||
|
||||
Reference in New Issue
Block a user