From 1ef5290576c43489b88783f50cfe8ef24ce5d24c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 9 Jul 2021 14:50:37 -0600 Subject: [PATCH 1/6] Updates runtime hash calc to be based on the assemblies returned from the assembly provider. --- .../DefaultUmbracoAssemblyProvider.cs | 11 ++++- src/Umbraco.Core/Composing/RuntimeHash.cs | 46 ++++++++++--------- src/Umbraco.Core/Composing/TypeFinder.cs | 4 +- src/Umbraco.Core/HashGenerator.cs | 8 ++-- src/Umbraco.Core/IO/FileSystemExtensions.cs | 23 +++++++++- .../UmbracoTestServerTestBase.cs | 2 - .../Testing/UmbracoIntegrationTest.cs | 1 - .../UmbracoBuilderExtensions.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 20 +++++--- 9 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index 60aa666034..52967a58e7 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,6 +17,7 @@ namespace Umbraco.Cms.Core.Composing { private readonly Assembly _entryPointAssembly; private readonly ILoggerFactory _loggerFactory; + private List _discovered; public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly, ILoggerFactory loggerFactory) { @@ -33,8 +35,15 @@ namespace Umbraco.Cms.Core.Composing { get { + if (_discovered != null) + { + return _discovered; + } + var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, Constants.Composing.UmbracoCoreAssemblyNames, true, _loggerFactory); - return finder.Find(); + _discovered = finder.Find().ToList(); + + return _discovered; } } } 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/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 1979f33d10..97b8e3847c 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -21,7 +21,7 @@ namespace Umbraco.Cms.Core.Composing 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 @@ -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)); 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/IO/FileSystemExtensions.cs b/src/Umbraco.Core/IO/FileSystemExtensions.cs index 6f1f47007f..23be195e4b 100644 --- a/src/Umbraco.Core/IO/FileSystemExtensions.cs +++ b/src/Umbraco.Core/IO/FileSystemExtensions.cs @@ -1,5 +1,7 @@ -using System; +using System; using System.IO; +using System.Security.Cryptography; +using System.Text; using System.Threading; using Umbraco.Cms.Core.IO; @@ -7,6 +9,25 @@ namespace Umbraco.Extensions { public static class FileSystemExtensions { + public static string GetStreamHash(this Stream fileStream) + { + if (fileStream.CanSeek) + { + fileStream.Seek(0, SeekOrigin.Begin); + } + + using HashAlgorithm alg = SHA1.Create(); + + // create a string output for the hash + var stringBuilder = new StringBuilder(); + var hashedByteArray = alg.ComputeHash(fileStream); + foreach (var b in hashedByteArray) + { + stringBuilder.Append(b.ToString("x2")); + } + return stringBuilder.ToString(); + } + /// /// Attempts to open the file at filePath up to maxRetries times, /// with a thread sleep time of sleepPerRetryInMilliseconds between retries. 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..b3a168e119 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -204,7 +204,6 @@ namespace Umbraco.Cms.Tests.Integration.Testing // Add it! TypeLoader typeLoader = services.AddTypeLoader( GetType().Assembly, - webHostEnvironment, TestHelper.GetHostingEnvironment(), TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index e329d9fb67..b4273446f9 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -108,7 +108,7 @@ namespace Umbraco.Extensions 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). diff --git a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs index 7fe337a9f9..16a83543ef 100644 --- a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs @@ -63,20 +63,29 @@ namespace Umbraco.Extensions internal static ITypeFinder AddTypeFinder( this IServiceCollection services, ILoggerFactory loggerFactory, - IWebHostEnvironment webHostEnvironment, Assembly entryAssembly, IConfiguration config, IProfilingLogger profilingLogger) { + TypeFinderSettings typeFinderSettings = config.GetSection(Cms.Core.Constants.Configuration.ConfigTypeFinder).Get() ?? new TypeFinderSettings(); - var typeFinderSettings = config.GetSection(Cms.Core.Constants.Configuration.ConfigTypeFinder).Get() ?? new TypeFinderSettings(); + var assemblyProvider = new DefaultUmbracoAssemblyProvider(entryAssembly, loggerFactory); + + var runtimeHashPaths = new RuntimeHashPaths(); + foreach(Assembly assembly in assemblyProvider.Assemblies) + { + // TODO: We need to test this on a published website + if (!assembly.IsDynamic && assembly.Location != null) + { + runtimeHashPaths.AddFile(new FileInfo(assembly.Location)); + } + } - 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), + assemblyProvider, runtimeHash, new TypeFinderConfig(Options.Create(typeFinderSettings)) ); @@ -89,7 +98,6 @@ namespace Umbraco.Extensions public static TypeLoader AddTypeLoader( this IServiceCollection services, Assembly entryAssembly, - IWebHostEnvironment webHostEnvironment, IHostingEnvironment hostingEnvironment, ILoggerFactory loggerFactory, AppCaches appCaches, @@ -97,7 +105,7 @@ namespace Umbraco.Extensions IProfiler profiler) { var profilingLogger = new ProfilingLogger(loggerFactory.CreateLogger(), profiler); - var typeFinder = services.AddTypeFinder(loggerFactory, webHostEnvironment, entryAssembly, configuration, profilingLogger); + var typeFinder = services.AddTypeFinder(loggerFactory, entryAssembly, configuration, profilingLogger); var typeLoader = new TypeLoader( typeFinder, From 7ec3afa5ff3db3b37fa60a478d5524511b792974 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 9 Jul 2021 15:31:01 -0600 Subject: [PATCH 2/6] Adds new ctor to runtime hash class. Adds startup objects to the IUmbracoBuilder that are not otherwise available during configuration. --- .../Composing/RuntimeHashPaths.cs | 21 ++++++++++++++- src/Umbraco.Core/Composing/TypeLoader.cs | 14 ++++++---- .../DependencyInjection/IUmbracoBuilder.cs | 20 ++++++++++++++ .../DependencyInjection/UmbracoBuilder.cs | 27 ++++++++++++++++--- src/Umbraco.Core/Logging/ProfilingLogger.cs | 2 +- .../TypeLoaderBenchmarks.cs | 4 +-- src/Umbraco.Tests.Common/TestHelperBase.cs | 2 +- .../Testing/UmbracoIntegrationTest.cs | 5 ++-- .../Umbraco.Core/Components/ComponentTests.cs | 4 +-- .../Composing/ComposingTestBase.cs | 6 +---- .../Umbraco.Core/Composing/TypeLoaderTests.cs | 2 +- .../UmbracoBuilderExtensions.cs | 8 +++--- .../Extensions/ServiceCollectionExtensions.cs | 23 +++++++--------- 13 files changed, 96 insertions(+), 42 deletions(-) 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/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 39b70b831b..fa66780914 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; @@ -55,8 +54,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, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, ILogger logger, IProfiler profiler, IEnumerable assembliesToScan = null) + : this(typeFinder, runtimeCache, localTempPath, logger, profiler, true, assembliesToScan) { } /// @@ -68,13 +67,18 @@ 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, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, ILogger logger, IProfiler profiler, bool detectChanges, IEnumerable assembliesToScan = null) { + if (profiler is null) + { + throw new ArgumentNullException(nameof(profiler)); + } + TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); _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); 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/Logging/ProfilingLogger.cs b/src/Umbraco.Core/Logging/ProfilingLogger.cs index 9c66c95d24..0e340baf90 100644 --- a/src/Umbraco.Core/Logging/ProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/ProfilingLogger.cs @@ -21,7 +21,7 @@ namespace Umbraco.Cms.Core.Logging /// /// Initializes a new instance of the class. /// - public ProfilingLogger(ILogger logger, IProfiler profiler) + public ProfilingLogger(ILogger logger, IProfiler profiler) { Logger = logger ?? throw new ArgumentNullException(nameof(logger)); Profiler = profiler ?? throw new ArgumentNullException(nameof(profiler)); diff --git a/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs index 380e3fca99..00d6820302 100644 --- a/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs @@ -30,7 +30,7 @@ namespace Umbraco.Tests.Benchmarks cache, null, new NullLogger(), - new ProfilingLogger(new NullLogger(), new NoopProfiler())); + new NoopProfiler()); // populate the cache cache.Insert( @@ -43,7 +43,7 @@ namespace Umbraco.Tests.Benchmarks 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..ec76e0ba08 100644 --- a/src/Umbraco.Tests.Common/TestHelperBase.cs +++ b/src/Umbraco.Tests.Common/TestHelperBase.cs @@ -50,7 +50,7 @@ namespace Umbraco.Cms.Tests.Common 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(), 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/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index b3a168e119..bb4be6f368 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -202,14 +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, - 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 11fee778e2..bd8fc3103f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -78,7 +78,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(), Mock.Of(), new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of()); [Test] public void Boot1A() @@ -438,7 +438,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, 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..3a38f0db11 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, 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/TypeLoaderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs index 794665c79d..e81fcfdabd 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs @@ -51,7 +51,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Composing 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 b4273446f9..5f9027db46 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -101,12 +101,10 @@ 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(), tempHostingEnvironment, loggerFactory, appCaches, config, profiler); @@ -114,7 +112,7 @@ namespace Umbraco.Extensions // 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 16a83543ef..d5a02027a2 100644 --- a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs @@ -65,23 +65,19 @@ namespace Umbraco.Extensions ILoggerFactory loggerFactory, Assembly entryAssembly, IConfiguration config, - IProfilingLogger profilingLogger) + IProfiler profiler) { TypeFinderSettings typeFinderSettings = config.GetSection(Cms.Core.Constants.Configuration.ConfigTypeFinder).Get() ?? new TypeFinderSettings(); var assemblyProvider = new DefaultUmbracoAssemblyProvider(entryAssembly, loggerFactory); - var runtimeHashPaths = new RuntimeHashPaths(); - foreach(Assembly assembly in assemblyProvider.Assemblies) - { - // TODO: We need to test this on a published website - if (!assembly.IsDynamic && assembly.Location != null) - { - runtimeHashPaths.AddFile(new FileInfo(assembly.Location)); - } - } + RuntimeHashPaths runtimeHashPaths = new RuntimeHashPaths().AddAssemblies(assemblyProvider); - var runtimeHash = new RuntimeHash(profilingLogger, runtimeHashPaths); + var runtimeHash = new RuntimeHash( + new ProfilingLogger( + loggerFactory.CreateLogger(), + profiler), + runtimeHashPaths); var typeFinder = new TypeFinder( loggerFactory.CreateLogger(), @@ -104,15 +100,14 @@ namespace Umbraco.Extensions IConfiguration configuration, IProfiler profiler) { - var profilingLogger = new ProfilingLogger(loggerFactory.CreateLogger(), profiler); - var typeFinder = services.AddTypeFinder(loggerFactory, entryAssembly, configuration, profilingLogger); + ITypeFinder typeFinder = services.AddTypeFinder(loggerFactory, entryAssembly, configuration, profiler); var typeLoader = new TypeLoader( typeFinder, appCaches.RuntimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), loggerFactory.CreateLogger(), - profilingLogger + profiler ); services.AddUnique(typeLoader); From f7b8a93a35e6f5acbd79e74bc15bbdbe5f0846d6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 9 Jul 2021 16:15:37 -0600 Subject: [PATCH 3/6] Fixes typefinder/loader dependency refs, allows config to specify additional entry assemblies --- .../DefaultUmbracoAssemblyProvider.cs | 11 ++- .../FindAssembliesWithReferencesTo.cs | 7 +- src/Umbraco.Core/Composing/ITypeFinder.cs | 11 +-- src/Umbraco.Core/Composing/TypeFinder.cs | 23 +++--- .../Composing/TypeFinderConfig.cs | 10 +-- src/Umbraco.Core/Composing/TypeLoader.cs | 33 +++++--- .../Models/TypeFinderSettings.cs | 9 +++ .../TypeFinderBenchmarks.cs | 4 +- .../TypeLoaderBenchmarks.cs | 5 +- src/Umbraco.Tests.Common/TestHelperBase.cs | 4 +- .../Umbraco.Core/Components/ComponentTests.cs | 4 +- .../Composing/ComposingTestBase.cs | 2 +- .../Umbraco.Core/Composing/TypeFinderTests.cs | 4 +- .../Umbraco.Core/Composing/TypeLoaderTests.cs | 1 + .../UmbracoBuilderExtensions.cs | 9 ++- .../Extensions/ServiceCollectionExtensions.cs | 77 +++++++++++-------- 16 files changed, 127 insertions(+), 87 deletions(-) diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index 52967a58e7..dce85264d1 100644 --- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -17,12 +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 @@ -40,7 +45,9 @@ namespace Umbraco.Cms.Core.Composing return _discovered; } - var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, Constants.Composing.UmbracoCoreAssemblyNames, true, _loggerFactory); + IEnumerable additionalTargetAssemblies = _additionalTargetAssemblies.Concat(Constants.Composing.UmbracoCoreAssemblyNames); + + 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/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 97b8e3847c..4299328b5d 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -17,7 +17,6 @@ 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(); @@ -27,13 +26,12 @@ namespace Umbraco.Cms.Core.Composing // 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 /// @@ -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 fa66780914..5e6808fb13 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -25,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; @@ -44,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. @@ -54,8 +55,8 @@ namespace Umbraco.Cms.Core.Composing /// Files storage location. /// A profiling logger. /// - public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, ILogger logger, IProfiler profiler, IEnumerable assembliesToScan = null) - : this(typeFinder, runtimeCache, localTempPath, logger, profiler, 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) { } /// @@ -67,14 +68,18 @@ namespace Umbraco.Cms.Core.Composing /// A profiling logger. /// Whether to detect changes using hashes. /// - public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, ILogger logger, IProfiler profiler, 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)); @@ -119,6 +124,8 @@ namespace Umbraco.Cms.Core.Composing } } + internal string CacheKey { get; } + /// /// Returns the underlying /// @@ -202,9 +209,11 @@ namespace Umbraco.Cms.Core.Composing get { if (_currentAssembliesHash != null) + { return _currentAssembliesHash; + } - _currentAssembliesHash = TypeFinder.GetRuntimeHash(); + _currentAssembliesHash = _runtimeHash.GetHashValue(); return _currentAssembliesHash; } @@ -268,7 +277,7 @@ namespace Umbraco.Cms.Core.Composing } } - return EmptyCache; + return _emptyCache; } // internal for tests @@ -277,7 +286,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>(); @@ -332,9 +341,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.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 00d6820302..1bd875142d 100644 --- a/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs @@ -21,12 +21,12 @@ 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(), @@ -40,6 +40,7 @@ namespace Umbraco.Tests.Benchmarks _typeLoader2 = new TypeLoader( typeFinder1, + new VaryingRuntimeHash(), NoAppCache.Instance, null, new NullLogger(), diff --git a/src/Umbraco.Tests.Common/TestHelperBase.cs b/src/Umbraco.Tests.Common/TestHelperBase.cs index ec76e0ba08..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.UnitTests/Umbraco.Core/Components/ComponentTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs index bd8fc3103f..af221514e3 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs @@ -78,7 +78,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() @@ -438,7 +438,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 3a38f0db11..ac536b7aee 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/ComposingTestBase.cs @@ -23,7 +23,7 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Composing public void Initialize() { var typeFinder = TestHelper.GetTypeFinder(); - TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, new DirectoryInfo(TestHelper.GetHostingEnvironment().MapPathContentRoot(Constants.SystemDirectories.TempData)), Mock.Of>(), Mock.Of(), 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 e81fcfdabd..cea18c5176 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Composing/TypeLoaderTests.cs @@ -48,6 +48,7 @@ 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>(), diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 5f9027db46..957d5976e1 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -106,7 +106,14 @@ namespace Umbraco.Extensions IProfiler profiler = GetWebProfiler(config); ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false)); - TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), 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). diff --git a/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/ServiceCollectionExtensions.cs index d5a02027a2..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,37 +61,22 @@ namespace Umbraco.Extensions return services; } - internal static ITypeFinder AddTypeFinder( - this IServiceCollection services, - ILoggerFactory loggerFactory, - Assembly entryAssembly, - IConfiguration config, - IProfiler profiler) - { - TypeFinderSettings typeFinderSettings = config.GetSection(Cms.Core.Constants.Configuration.ConfigTypeFinder).Get() ?? new TypeFinderSettings(); - - var assemblyProvider = new DefaultUmbracoAssemblyProvider(entryAssembly, loggerFactory); - - RuntimeHashPaths runtimeHashPaths = new RuntimeHashPaths().AddAssemblies(assemblyProvider); - - var runtimeHash = new RuntimeHash( - new ProfilingLogger( - loggerFactory.CreateLogger(), - profiler), - runtimeHashPaths); - - var typeFinder = new TypeFinder( - loggerFactory.CreateLogger(), - assemblyProvider, - 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, @@ -100,17 +86,42 @@ namespace Umbraco.Extensions IConfiguration configuration, IProfiler profiler) { - ITypeFinder typeFinder = services.AddTypeFinder(loggerFactory, entryAssembly, configuration, profiler); + 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(), 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; } From 582fdec2138c6e73ccf6491fbfb439d9508b0a53 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 9 Jul 2021 16:37:37 -0600 Subject: [PATCH 4/6] Adds null check and adds back orig ctor for ProfilingLogger --- .../Composing/DefaultUmbracoAssemblyProvider.cs | 6 +++++- src/Umbraco.Core/Logging/ProfilingLogger.cs | 9 +++++++++ src/Umbraco.Core/Templates/HtmlUrlParser.cs | 7 ++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index dce85264d1..56d798ea0c 100644 --- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -45,7 +45,11 @@ namespace Umbraco.Cms.Core.Composing return _discovered; } - IEnumerable additionalTargetAssemblies = _additionalTargetAssemblies.Concat(Constants.Composing.UmbracoCoreAssemblyNames); + 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(); diff --git a/src/Umbraco.Core/Logging/ProfilingLogger.cs b/src/Umbraco.Core/Logging/ProfilingLogger.cs index 0e340baf90..a4843ccc29 100644 --- a/src/Umbraco.Core/Logging/ProfilingLogger.cs +++ b/src/Umbraco.Core/Logging/ProfilingLogger.cs @@ -18,6 +18,15 @@ namespace Umbraco.Cms.Core.Logging /// public IProfiler Profiler { get; } + /// + /// 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)); + } + /// /// Initializes a new instance of the class. /// 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")) { From bc2aab5e67f6832a45fcf70be6b29b2c0a450894 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 12 Jul 2021 09:49:27 -0600 Subject: [PATCH 5/6] fix benchmark build --- src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs index 1bd875142d..d11bd6920d 100644 --- a/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/TypeLoaderBenchmarks.cs @@ -34,7 +34,7 @@ namespace Umbraco.Tests.Benchmarks // populate the cache cache.Insert( - TypeLoader.CacheKey, + _typeLoader1.CacheKey, GetCache, TimeSpan.FromDays(1)); From e04d2a84054266c8f1b16afc494c84d83925dcc9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 12 Jul 2021 09:58:19 -0600 Subject: [PATCH 6/6] adds debug logging --- src/Umbraco.Core/Composing/TypeLoader.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 5e6808fb13..6af8cb5ced 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -96,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