New IRuntimeHash to fix type scanning in netcore in order to look at the right runtime bits to creaet a hash for

This commit is contained in:
Shannon
2020-04-03 01:08:52 +11:00
parent f0579714e9
commit 9d320f79da
38 changed files with 322 additions and 192 deletions
@@ -131,8 +131,8 @@ namespace Umbraco.Core.Configuration.Legacy
if (_reservedPaths != null) return _reservedPaths;
var reservedPaths = StaticReservedPaths;
var umbPath = ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.Path) && !ConfigurationManager.AppSettings[Constants.AppSettings.Path].IsNullOrWhiteSpace()
? ConfigurationManager.AppSettings[Constants.AppSettings.Path]
var umbPath = ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.UmbracoPath) && !ConfigurationManager.AppSettings[Constants.AppSettings.UmbracoPath].IsNullOrWhiteSpace()
? ConfigurationManager.AppSettings[Constants.AppSettings.UmbracoPath]
: "~/umbraco";
//always add the umbraco path to the list
reservedPaths += umbPath.EnsureEndsWith(',');
@@ -146,12 +146,6 @@ namespace Umbraco.Core.Configuration.Legacy
}
}
/// <summary>
/// Gets the path to umbraco's root directory (/umbraco by default).
/// </summary>
/// <value>The path.</value>
public string Path => ConfigurationManager.AppSettings[Constants.AppSettings.Path];
/// <summary>
/// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance.
/// </summary>
@@ -30,8 +30,6 @@ namespace Umbraco.Configuration.Models
public string ReservedUrls => _configuration.GetValue(Prefix + "ReservedUrls", StaticReservedUrls);
public string ReservedPaths => _configuration.GetValue(Prefix + "ReservedPaths", StaticReservedPaths);
public string Path => _configuration.GetValue<string>(Prefix + "Path");
// TODO: https://github.com/umbraco/Umbraco-CMS/issues/4238 - stop having version in web.config appSettings
public string ConfigurationStatus
{
@@ -0,0 +1,14 @@
namespace Umbraco.Core.Composing
{
/// <summary>
/// Used to create a hash value of the current runtime
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public interface IRuntimeHash
{
string GetHashValue();
}
}
@@ -51,5 +51,14 @@ namespace Umbraco.Core.Composing
Type attributeType,
IEnumerable<Assembly> assemblies,
bool onlyConcreteClasses);
/// <summary>
/// Gets a hash value of the current runtime
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
string GetRuntimeHash();
}
}
+91
View File
@@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Determines the runtime hash based on file system paths to scan
/// </summary>
public class RuntimeHash : IRuntimeHash
{
private readonly IProfilingLogger _logger;
private readonly RuntimeHashPaths _paths;
public RuntimeHash(IProfilingLogger logger, RuntimeHashPaths paths)
{
_logger = logger;
_paths = paths;
}
public string GetHashValue()
{
var allPaths = _paths.GetFolders()
.Select(x => ((FileSystemInfo) x, false))
.Concat(_paths.GetFiles().Select(x => ((FileSystemInfo) x.Key, x.Value)));
var hash = GetFileHash(allPaths);
return hash;
}
/// <summary>
/// Returns a unique hash for a combination of FileInfo objects.
/// </summary>
/// <param name="filesAndFolders">A collection of files.</param>
/// <returns>The hash.</returns>
/// <remarks>Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the
/// file properties (false) or the file contents (true).</remarks>
private string GetFileHash(IEnumerable<(FileSystemInfo fileOrFolder, bool scanFileContent)> filesAndFolders)
{
using (_logger.DebugDuration<TypeLoader>("Determining hash of code files on disk", "Hash determined"))
{
// get the distinct file infos to hash
var uniqInfos = new HashSet<string>();
var uniqContent = new HashSet<string>();
using var generator = new HashGenerator();
foreach (var (fileOrFolder, scanFileContent) in filesAndFolders)
{
if (scanFileContent)
{
// add each unique file's contents to the hash
// 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);
}
}
else
{
// add each unique folder/file to the hash
if (uniqInfos.Add(fileOrFolder.FullName))
{
generator.AddFileSystemItem(fileOrFolder);
}
}
}
return generator.GenerateHash();
}
}
// 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);
}
}
}
@@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.IO;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Paths used to determine the <see cref="IRuntimeHash"/>
/// </summary>
public sealed class RuntimeHashPaths
{
private readonly List<DirectoryInfo> _paths = new List<DirectoryInfo>();
private readonly Dictionary<FileInfo, bool> _files = new Dictionary<FileInfo, bool>();
public void AddFolder(DirectoryInfo pathInfo) => _paths.Add(pathInfo);
public void AddFile(FileInfo fileInfo, bool scanFileContent = false) => _files.Add(fileInfo, scanFileContent);
public IEnumerable<DirectoryInfo> GetFolders() => _paths;
public IReadOnlyDictionary<FileInfo, bool> GetFiles() => _files;
}
}
+6 -1
View File
@@ -16,6 +16,7 @@ namespace Umbraco.Core.Composing
{
private readonly ILogger _logger;
private readonly IAssemblyProvider _assemblyProvider;
private readonly IRuntimeHash _runtimeHash;
private volatile HashSet<Assembly> _localFilteredAssemblyCache;
private readonly object _localFilteredAssemblyCacheLocker = new object();
private readonly List<string> _notifiedLoadExceptionAssemblies = new List<string>();
@@ -25,10 +26,11 @@ namespace Umbraco.Core.Composing
// used for benchmark tests
internal bool QueryWithReferencingAssemblies = true;
public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig typeFinderConfig = null)
public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, IRuntimeHash runtimeHash, ITypeFinderConfig typeFinderConfig = null)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_assemblyProvider = assemblyProvider;
_runtimeHash = runtimeHash;
_assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty<string>();
}
@@ -208,6 +210,9 @@ namespace Umbraco.Core.Composing
return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses);
}
/// <inheritdoc />
public string GetRuntimeHash() => _runtimeHash.GetHashValue();
/// <summary>
/// Returns a Type for the string type name
/// </summary>
+7 -109
View File
@@ -14,8 +14,6 @@ using File = System.IO.File;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Provides methods to find and instantiate types.
/// </summary>
@@ -29,7 +27,6 @@ namespace Umbraco.Core.Composing
{
private const string CacheKey = "umbraco-types.list";
private readonly IIOHelper _ioHelper;
private readonly IAppPolicyCache _runtimeCache;
private readonly IProfilingLogger _logger;
@@ -49,30 +46,29 @@ namespace Umbraco.Core.Composing
/// <summary>
/// Initializes a new instance of the <see cref="TypeLoader"/> class.
/// </summary>
/// <param name="ioHelper"></param>
/// <param name="runtimeHash"></param>
/// <param name="typeFinder"></param>
/// <param name="runtimeCache">The application runtime cache.</param>
/// <param name="localTempPath">Files storage location.</param>
/// <param name="logger">A profiling logger.</param>
/// <param name="assembliesToScan"></param>
public TypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, IEnumerable<Assembly> assembliesToScan = null)
: this(ioHelper, typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan)
public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, IEnumerable<Assembly> assembliesToScan = null)
: this(typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="TypeLoader"/> class.
/// </summary>
/// <param name="ioHelper"></param>
/// <param name="runtimeHash"></param>
/// <param name="typeFinder"></param>
/// <param name="runtimeCache">The application runtime cache.</param>
/// <param name="localTempPath">Files storage location.</param>
/// <param name="logger">A profiling logger.</param>
/// <param name="detectChanges">Whether to detect changes using hashes.</param>
/// <param name="assembliesToScan"></param>
public TypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable<Assembly> assembliesToScan = null)
public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable<Assembly> assembliesToScan = null)
{
TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder));
_ioHelper = ioHelper;
_runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache));
_localTempPath = localTempPath;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
@@ -124,7 +120,7 @@ namespace Umbraco.Core.Composing
/// <para>This is for unit tests.</para>
/// </remarks>
// internal for tests
public IEnumerable<Assembly> AssembliesToScan => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan);
public IEnumerable<Assembly> AssembliesToScan => _assemblies ??= TypeFinder.AssembliesToScan;
/// <summary>
/// Gets the type lists.
@@ -183,19 +179,7 @@ namespace Umbraco.Core.Composing
if (_currentAssembliesHash != null)
return _currentAssembliesHash;
_currentAssembliesHash = GetFileHash(new List<Tuple<FileSystemInfo, bool>>
{
// TODO: Would be nicer to abstract this logic out into IAssemblyHash
// TODO: Use constants from SystemDirectories when we can (once it's ported to netstandard lib)
// the bin folder and everything in it
new Tuple<FileSystemInfo, bool>(new DirectoryInfo(_ioHelper.MapPath("~/bin")), false),
// the app code folder and everything in it
new Tuple<FileSystemInfo, bool>(new DirectoryInfo(_ioHelper.MapPath("~/App_Code")), false),
// global.asax (the app domain also monitors this, if it changes will do a full restart)
new Tuple<FileSystemInfo, bool>(new FileInfo(_ioHelper.MapPath("~/global.asax")), false)
}, _logger);
_currentAssembliesHash = TypeFinder.GetRuntimeHash();
return _currentAssembliesHash;
}
@@ -210,92 +194,6 @@ namespace Umbraco.Core.Composing
File.WriteAllText(typesHashFilePath, CurrentAssembliesHash, Encoding.UTF8);
}
/// <summary>
/// Returns a unique hash for a combination of FileInfo objects.
/// </summary>
/// <param name="filesAndFolders">A collection of files.</param>
/// <param name="logger">A profiling logger.</param>
/// <returns>The hash.</returns>
/// <remarks>Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the
/// file properties (false) or the file contents (true).</remarks>
private static string GetFileHash(IEnumerable<Tuple<FileSystemInfo, bool>> filesAndFolders, IProfilingLogger logger)
{
using (logger.DebugDuration<TypeLoader>("Determining hash of code files on disk", "Hash determined"))
{
// get the distinct file infos to hash
var uniqInfos = new HashSet<string>();
var uniqContent = new HashSet<string>();
using (var generator = new HashGenerator())
{
foreach (var fileOrFolder in filesAndFolders)
{
var info = fileOrFolder.Item1;
if (fileOrFolder.Item2)
{
// add each unique file's contents to the hash
// normalize the content for cr/lf and case-sensitivity
if (uniqContent.Add(info.FullName))
{
if (File.Exists(info.FullName) == false) continue;
var content = RemoveCrLf(File.ReadAllText(info.FullName));
generator.AddCaseInsensitiveString(content);
}
}
else
{
// add each unique folder/file to the hash
if (uniqInfos.Add(info.FullName))
{
generator.AddFileSystemItem(info);
}
}
}
return generator.GenerateHash();
}
}
}
// 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);
}
/// <summary>
/// Returns a unique hash for a combination of FileInfo objects.
/// </summary>
/// <param name="filesAndFolders">A collection of files.</param>
/// <param name="logger">A profiling logger.</param>
/// <returns>The hash.</returns>
// internal for tests
public static string GetFileHash(IEnumerable<FileSystemInfo> filesAndFolders, IProfilingLogger logger)
{
using (logger.DebugDuration<TypeLoader>("Determining hash of code files on disk", "Hash determined"))
{
using (var generator = new HashGenerator())
{
// get the distinct file infos to hash
var uniqInfos = new HashSet<string>();
foreach (var fileOrFolder in filesAndFolders)
{
if (uniqInfos.Contains(fileOrFolder.FullName)) continue;
uniqInfos.Add(fileOrFolder.FullName);
generator.AddFileSystemItem(fileOrFolder);
}
return generator.GenerateHash();
}
}
}
#endregion
#region Cache
@@ -0,0 +1,19 @@
using System;
namespace Umbraco.Core.Composing
{
/// <summary>
/// A runtime hash this is always different on each app startup
/// </summary>
public sealed class VaryingRuntimeHash : IRuntimeHash
{
private readonly string _hash;
public VaryingRuntimeHash()
{
_hash = DateTime.Now.Ticks.ToString();
}
public string GetHashValue() => _hash;
}
}
@@ -20,11 +20,6 @@
/// <value>The reserved paths.</value>
string ReservedPaths { get; }
/// <summary>
/// Gets the path to umbraco's root directory.
/// </summary>
string Path { get; }
/// <summary>
/// Gets or sets the configuration status. This will return the version number of the currently installed umbraco instance.
/// </summary>
@@ -61,6 +56,9 @@
/// <value>The version check period in days (0 = never).</value>
int VersionCheckPeriod { get; }
/// <summary>
/// Gets the path to umbraco's root directory.
/// </summary>
string UmbracoPath { get; }
string UmbracoCssPath { get; }
string UmbracoScriptsPath { get; }
+5 -10
View File
@@ -41,32 +41,27 @@ namespace Umbraco.Core
/// <summary>
/// Gets the path to umbraco's root directory (/umbraco by default).
/// </summary>
public const string Path = "Umbraco.Core.Path";
public const string UmbracoPath = "Umbraco.Core.Path";
/// <summary>
/// The reserved urls from web.config.
/// </summary>
public const string ReservedUrls = "Umbraco.Core.ReservedUrls";
/// <summary>
/// The path of backoffice.
/// </summary>
public const string UmbracoPath = "umbracoPath";
/// <summary>
/// The path of the stylesheet folder.
/// </summary>
public const string UmbracoCssPath = "umbracoCssPath";
public const string UmbracoCssPath = "Umbraco.Web.CssPath";
/// <summary>
/// The path of script folder.
/// </summary>
public const string UmbracoScriptsPath = "umbracoScriptsPath";
public const string UmbracoScriptsPath = "Umbraco.Core.ScriptsPath";
/// <summary>
/// The path of media folder.
/// </summary>
public const string UmbracoMediaPath = "umbracoMediaPath";
public const string UmbracoMediaPath = "Umbraco.Core.MediaPath";
/// <summary>
/// The reserved paths from web.config
@@ -18,6 +18,21 @@ namespace Umbraco.Core.Hosting
bool IsHosted { get; }
Version IISVersion { get; }
string MapPath(string path);
/// <summary>
/// Maps a virtual path to the application's web root
/// </summary>
/// <param name="virtualPath">The virtual path. Must start with either ~/ or / else an exception is thrown.</param>
/// <param name="root">The absolute web root value. Must start with / else an exception is thrown.</param>
/// <returns></returns>
/// <remarks>
/// This maps the virtual path syntax to the web root. For example when hosting in a virtual directory called "site" and the value "~/pages/test" is passed in, it will
/// map to "/site/pages/test" where "/site" is the value of root.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// If virtualPath does not start with ~/ or /
/// If root does not start with /
/// </exception>
string ToAbsolute(string virtualPath, string root);
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ namespace Umbraco.Core.IO
{
get
{
var path = _globalSettings.Path;
var path = _globalSettings.UmbracoPath;
return string.IsNullOrEmpty(path) ? string.Empty : ResolveUrl(path);
}
@@ -163,7 +163,7 @@ namespace Umbraco.Core.Runtime
var databaseFactory = GetDatabaseFactory();
// type finder/loader
var typeLoader = new TypeLoader(IOHelper, TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger);
var typeLoader = new TypeLoader(TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger);
// create the composition
composition = new Composition(register, typeLoader, ProfilingLogger, _state, Configs, IOHelper, appCaches);
@@ -15,16 +15,18 @@ namespace Umbraco.Tests.Benchmarks
[Benchmark(Baseline = true)]
public void WithGetReferencingAssembliesCheck()
{
var typeFinder1 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var typeFinder1 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash());
var found = typeFinder1.FindClassesOfType<IDiscoverable>().Count();
}
[Benchmark]
public void WithoutGetReferencingAssembliesCheck()
{
var typeFinder2 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var typeFinder2 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash());
typeFinder2.QueryWithReferencingAssemblies = false;
var found = typeFinder2.FindClassesOfType<IDiscoverable>().Count();
}
}
}
@@ -24,7 +24,6 @@ namespace Umbraco.Tests.Common
settings.ConfigurationStatus == semanticVersion.ToSemanticString() &&
settings.UseHttps == false &&
settings.HideTopLevelNodeFromPath == false &&
settings.Path == "~/umbraco" &&
settings.TimeOutInMinutes == 20 &&
settings.DefaultUILanguage == "en" &&
settings.ReservedPaths == (GlobalSettings.StaticReservedPaths + "~/umbraco") &&
+2 -2
View File
@@ -36,14 +36,14 @@ namespace Umbraco.Tests.Common
{
SettingsForTests = new SettingsForTests();
MainDom = new SimpleMainDom();
_typeFinder = new TypeFinder(Mock.Of<ILogger>(), new DefaultUmbracoAssemblyProvider(entryAssembly));
_typeFinder = new TypeFinder(Mock.Of<ILogger>(), new DefaultUmbracoAssemblyProvider(entryAssembly), new VaryingRuntimeHash());
}
public ITypeFinder GetTypeFinder() => _typeFinder;
public TypeLoader GetMockedTypeLoader()
{
return new TypeLoader(IOHelper, Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), new DirectoryInfo(IOHelper.MapPath("~/App_Data/TEMP")), Mock.Of<IProfilingLogger>());
return new TypeLoader(Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), new DirectoryInfo(IOHelper.MapPath("~/App_Data/TEMP")), Mock.Of<IProfilingLogger>());
}
public Configs GetConfigs() => GetConfigsFactory().Create();
@@ -106,8 +106,7 @@ namespace Umbraco.Tests.Integration.Implementations
public override IHostingEnvironment GetHostingEnvironment()
=> _hostingEnvironment ??= new TestHostingEnvironment(
SettingsForTests.GetDefaultHostingSettings(),
_hostEnvironment,
_httpContextAccessor);
_hostEnvironment);
public override IApplicationShutdownRegistry GetHostingEnvironmentLifetime() => _hostingLifetime;
@@ -8,8 +8,8 @@ namespace Umbraco.Tests.Integration.Implementations
public class TestHostingEnvironment : AspNetCoreHostingEnvironment, Umbraco.Core.Hosting.IHostingEnvironment
{
public TestHostingEnvironment(IHostingSettings hostingSettings, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContextAccessor)
: base(hostingSettings, webHostEnvironment, httpContextAccessor)
public TestHostingEnvironment(IHostingSettings hostingSettings, IWebHostEnvironment webHostEnvironment)
: base(hostingSettings, webHostEnvironment)
{
}
@@ -56,7 +56,7 @@ namespace Umbraco.Tests.Components
private static TypeLoader MockTypeLoader()
{
var ioHelper = TestHelper.IOHelper;
return new TypeLoader(ioHelper, Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of<IProfilingLogger>());
return new TypeLoader(Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of<IProfilingLogger>());
}
public static IRuntimeState MockRuntimeState(RuntimeLevel level)
@@ -372,7 +372,7 @@ namespace Umbraco.Tests.Components
{
var ioHelper = TestHelper.IOHelper;
var typeFinder = TestHelper.GetTypeFinder();
var typeLoader = new TypeLoader(ioHelper, typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of<IProfilingLogger>());
var typeLoader = new TypeLoader(typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of<IProfilingLogger>());
var register = MockRegister();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(),
@@ -24,7 +24,7 @@ namespace Umbraco.Tests.Composing
var typeFinder = TestHelper.GetTypeFinder();
var ioHelper = TestHelper.IOHelper;
TypeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), ProfilingLogger, false, AssembliesToScan);
TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), ProfilingLogger, false, AssembliesToScan);
}
[TearDown]
@@ -40,7 +40,7 @@ namespace Umbraco.Tests.Composing
var logger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
var typeFinder = TestHelper.GetTypeFinder();
var ioHelper = TestHelper.IOHelper;
var typeLoader = new TypeLoader(ioHelper, typeFinder, Mock.Of<IAppPolicyCache>(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger);
var typeLoader = new TypeLoader(typeFinder, Mock.Of<IAppPolicyCache>(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger);
var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of<IRuntimeState>(), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache);
// create the factory, ensure it is the mocked factory
@@ -57,7 +57,7 @@ namespace Umbraco.Tests.Composing
[Test]
public void Find_Class_Of_Type_With_Attribute()
{
var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash());
var typesFound = typeFinder.FindClassesOfTypeWithAttribute<TestEditor, MyTestAttribute>(_assemblies);
Assert.AreEqual(2, typesFound.Count());
}
@@ -65,7 +65,7 @@ namespace Umbraco.Tests.Composing
[Test]
public void Find_Classes_With_Attribute()
{
var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash());
var typesFound = typeFinder.FindClassesWithAttribute<TreeAttribute>(_assemblies);
Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree]
+33 -6
View File
@@ -28,7 +28,7 @@ namespace Umbraco.Tests.Composing
{
// this ensures it's reset
var typeFinder = TestHelper.GetTypeFinder();
_typeLoader = new TypeLoader(TestHelper.IOHelper, typeFinder, NoAppCache.Instance,
_typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance,
new DirectoryInfo(TestHelper.IOHelper.MapPath("~/App_Data/TEMP")),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()), false,
@@ -217,7 +217,7 @@ AnotherContentFinder
}
[Test]
public void Get_Plugins_Hash()
public void Get_Plugins_Hash_With_Hash_Generator()
{
//Arrange
var dir = PrepareFolder();
@@ -244,16 +244,16 @@ AnotherContentFinder
var list3 = new[] { f1, f3, f5, f7 };
//Act
var hash1 = TypeLoader.GetFileHash(list1, new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
var hash2 = TypeLoader.GetFileHash(list2, new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
var hash3 = TypeLoader.GetFileHash(list3, new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
var hash1 = GetFileHash(list1, new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
var hash2 = GetFileHash(list2, new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
var hash3 = GetFileHash(list3, new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
//Assert
Assert.AreNotEqual(hash1, hash2);
Assert.AreNotEqual(hash1, hash3);
Assert.AreNotEqual(hash2, hash3);
Assert.AreEqual(hash1, TypeLoader.GetFileHash(list1, new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>())));
Assert.AreEqual(hash1, GetFileHash(list1, new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>())));
}
[Test]
@@ -316,5 +316,32 @@ AnotherContentFinder
}
/// <summary>
/// Returns a unique hash for a combination of FileInfo objects.
/// </summary>
/// <param name="filesAndFolders">A collection of files.</param>
/// <param name="logger">A profiling logger.</param>
/// <returns>The hash.</returns>
// internal for tests
private static string GetFileHash(IEnumerable<FileSystemInfo> filesAndFolders, IProfilingLogger logger)
{
using (logger.DebugDuration<TypeLoader>("Determining hash of code files on disk", "Hash determined"))
{
using (var generator = new HashGenerator())
{
// get the distinct file infos to hash
var uniqInfos = new HashSet<string>();
foreach (var fileOrFolder in filesAndFolders)
{
if (uniqInfos.Contains(fileOrFolder.FullName)) continue;
uniqInfos.Add(fileOrFolder.FullName);
generator.AddFileSystemItem(fileOrFolder);
}
return generator.GenerateHash();
}
}
}
}
}
@@ -36,7 +36,7 @@ namespace Umbraco.Tests.Configurations
var ioHelper = new IOHelper(TestHelper.GetHostingEnvironment(), globalSettings);
var globalSettingsMock = Mock.Get(globalSettings);
globalSettingsMock.Setup(x => x.Path).Returns(() => path);
globalSettingsMock.Setup(x => x.UmbracoPath).Returns(() => path);
ioHelper.Root = rootPath;
Assert.AreEqual(outcome, ioHelper.GetUmbracoMvcAreaNoCache());
@@ -50,7 +50,7 @@ namespace Umbraco.Tests.PublishedContent
{
var baseLoader = base.CreateTypeLoader(ioHelper, typeFinder, runtimeCache, logger, hostingEnvironment);
return new TypeLoader(ioHelper, typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, false,
return new TypeLoader(typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, false,
// this is so the model factory looks into the test assembly
baseLoader.AssembliesToScan
.Union(new[] {typeof(PublishedContentMoreTests).Assembly})
@@ -99,7 +99,7 @@ namespace Umbraco.Tests.PublishedContent
{
var baseLoader = base.CreateTypeLoader(ioHelper, typeFinder, runtimeCache, logger, hostingEnvironment);
return new TypeLoader(ioHelper, typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, false,
return new TypeLoader(typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, false,
// this is so the model factory looks into the test assembly
baseLoader.AssembliesToScan
.Union(new[] { typeof(PublishedContentTests).Assembly })
@@ -69,7 +69,7 @@ namespace Umbraco.Tests.Runtimes
var databaseFactory = new UmbracoDatabaseFactory(logger,globalSettings, connectionStrings, new Lazy<IMapperCollection>(() => factory.GetInstance<IMapperCollection>()), TestHelper.DbProviderFactoryCreator);
var ioHelper = TestHelper.IOHelper;
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger);
var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger);
var mainDom = new SimpleMainDom();
var umbracoVersion = TestHelper.GetUmbracoVersion();
var backOfficeInfo = TestHelper.GetBackOfficeInfo();
@@ -262,7 +262,7 @@ namespace Umbraco.Tests.Runtimes
var databaseFactory = Mock.Of<IUmbracoDatabaseFactory>();
var typeFinder = TestHelper.GetTypeFinder();
var ioHelper = TestHelper.IOHelper;
var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger);
var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger);
var runtimeState = Mock.Of<IRuntimeState>();
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
var backOfficeInfo = TestHelper.GetBackOfficeInfo();
@@ -39,7 +39,7 @@ namespace Umbraco.Tests.TestHelpers
var ioHelper = TestHelper.IOHelper;
var logger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
var typeFinder = TestHelper.GetTypeFinder();
var typeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance,
var typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance,
new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")),
logger,
false);
+1 -1
View File
@@ -253,7 +253,7 @@ namespace Umbraco.Tests.TestHelpers
TestHelper.DbProviderFactoryCreator);
}
typeFinder ??= new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
typeFinder ??= new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash());
fileSystems ??= new FileSystems(Current.Factory, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings());
var coreDebug = TestHelper.CoreDebugSettings;
var mediaFileSystem = Mock.Of<IMediaFileSystem>();
+5 -5
View File
@@ -173,7 +173,7 @@ namespace Umbraco.Tests.Testing
var proflogger = new ProfilingLogger(logger, profiler);
IOHelper = TestHelper.IOHelper;
TypeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
TypeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly), new VaryingRuntimeHash());
var appCaches = GetAppCaches();
var globalSettings = TestHelpers.SettingsForTests.GetDefaultGlobalSettings();
var settings = TestHelpers.SettingsForTests.GenerateMockWebRoutingSettings();
@@ -376,7 +376,7 @@ namespace Umbraco.Tests.Testing
switch (option)
{
case UmbracoTestOptions.TypeLoader.Default:
return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(ioHelper, typeFinder, runtimeCache, logger, hostingEnvironment));
return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(typeFinder, runtimeCache, logger, hostingEnvironment));
case UmbracoTestOptions.TypeLoader.PerFixture:
return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(ioHelper, typeFinder, runtimeCache, logger, hostingEnvironment));
case UmbracoTestOptions.TypeLoader.PerTest:
@@ -388,13 +388,13 @@ namespace Umbraco.Tests.Testing
protected virtual TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IProfilingLogger logger, IHostingEnvironment hostingEnvironment)
{
return CreateCommonTypeLoader(ioHelper, typeFinder, runtimeCache, logger, hostingEnvironment);
return CreateCommonTypeLoader(typeFinder, runtimeCache, logger, hostingEnvironment);
}
// common to all tests = cannot be overriden
private static TypeLoader CreateCommonTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IProfilingLogger logger, IHostingEnvironment hostingEnvironment)
private static TypeLoader CreateCommonTypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IProfilingLogger logger, IHostingEnvironment hostingEnvironment)
{
return new TypeLoader(ioHelper, typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, false, new[]
return new TypeLoader(typeFinder, runtimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), logger, false, new[]
{
Assembly.Load("Umbraco.Core"),
Assembly.Load("Umbraco.Web"),
@@ -33,7 +33,6 @@ namespace Umbraco.Tests.Web
container
.Setup(x => x.GetInstance(typeof(TypeLoader)))
.Returns(new TypeLoader(
ioHelper,
typeFinder,
NoAppCache.Instance,
new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")),
@@ -1,5 +1,6 @@
using System;
using System.Data.Common;
using System.IO;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
@@ -100,10 +101,6 @@ namespace Umbraco.Web.Common.Extensions
var globalSettings = configs.Global();
var umbracoVersion = new UmbracoVersion(globalSettings);
// TODO: Currently we are not passing in any TypeFinderConfig (with ITypeFinderSettings) which we should do, however
// this is not critical right now and would require loading in some config before boot time so just leaving this as-is for now.
var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(entryAssembly));
var coreRuntime = GetCoreRuntime(
configs,
umbracoVersion,
@@ -112,13 +109,23 @@ namespace Umbraco.Web.Common.Extensions
profiler,
hostingEnvironment,
backOfficeInfo,
typeFinder);
CreateTypeFinder(logger, profiler, webHostEnvironment, entryAssembly));
factory = coreRuntime.Configure(container);
return services;
}
private static ITypeFinder CreateTypeFinder(ILogger logger, IProfiler profiler, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly)
{
// TODO: Currently we are not passing in any TypeFinderConfig (with ITypeFinderSettings) which we should do, however
// this is not critical right now and would require loading in some config before boot time so just leaving this as-is for now.
var runtimeHashPaths = new RuntimeHashPaths();
runtimeHashPaths.AddFolder(new DirectoryInfo(Path.Combine(webHostEnvironment.ContentRootPath, "bin")));
var runtimeHash = new RuntimeHash(new ProfilingLogger(logger, profiler), runtimeHashPaths);
return new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(entryAssembly), runtimeHash);
}
private static IRuntime GetCoreRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger,
IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo,
ITypeFinder typeFinder)
@@ -161,7 +168,7 @@ namespace Umbraco.Web.Common.Extensions
var coreDebug = configs.CoreDebug();
var globalSettings = configs.Global();
hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment, httpContextAccessor);
hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment);
ioHelper = new IOHelper(hostingEnvironment, globalSettings);
logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment,
new AspNetCoreSessionIdResolver(httpContextAccessor),
@@ -9,7 +9,8 @@ namespace Umbraco.Web.Common.RuntimeMinification
{
public void Compose(Composition composition)
{
composition.Register<IRuntimeMinifier, SmidgeRuntimeMinifier>(Core.Composing.Lifetime.Scope);
composition.RegisterUnique<IRuntimeMinifier, SmidgeRuntimeMinifier>();
composition.RegisterUnique<SmidgeHelperAccessor>();
}
}
}
@@ -0,0 +1,29 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Smidge;
namespace Umbraco.Web.Common.RuntimeMinification
{
// work around for SmidgeHelper being request/scope lifetime
public sealed class SmidgeHelperAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public SmidgeHelperAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public SmidgeHelper SmidgeHelper
{
get
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
throw new InvalidOperationException($"Cannot get a {nameof(SmidgeHelper)} instance since there is no current http request");
return httpContext.RequestServices.GetService<SmidgeHelper>();
}
}
}
}
@@ -22,15 +22,15 @@ namespace Umbraco.Web.Common.RuntimeMinification
private readonly ISmidgeConfig _smidgeConfig;
private readonly IConfigManipulator _configManipulator;
private readonly PreProcessPipelineFactory _preProcessPipelineFactory;
private readonly BundleManager _bundles;
private readonly SmidgeHelper _smidge;
private readonly IBundleManager _bundles;
private readonly SmidgeHelperAccessor _smidge;
private PreProcessPipeline _jsPipeline;
private PreProcessPipeline _cssPipeline;
public SmidgeRuntimeMinifier(
BundleManager bundles,
SmidgeHelper smidge,
IBundleManager bundles,
SmidgeHelperAccessor smidge,
PreProcessPipelineFactory preProcessPipelineFactory,
IHostingEnvironment hostingEnvironment,
ISmidgeConfig smidgeConfig,
@@ -65,7 +65,7 @@ namespace Umbraco.Web.Common.RuntimeMinification
// affect this or vice versa.
}
public string RenderCssHere(string bundleName) => _smidge.CssHereAsync(bundleName, _hostingEnvironment.IsDebugMode).ToString();
public string RenderCssHere(string bundleName) => _smidge.SmidgeHelper.CssHereAsync(bundleName, _hostingEnvironment.IsDebugMode).ToString();
public void CreateJsBundle(string bundleName, params string[] filePaths)
{
@@ -82,9 +82,9 @@ namespace Umbraco.Web.Common.RuntimeMinification
// affect this or vice versa.
}
public string RenderJsHere(string bundleName) => _smidge.JsHereAsync(bundleName, _hostingEnvironment.IsDebugMode).ToString();
public string RenderJsHere(string bundleName) => _smidge.SmidgeHelper.JsHereAsync(bundleName, _hostingEnvironment.IsDebugMode).ToString();
public async Task<IEnumerable<string>> GetAssetPathsAsync(string bundleName) => await _smidge.GenerateJsUrlsAsync(bundleName, _hostingEnvironment.IsDebugMode);
public async Task<IEnumerable<string>> GetAssetPathsAsync(string bundleName) => await _smidge.SmidgeHelper.GenerateJsUrlsAsync(bundleName, _hostingEnvironment.IsDebugMode);
public async Task<string> MinifyAsync(string fileContent, AssetType assetType)
{
+5 -5
View File
@@ -21,7 +21,7 @@ namespace Umbraco.Web.UI.BackOffice
{
public class Startup
{
private readonly IWebHostEnvironment _webHostEnvironment;
private readonly IWebHostEnvironment _env;
private readonly IConfiguration _config;
/// <summary>
@@ -34,7 +34,7 @@ namespace Umbraco.Web.UI.BackOffice
/// </remarks>
public Startup(IWebHostEnvironment webHostEnvironment, IConfiguration config)
{
_webHostEnvironment = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment));
_env = webHostEnvironment ?? throw new ArgumentNullException(nameof(webHostEnvironment));
_config = config ?? throw new ArgumentNullException(nameof(config));
}
@@ -44,7 +44,7 @@ namespace Umbraco.Web.UI.BackOffice
{
services.AddUmbracoConfiguration(_config);
services.AddUmbracoRuntimeMinifier(_config);
services.AddUmbracoCore(_webHostEnvironment, out var factory);
services.AddUmbracoCore(_env, out var factory);
services.AddUmbracoWebsite();
services.AddMvc();
@@ -66,12 +66,12 @@ namespace Umbraco.Web.UI.BackOffice
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
public void Configure(IApplicationBuilder app)
{
// app.UseMiniProfiler();
app.UseUmbracoRequest();
if (env.IsDevelopment())
if (_env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
+13 -2
View File
@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Web;
@@ -80,9 +81,18 @@ namespace Umbraco.Web
/// </summary>
/// <returns></returns>
protected virtual ITypeFinder GetTypeFinder()
{
// TODO: Currently we are not passing in any TypeFinderConfig (with ITypeFinderSettings) which we should do, however
// this is not critical right now and would require loading in some config before boot time so just leaving this as-is for now.
=> new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(
var runtimeHashPaths = new RuntimeHashPaths();
// the bin folder and everything in it
runtimeHashPaths.AddFolder(new DirectoryInfo(Current.IOHelper.MapPath("~/bin")));
// the app code folder and everything in it
runtimeHashPaths.AddFile(new FileInfo(Current.IOHelper.MapPath("~/App_Code")));
// global.asax (the app domain also monitors this, if it changes will do a full restart)
runtimeHashPaths.AddFile(new FileInfo(Current.IOHelper.MapPath("~/global.asax")));
var runtimeHash = new RuntimeHash(new ProfilingLogger(Current.Logger, Current.Profiler), runtimeHashPaths);
return new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(
// GetEntryAssembly was actually an exposed API by request of the aspnetcore team which works in aspnet core because a website
// in that case is essentially an exe. However in netframework there is no entry assembly, things don't really work that way since
// the process that is running the site is iisexpress, so this returns null. The best we can do is fallback to GetExecutingAssembly()
@@ -94,7 +104,8 @@ namespace Umbraco.Web
// assembly we can get and we can only get that if we put this code into the WebRuntime since the executing assembly is the 'current' one.
// For this purpose, it doesn't matter if it's Umbraco.Web or Umbraco.Infrastructure since all assemblies are in that same path and we are
// getting rid of netframework.
Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()));
Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()), runtimeHash);
}
/// <summary>
/// Gets a runtime.