diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index 60aa666034..56d798ea0c 100644 --- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using Microsoft.Extensions.Logging; @@ -16,11 +17,17 @@ namespace Umbraco.Cms.Core.Composing { private readonly Assembly _entryPointAssembly; private readonly ILoggerFactory _loggerFactory; + private readonly IEnumerable _additionalTargetAssemblies; + private List _discovered; - public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly, ILoggerFactory loggerFactory) + public DefaultUmbracoAssemblyProvider( + Assembly entryPointAssembly, + ILoggerFactory loggerFactory, + IEnumerable additionalTargetAssemblies = null) { _entryPointAssembly = entryPointAssembly ?? throw new ArgumentNullException(nameof(entryPointAssembly)); _loggerFactory = loggerFactory; + _additionalTargetAssemblies = additionalTargetAssemblies; } // TODO: It would be worth investigating a netcore3 version of this which would use @@ -33,8 +40,21 @@ namespace Umbraco.Cms.Core.Composing { get { - var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, Constants.Composing.UmbracoCoreAssemblyNames, true, _loggerFactory); - return finder.Find(); + if (_discovered != null) + { + return _discovered; + } + + IEnumerable additionalTargetAssemblies = Constants.Composing.UmbracoCoreAssemblyNames; + if (_additionalTargetAssemblies != null) + { + additionalTargetAssemblies = additionalTargetAssemblies.Concat(_additionalTargetAssemblies); + } + + var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, additionalTargetAssemblies.ToArray(), true, _loggerFactory); + _discovered = finder.Find().ToList(); + + return _discovered; } } } diff --git a/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs index 5b554a5321..78cdb80f58 100644 --- a/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs +++ b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -18,6 +18,7 @@ namespace Umbraco.Cms.Core.Composing private readonly string[] _targetAssemblies; private readonly bool _includeTargets; private readonly ILoggerFactory _loggerFactory; + private readonly ILogger _logger; /// /// Constructor @@ -32,6 +33,7 @@ namespace Umbraco.Cms.Core.Composing _targetAssemblies = targetAssemblyNames; _includeTargets = includeTargets; _loggerFactory = loggerFactory; + _logger = _loggerFactory.CreateLogger(); } public IEnumerable Find() @@ -50,9 +52,10 @@ namespace Umbraco.Cms.Core.Composing { referenceItems.Add(Assembly.Load(target)); } - catch (FileNotFoundException) + catch (FileNotFoundException ex) { // occurs if we cannot load this ... for example in a test project where we aren't currently referencing Umbraco.Web, etc... + _logger.LogDebug(ex, "Could not load assembly " + target); } } } diff --git a/src/Umbraco.Core/Composing/ITypeFinder.cs b/src/Umbraco.Core/Composing/ITypeFinder.cs index 2cf6ca23f7..2522c2f593 100644 --- a/src/Umbraco.Core/Composing/ITypeFinder.cs +++ b/src/Umbraco.Core/Composing/ITypeFinder.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Reflection; @@ -51,14 +51,5 @@ namespace Umbraco.Cms.Core.Composing Type attributeType, IEnumerable assemblies, bool onlyConcreteClasses); - - /// - /// Gets a hash value of the current runtime - /// - /// - /// This is used to detect if the runtime itself has changed, like a DLL has changed or another dynamically compiled - /// part of the application has changed. This is used to detect if we need to re-type scan. - /// - string GetRuntimeHash(); } } diff --git a/src/Umbraco.Core/Composing/RuntimeHash.cs b/src/Umbraco.Core/Composing/RuntimeHash.cs index 4eb70cea1f..7ca35855de 100644 --- a/src/Umbraco.Core/Composing/RuntimeHash.cs +++ b/src/Umbraco.Core/Composing/RuntimeHash.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Cryptography; using Umbraco.Cms.Core.Logging; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.Composing { @@ -12,6 +14,7 @@ namespace Umbraco.Cms.Core.Composing { private readonly IProfilingLogger _logger; private readonly RuntimeHashPaths _paths; + private string _calculated; public RuntimeHash(IProfilingLogger logger, RuntimeHashPaths paths) { @@ -22,13 +25,18 @@ namespace Umbraco.Cms.Core.Composing public string GetHashValue() { - var allPaths = _paths.GetFolders() - .Select(x => ((FileSystemInfo) x, false)) - .Concat(_paths.GetFiles().Select(x => ((FileSystemInfo) x.Key, x.Value))); + if (_calculated != null) + { + return _calculated; + } - var hash = GetFileHash(allPaths); + IEnumerable<(FileSystemInfo, bool)> allPaths = _paths.GetFolders() + .Select(x => ((FileSystemInfo)x, false)) + .Concat(_paths.GetFiles().Select(x => ((FileSystemInfo)x.Key, x.Value))); - return hash; + _calculated = GetFileHash(allPaths); + + return _calculated; } /// @@ -48,7 +56,7 @@ namespace Umbraco.Cms.Core.Composing using var generator = new HashGenerator(); - foreach (var (fileOrFolder, scanFileContent) in filesAndFolders) + foreach ((FileSystemInfo fileOrFolder, bool scanFileContent) in filesAndFolders) { if (scanFileContent) { @@ -56,9 +64,16 @@ namespace Umbraco.Cms.Core.Composing // normalize the content for cr/lf and case-sensitivity if (uniqContent.Add(fileOrFolder.FullName)) { - if (File.Exists(fileOrFolder.FullName) == false) continue; - var content = RemoveCrLf(File.ReadAllText(fileOrFolder.FullName)); - generator.AddCaseInsensitiveString(content); + if (File.Exists(fileOrFolder.FullName) == false) + { + continue; + } + + using (FileStream fileStream = File.OpenRead(fileOrFolder.FullName)) + { + var hash = fileStream.GetStreamHash(); + generator.AddCaseInsensitiveString(hash); + } } } else @@ -74,18 +89,5 @@ namespace Umbraco.Cms.Core.Composing } } - // fast! (yes, according to benchmarks) - private static string RemoveCrLf(string s) - { - var buffer = new char[s.Length]; - var count = 0; - // ReSharper disable once ForCanBeConvertedToForeach - no! - for (var i = 0; i < s.Length; i++) - { - if (s[i] != '\r' && s[i] != '\n') - buffer[count++] = s[i]; - } - return new string(buffer, 0, count); - } } } diff --git a/src/Umbraco.Core/Composing/RuntimeHashPaths.cs b/src/Umbraco.Core/Composing/RuntimeHashPaths.cs index 12a878ee94..eac2f83bcd 100644 --- a/src/Umbraco.Core/Composing/RuntimeHashPaths.cs +++ b/src/Umbraco.Core/Composing/RuntimeHashPaths.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; +using System.Reflection; namespace Umbraco.Cms.Core.Composing { @@ -17,6 +18,24 @@ namespace Umbraco.Cms.Core.Composing return this; } + /// + /// Creates a runtime hash based on the assembly provider + /// + /// + /// + public RuntimeHashPaths AddAssemblies(IAssemblyProvider assemblyProvider) + { + foreach (Assembly assembly in assemblyProvider.Assemblies) + { + // TODO: We need to test this on a published website + if (!assembly.IsDynamic && assembly.Location != null) + { + AddFile(new FileInfo(assembly.Location)); + } + } + return this; + } + public void AddFile(FileInfo fileInfo, bool scanFileContent = false) => _files.Add(fileInfo, scanFileContent); public IEnumerable GetFolders() => _paths; diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 1979f33d10..4299328b5d 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -17,23 +17,21 @@ namespace Umbraco.Cms.Core.Composing { private readonly ILogger _logger; private readonly IAssemblyProvider _assemblyProvider; - private readonly IRuntimeHash _runtimeHash; private volatile HashSet _localFilteredAssemblyCache; private readonly object _localFilteredAssemblyCacheLocker = new object(); private readonly List _notifiedLoadExceptionAssemblies = new List(); - private static readonly ConcurrentDictionary TypeNamesCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary s_typeNamesCache = new ConcurrentDictionary(); private readonly ITypeFinderConfig _typeFinderConfig; // used for benchmark tests internal bool QueryWithReferencingAssemblies { get; set; } = true; - public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, IRuntimeHash runtimeHash, ITypeFinderConfig typeFinderConfig = null) + public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig typeFinderConfig = null) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _assemblyProvider = assemblyProvider; - _runtimeHash = runtimeHash; _typeFinderConfig = typeFinderConfig; - } + } private string[] _assembliesAcceptingLoadExceptions = null; @@ -64,9 +62,12 @@ namespace Umbraco.Cms.Core.Composing var name = a.GetName().Name; // simple name of the assembly return AssembliesAcceptingLoadExceptions.Any(pattern => { - if (pattern.Length > name.Length) return false; // pattern longer than name - if (pattern.Length == name.Length) return pattern.InvariantEquals(name); // same length, must be identical - if (pattern[pattern.Length] != '.') return false; // pattern is shorter than name, must end with dot + if (pattern.Length > name.Length) + return false; // pattern longer than name + if (pattern.Length == name.Length) + return pattern.InvariantEquals(name); // same length, must be identical + if (pattern[pattern.Length] != '.') + return false; // pattern is shorter than name, must end with dot return name.StartsWith(pattern); // and name must start with pattern }); } @@ -112,6 +113,8 @@ namespace Umbraco.Cms.Core.Composing && exclusionFilter.Any(f => x.FullName.StartsWith(f)) == false); } + // TODO: Kill this + /// /// this is our assembly filter to filter out known types that def don't contain types we'd like to find or plugins /// @@ -232,9 +235,6 @@ namespace Umbraco.Cms.Core.Composing return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses); } - /// - public string GetRuntimeHash() => _runtimeHash.GetHashValue(); - /// /// Returns a Type for the string type name /// @@ -255,7 +255,7 @@ namespace Umbraco.Cms.Core.Composing } // It didn't parse, so try loading from each already loaded assembly and cache it - return TypeNamesCache.GetOrAdd(name, s => + return s_typeNamesCache.GetOrAdd(name, s => AppDomain.CurrentDomain.GetAssemblies() .Select(x => x.GetType(s)) .FirstOrDefault(x => x != null)); @@ -455,7 +455,8 @@ namespace Umbraco.Cms.Core.Composing var ex = new ReflectionTypeLoadException(rex.Types, rex.LoaderExceptions, sb.ToString()); // rethrow with new message, unless accepted - if (AcceptsLoadExceptions(a) == false) throw ex; + if (AcceptsLoadExceptions(a) == false) + throw ex; // log a warning, and return what we can lock (_notifiedLoadExceptionAssemblies) diff --git a/src/Umbraco.Core/Composing/TypeFinderConfig.cs b/src/Umbraco.Core/Composing/TypeFinderConfig.cs index 7940773231..8e63958a06 100644 --- a/src/Umbraco.Core/Composing/TypeFinderConfig.cs +++ b/src/Umbraco.Core/Composing/TypeFinderConfig.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Configuration.UmbracoSettings; @@ -15,17 +16,16 @@ namespace Umbraco.Cms.Core.Composing private readonly TypeFinderSettings _settings; private IEnumerable _assembliesAcceptingLoadExceptions; - public TypeFinderConfig(IOptions settings) - { - _settings = settings.Value; - } + public TypeFinderConfig(IOptions settings) => _settings = settings.Value; public IEnumerable AssembliesAcceptingLoadExceptions { get { if (_assembliesAcceptingLoadExceptions != null) + { return _assembliesAcceptingLoadExceptions; + } var s = _settings.AssembliesAcceptingLoadExceptions; return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 39b70b831b..6af8cb5ced 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; using System.Threading; @@ -26,9 +25,8 @@ namespace Umbraco.Cms.Core.Composing /// on a hash of the DLLs in the ~/bin folder to check for cache expiration. /// public sealed class TypeLoader - { - internal const string CacheKey = "umbraco-types.list"; - + { + private readonly IRuntimeHash _runtimeHash; private readonly IAppPolicyCache _runtimeCache; private readonly ILogger _logger; private readonly IProfilingLogger _profilingLogger; @@ -45,7 +43,9 @@ namespace Umbraco.Cms.Core.Composing private bool _reportedChange; private readonly DirectoryInfo _localTempPath; private readonly Lazy _fileBasePath; - private readonly Dictionary<(string, string), IEnumerable> EmptyCache = new Dictionary<(string, string), IEnumerable>(); + private readonly Dictionary<(string, string), IEnumerable> _emptyCache = new Dictionary<(string, string), IEnumerable>(); + private string _typesListFilePath; + private string _typesHashFilePath; /// /// Initializes a new instance of the class. @@ -55,8 +55,8 @@ namespace Umbraco.Cms.Core.Composing /// Files storage location. /// A profiling logger. /// - public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, ILogger logger, IProfilingLogger profilingLogger, IEnumerable assembliesToScan = null) - : this(typeFinder, runtimeCache, localTempPath, logger, profilingLogger, true, assembliesToScan) + public TypeLoader(ITypeFinder typeFinder, IRuntimeHash runtimeHash, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, ILogger logger, IProfiler profiler, IEnumerable assembliesToScan = null) + : this(typeFinder, runtimeHash, runtimeCache, localTempPath, logger, profiler, true, assembliesToScan) { } /// @@ -68,13 +68,22 @@ namespace Umbraco.Cms.Core.Composing /// A profiling logger. /// Whether to detect changes using hashes. /// - public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, ILogger logger, IProfilingLogger profilingLogger, bool detectChanges, IEnumerable assembliesToScan = null) + public TypeLoader(ITypeFinder typeFinder, IRuntimeHash runtimeHash, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, ILogger logger, IProfiler profiler, bool detectChanges, IEnumerable assembliesToScan = null) { + 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 = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger)); + _profilingLogger = new ProfilingLogger(logger, profiler); _assemblies = assembliesToScan; _fileBasePath = new Lazy(GetFileBasePath); @@ -87,6 +96,8 @@ namespace Umbraco.Cms.Core.Composing //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 @@ -115,6 +126,8 @@ namespace Umbraco.Cms.Core.Composing } } + internal string CacheKey { get; } + /// /// Returns the underlying /// @@ -198,9 +211,11 @@ namespace Umbraco.Cms.Core.Composing get { if (_currentAssembliesHash != null) + { return _currentAssembliesHash; + } - _currentAssembliesHash = TypeFinder.GetRuntimeHash(); + _currentAssembliesHash = _runtimeHash.GetHashValue(); return _currentAssembliesHash; } @@ -264,7 +279,7 @@ namespace Umbraco.Cms.Core.Composing } } - return EmptyCache; + return _emptyCache; } // internal for tests @@ -273,7 +288,7 @@ namespace Umbraco.Cms.Core.Composing var typesListFilePath = GetTypesListFilePath(); if (typesListFilePath == null || File.Exists(typesListFilePath) == false) { - return EmptyCache; + return _emptyCache; } var cache = new Dictionary<(string, string), IEnumerable>(); @@ -328,9 +343,9 @@ namespace Umbraco.Cms.Core.Composing } // internal for tests - public string GetTypesListFilePath() => _fileBasePath.Value == null ? null : _fileBasePath.Value + ".list"; + public string GetTypesListFilePath() => _typesListFilePath ??= _fileBasePath.Value == null ? null : _fileBasePath.Value + ".list"; - private string GetTypesHashFilePath() => _fileBasePath.Value == null ? null : _fileBasePath.Value + ".hash"; + private string GetTypesHashFilePath() => _typesHashFilePath ??= _fileBasePath.Value == null ? null : _fileBasePath.Value + ".hash"; /// /// Used to produce the Lazy value of _fileBasePath diff --git a/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs b/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs index d942f47b4f..e41921de8c 100644 --- a/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/TypeFinderSettings.cs @@ -1,6 +1,9 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Collections; +using System.Collections.Generic; + namespace Umbraco.Cms.Core.Configuration.Models { /// @@ -13,5 +16,11 @@ namespace Umbraco.Cms.Core.Configuration.Models /// Gets or sets a value for the assemblies that accept load exceptions during type finder operations. /// public string AssembliesAcceptingLoadExceptions { get; set; } + + /// + /// By default the entry assemblies for scanning plugin types is the Umbraco DLLs. If you require + /// scanning for plugins based on different root referenced assemblies you can add the assembly name to this list. + /// + public IEnumerable AdditionalEntryAssemblies { get; set; } } } diff --git a/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs index 0b65c3443c..738cfed26e 100644 --- a/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/IUmbracoBuilder.cs @@ -1,7 +1,10 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Logging; namespace Umbraco.Cms.Core.DependencyInjection { @@ -10,7 +13,24 @@ namespace Umbraco.Cms.Core.DependencyInjection IServiceCollection Services { get; } IConfiguration Config { get; } TypeLoader TypeLoader { get; } + + /// + /// A Logger factory created specifically for the . This is NOT the same + /// instance that will be resolved from DI. Use only if required during configuration. + /// ILoggerFactory BuilderLoggerFactory { get; } + + /// + /// A hosting environment created specifically for the . This is NOT the same + /// instance that will be resolved from DI. Use only if required during configuration. + /// + /// + /// This may be null. + /// + IHostingEnvironment BuilderHostingEnvironment { get; } + + IProfiler Profiler { get; } + AppCaches AppCaches { get; } TBuilder WithCollectionBuilder() where TBuilder : ICollectionBuilder, new(); void Build(); } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 5dd2499624..648af8f082 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -52,23 +52,41 @@ namespace Umbraco.Cms.Core.DependencyInjection public TypeLoader TypeLoader { get; } + /// public ILoggerFactory BuilderLoggerFactory { get; } + /// + public IHostingEnvironment BuilderHostingEnvironment { get; } + + public IProfiler Profiler { get; } + + public AppCaches AppCaches { get; } + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class primarily for testing. /// public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader) - : this(services, config, typeLoader, NullLoggerFactory.Instance) + : this(services, config, typeLoader, NullLoggerFactory.Instance, new NoopProfiler(), AppCaches.Disabled, null) { } /// /// Initializes a new instance of the class. /// - public UmbracoBuilder(IServiceCollection services, IConfiguration config, TypeLoader typeLoader, ILoggerFactory loggerFactory) + public UmbracoBuilder( + IServiceCollection services, + IConfiguration config, + TypeLoader typeLoader, + ILoggerFactory loggerFactory, + IProfiler profiler, + AppCaches appCaches, + IHostingEnvironment hostingEnvironment) { Services = services; Config = config; BuilderLoggerFactory = loggerFactory; + BuilderHostingEnvironment = hostingEnvironment; + Profiler = profiler; + AppCaches = appCaches; TypeLoader = typeLoader; AddCoreServices(); @@ -106,6 +124,9 @@ namespace Umbraco.Cms.Core.DependencyInjection private void AddCoreServices() { + Services.AddSingleton(AppCaches); + Services.AddSingleton(Profiler); + // Register as singleton to allow injection everywhere. Services.AddSingleton(p => p.GetService); Services.AddSingleton(); diff --git a/src/Umbraco.Core/HashGenerator.cs b/src/Umbraco.Core/HashGenerator.cs index 255cf381af..944e0bdf49 100644 --- a/src/Umbraco.Core/HashGenerator.cs +++ b/src/Umbraco.Core/HashGenerator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Security.Cryptography; using System.Text; @@ -73,14 +73,12 @@ namespace Umbraco.Cms.Core AddDateTime(f.LastWriteTimeUtc); //check if it is a file or folder - var fileInfo = f as FileInfo; - if (fileInfo != null) + if (f is FileInfo fileInfo) { AddLong(fileInfo.Length); } - var dirInfo = f as DirectoryInfo; - if (dirInfo != null) + if (f is DirectoryInfo dirInfo) { foreach (var d in dirInfo.GetFiles()) { diff --git a/src/Umbraco.Core/Logging/ProfilingLogger.cs b/src/Umbraco.Core/Logging/ProfilingLogger.cs index 9c66c95d24..a4843ccc29 100644 --- a/src/Umbraco.Core/Logging/ProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/ProfilingLogger.cs @@ -27,6 +27,15 @@ namespace Umbraco.Cms.Core.Logging Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); } + /// + /// Initializes a new instance of the class. + /// + public ProfilingLogger(ILogger logger, IProfiler profiler) + { + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); + } + public DisposableTimer TraceDuration(string startMessage, object[] startMessageArgs = null) => TraceDuration(startMessage, "Completed.", startMessageArgs: startMessageArgs); diff --git a/src/Umbraco.Core/Templates/HtmlUrlParser.cs b/src/Umbraco.Core/Templates/HtmlUrlParser.cs index 97bab9276a..50dc937466 100644 --- a/src/Umbraco.Core/Templates/HtmlUrlParser.cs +++ b/src/Umbraco.Core/Templates/HtmlUrlParser.cs @@ -1,4 +1,4 @@ -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Cms.Core.Configuration.Models; @@ -17,7 +17,7 @@ namespace Umbraco.Cms.Core.Templates private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - public HtmlUrlParser(IOptions contentSettings, ILogger logger ,IProfilingLogger profilingLogger, IIOHelper ioHelper) + public HtmlUrlParser(IOptions contentSettings, ILogger logger, IProfilingLogger profilingLogger, IIOHelper ioHelper) { _contentSettings = contentSettings.Value; _logger = logger; @@ -36,7 +36,8 @@ namespace Umbraco.Cms.Core.Templates /// public string EnsureUrls(string text) { - if (_contentSettings.ResolveUrlsFromTextString == false) return text; + if (_contentSettings.ResolveUrlsFromTextString == false) + return text; using (var timer = _profilingLogger.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete")) { diff --git a/src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs index 54dc414762..c3ba9ad393 100644 --- a/src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs @@ -16,8 +16,8 @@ namespace Umbraco.Tests.Benchmarks public TypeFinderBenchmarks() { - _typeFinder1 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance), new VaryingRuntimeHash()); - _typeFinder2 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance), new VaryingRuntimeHash()) + _typeFinder1 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance)); + _typeFinder2 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance)) { QueryWithReferencingAssemblies = false }; diff --git a/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs index 380e3fca99..d11bd6920d 100644 --- a/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs @@ -21,29 +21,30 @@ namespace Umbraco.Tests.Benchmarks { var typeFinder1 = new TypeFinder( new NullLogger(), - new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance), - new VaryingRuntimeHash()); + new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance)); var cache = new ObjectCacheAppCache(); _typeLoader1 = new TypeLoader( typeFinder1, + new VaryingRuntimeHash(), cache, null, new NullLogger(), - new ProfilingLogger(new NullLogger(), new NoopProfiler())); + new NoopProfiler()); // populate the cache cache.Insert( - TypeLoader.CacheKey, + _typeLoader1.CacheKey, GetCache, TimeSpan.FromDays(1)); _typeLoader2 = new TypeLoader( typeFinder1, + new VaryingRuntimeHash(), NoAppCache.Instance, null, new NullLogger(), - new ProfilingLogger(new NullLogger(), new NoopProfiler())); + new NoopProfiler()); } /// diff --git a/src/Umbraco.Tests.Common/TestHelperBase.cs b/src/Umbraco.Tests.Common/TestHelperBase.cs index 79f5e4d578..5a7bd1abc7 100644 --- a/src/Umbraco.Tests.Common/TestHelperBase.cs +++ b/src/Umbraco.Tests.Common/TestHelperBase.cs @@ -44,13 +44,13 @@ namespace Umbraco.Cms.Tests.Common protected TestHelperBase(Assembly entryAssembly) { MainDom = new SimpleMainDom(); - _typeFinder = new TypeFinder(NullLoggerFactory.Instance.CreateLogger(), new DefaultUmbracoAssemblyProvider(entryAssembly, NullLoggerFactory.Instance), new VaryingRuntimeHash()); + _typeFinder = new TypeFinder(NullLoggerFactory.Instance.CreateLogger(), new DefaultUmbracoAssemblyProvider(entryAssembly, NullLoggerFactory.Instance)); } public ITypeFinder GetTypeFinder() => _typeFinder; public TypeLoader GetMockedTypeLoader() => - new TypeLoader(Mock.Of(), Mock.Of(), new DirectoryInfo(GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of()); + new TypeLoader(Mock.Of(), new VaryingRuntimeHash(), Mock.Of(), new DirectoryInfo(GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of()); //// public Configs GetConfigs() => GetConfigsFactory().Create(); diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index b91a034420..59b970ebbc 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -134,10 +134,8 @@ namespace Umbraco.Cms.Tests.Integration.TestServerTest public override void ConfigureServices(IServiceCollection services) { services.AddTransient(); - IWebHostEnvironment webHostEnvironment = TestHelper.GetWebHostEnvironment(); TypeLoader typeLoader = services.AddTypeLoader( GetType().Assembly, - webHostEnvironment, TestHelper.GetHostingEnvironment(), TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 9629375553..bb4be6f368 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -202,15 +202,15 @@ namespace Umbraco.Cms.Tests.Integration.Testing services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment); // Add it! + Core.Hosting.IHostingEnvironment hostingEnvironment = TestHelper.GetHostingEnvironment(); TypeLoader typeLoader = services.AddTypeLoader( GetType().Assembly, - webHostEnvironment, - TestHelper.GetHostingEnvironment(), + hostingEnvironment, TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, Configuration, TestHelper.Profiler); - var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory); + var builder = new UmbracoBuilder(services, Configuration, typeLoader, TestHelper.ConsoleLoggerFactory, TestHelper.Profiler, AppCaches.NoCache, hostingEnvironment); builder.Services.AddLogger(TestHelper.GetHostingEnvironment(), TestHelper.GetLoggingConfiguration(), Configuration); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index 0c9b94a978..9e1669a846 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -85,7 +85,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components private static IServiceCollection MockRegister() => new ServiceCollection(); - private static TypeLoader MockTypeLoader() => new TypeLoader(Mock.Of(), Mock.Of(), new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of()); + private static TypeLoader MockTypeLoader() => new TypeLoader(Mock.Of(), new VaryingRuntimeHash(), Mock.Of(), new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of()); [Test] public void Boot1A() @@ -445,7 +445,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Components public void AllComposers() { ITypeFinder typeFinder = TestHelper.GetTypeFinder(); - var typeLoader = new TypeLoader(typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of()); + var typeLoader = new TypeLoader(typeFinder, new VaryingRuntimeHash(), AppCaches.Disabled.RuntimeCache, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of()); IServiceCollection register = MockRegister(); var builder = new UmbracoBuilder(register, Mock.Of(), TestHelper.GetMockedTypeLoader()); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs index 17f271888b..ac536b7aee 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs @@ -19,15 +19,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Composing { protected TypeLoader TypeLoader { get; private set; } - protected IProfilingLogger ProfilingLogger { get; private set; } - [SetUp] public void Initialize() { - ProfilingLogger = new ProfilingLogger(Mock.Of>(), Mock.Of()); - var typeFinder = TestHelper.GetTypeFinder(); - TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), ProfilingLogger, false, AssembliesToScan); + TypeLoader = new TypeLoader(typeFinder, new VaryingRuntimeHash(), NoAppCache.Instance, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of(), false, AssembliesToScan); } protected virtual IEnumerable AssembliesToScan diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs index 027b851529..bbe6519817 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeFinderTests.cs @@ -40,7 +40,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Composing [Test] public void Find_Class_Of_Type_With_Attribute() { - var typeFinder = new TypeFinder(Mock.Of>(), new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance), new VaryingRuntimeHash()); + var typeFinder = new TypeFinder(Mock.Of>(), new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance)); IEnumerable typesFound = typeFinder.FindClassesOfTypeWithAttribute(_assemblies); Assert.AreEqual(2, typesFound.Count()); } @@ -48,7 +48,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Composing [Test] public void Find_Classes_With_Attribute() { - var typeFinder = new TypeFinder(Mock.Of>(), new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance), new VaryingRuntimeHash()); + var typeFinder = new TypeFinder(Mock.Of>(), new DefaultUmbracoAssemblyProvider(GetType().Assembly, NullLoggerFactory.Instance)); IEnumerable typesFound = typeFinder.FindClassesWithAttribute(_assemblies); Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs index 794665c79d..cea18c5176 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs @@ -48,10 +48,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Composing }; _typeLoader = new TypeLoader( typeFinder, + new VaryingRuntimeHash(), NoAppCache.Instance, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), - new ProfilingLogger(Mock.Of>(), Mock.Of()), + Mock.Of(), false, assemblies); } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index e329d9fb67..957d5976e1 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -101,20 +101,25 @@ namespace Umbraco.Extensions services.AddSingleton(httpContextAccessor); var requestCache = new HttpContextRequestAppCache(httpContextAccessor); - var appCaches = AppCaches.Create(requestCache); - services.AddUnique(appCaches); + var appCaches = AppCaches.Create(requestCache); IProfiler profiler = GetWebProfiler(config); - services.AddUnique(profiler); - + ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false)); - TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), webHostEnvironment, tempHostingEnvironment, loggerFactory, appCaches, config, profiler); + + TypeLoader typeLoader = services.AddTypeLoader( + Assembly.GetEntryAssembly(), + tempHostingEnvironment, + loggerFactory, + appCaches, + config, + profiler); // adds the umbraco startup filter which will call UseUmbraco early on before // other start filters are applied (depending on the ordering of IStartupFilters in DI). services.AddTransient(); - return new UmbracoBuilder(services, config, typeLoader, loggerFactory); + return new UmbracoBuilder(services, config, typeLoader, loggerFactory, profiler, appCaches, tempHostingEnvironment); } /// diff --git a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs index 7fe337a9f9..df7ffd4328 100644 --- a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using System.Reflection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Serilog; @@ -60,54 +61,67 @@ namespace Umbraco.Extensions return services; } - internal static ITypeFinder AddTypeFinder( - this IServiceCollection services, - ILoggerFactory loggerFactory, - IWebHostEnvironment webHostEnvironment, - Assembly entryAssembly, - IConfiguration config, - IProfilingLogger profilingLogger) - { - - var typeFinderSettings = config.GetSection(Cms.Core.Constants.Configuration.ConfigTypeFinder).Get() ?? new TypeFinderSettings(); - - var runtimeHashPaths = new RuntimeHashPaths().AddFolder(new DirectoryInfo(Path.Combine(webHostEnvironment.ContentRootPath, "bin"))); - var runtimeHash = new RuntimeHash(profilingLogger, runtimeHashPaths); - - var typeFinder = new TypeFinder( - loggerFactory.CreateLogger(), - new DefaultUmbracoAssemblyProvider(entryAssembly, loggerFactory), - runtimeHash, - new TypeFinderConfig(Options.Create(typeFinderSettings)) - ); - - services.AddUnique(typeFinder); - - return typeFinder; - } - + /// + /// Called to create the to assign to the + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// 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. + /// public static TypeLoader AddTypeLoader( this IServiceCollection services, Assembly entryAssembly, - IWebHostEnvironment webHostEnvironment, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory, AppCaches appCaches, IConfiguration configuration, IProfiler profiler) { - var profilingLogger = new ProfilingLogger(loggerFactory.CreateLogger(), profiler); - var typeFinder = services.AddTypeFinder(loggerFactory, webHostEnvironment, entryAssembly, configuration, profilingLogger); + TypeFinderSettings typeFinderSettings = configuration.GetSection(Cms.Core.Constants.Configuration.ConfigTypeFinder).Get() ?? new TypeFinderSettings(); + + var assemblyProvider = new DefaultUmbracoAssemblyProvider( + entryAssembly, + loggerFactory, + typeFinderSettings.AdditionalEntryAssemblies); + + RuntimeHashPaths runtimeHashPaths = new RuntimeHashPaths().AddAssemblies(assemblyProvider); + + var runtimeHash = new RuntimeHash( + new ProfilingLogger( + loggerFactory.CreateLogger(), + profiler), + runtimeHashPaths); + + var typeFinderConfig = new TypeFinderConfig(Options.Create(typeFinderSettings)); + + var typeFinder = new TypeFinder( + loggerFactory.CreateLogger(), + assemblyProvider, + typeFinderConfig + ); var typeLoader = new TypeLoader( typeFinder, + runtimeHash, appCaches.RuntimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), loggerFactory.CreateLogger(), - profilingLogger + profiler ); - services.AddUnique(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. + services.TryAddSingleton(typeFinder); + services.TryAddSingleton(typeLoader); return typeLoader; }