From 41c2d01078602852f97337a5af360b5404fda0a2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 9 Mar 2020 11:03:42 +1100 Subject: [PATCH 01/10] initial POC code --- src/Umbraco.Core/Composing/TypeFinder.cs | 627 +++++++++++++++--- src/Umbraco.Core/Composing/TypeLoader.cs | 44 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Runtime/CoreRuntime.cs | 3 +- .../Cache/DeepCloneAppCacheTests.cs | 2 +- .../Cache/HttpRequestAppCacheTests.cs | 2 +- .../Cache/ObjectAppCacheTests.cs | 2 +- .../Components/ComponentTests.cs | 4 +- .../Composing/ComposingTestBase.cs | 2 +- .../Composing/CompositionTests.cs | 2 +- .../Composing/TypeFinderTests.cs | 7 +- .../Composing/TypeLoaderTests.cs | 2 +- src/Umbraco.Tests/Macros/MacroTests.cs | 2 +- src/Umbraco.Tests/Models/ContentTests.cs | 2 +- .../Published/PropertyCacheLevelTests.cs | 2 +- .../PublishedContent/NuCacheChildrenTests.cs | 2 +- .../PublishedContent/NuCacheTests.cs | 2 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 4 +- .../Scoping/ScopedNuCacheTests.cs | 2 +- .../ContentTypeServiceVariantsTests.cs | 2 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 2 +- .../Stubs/TestControllerFactory.cs | 2 +- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 2 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 2 +- src/Umbraco.Tests/Web/UmbracoHelperTests.cs | 2 +- .../Composing/BuildManagerTypeFinder.cs | 5 +- src/Umbraco.Web/Runtime/WebRuntime.cs | 2 +- 27 files changed, 591 insertions(+), 142 deletions(-) diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 9d88153b0a..2f2d81a72c 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -1,15 +1,16 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Configuration; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Security; using System.Text; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Core.Composing @@ -18,91 +19,26 @@ namespace Umbraco.Core.Composing public class TypeFinder : ITypeFinder { private readonly ILogger _logger; + private readonly IAssemblyProvider _assemblyProvider; - public TypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) + //public TypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) + // : this(logger, new DefaultUmbracoAssemblyProvider(Assembly.GetEntryAssembly()?.GetName()?.Name), typeFinderConfig) + //{ + //} + + public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig typeFinderConfig = null) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty(); - _allAssemblies = new Lazy>(() => - { - HashSet assemblies = null; - try - { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = GetRootDirectorySafe(); - var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new HashSet(); - foreach (var a in binAssemblyFiles) - { - try - { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); - } - catch (Exception e) - { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } - } - } - - //Since we are only loading in the /bin assemblies above, we will also load in anything that's already loaded (which will include gac items) - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - { - assemblies.Add(a); - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - return assemblies; - }); + _assemblyProvider = assemblyProvider; + _assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty(); } - //Lazy access to the all assemblies list - private readonly Lazy> _allAssemblies; private volatile HashSet _localFilteredAssemblyCache; private readonly object _localFilteredAssemblyCacheLocker = new object(); private readonly List _notifiedLoadExceptionAssemblies = new List(); - private static readonly ConcurrentDictionary TypeNamesCache= new ConcurrentDictionary(); - private string _rootDir = ""; + private static readonly ConcurrentDictionary TypeNamesCache= new ConcurrentDictionary(); private readonly string[] _assembliesAcceptingLoadExceptions; - // FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here - private string GetRootDirectorySafe() - { - if (string.IsNullOrEmpty(_rootDir) == false) - { - return _rootDir; - } - - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - var baseDirectory = Path.GetDirectoryName(path); - if (string.IsNullOrEmpty(baseDirectory)) - throw new PanicException("No root directory could be resolved."); - - _rootDir = baseDirectory.Contains("bin") - ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) - : baseDirectory; - - return _rootDir; - } - private bool AcceptsLoadExceptions(Assembly a) { if (_assembliesAcceptingLoadExceptions.Length == 0) @@ -119,22 +55,8 @@ namespace Umbraco.Core.Composing }); } - /// - /// lazily load a reference to all assemblies and only local assemblies. - /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder - /// - /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been - /// loaded in the CLR, not all assemblies. - /// See these threads: - /// http://issues.umbraco.org/issue/U5-198 - /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app - /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl - /// - private IEnumerable GetAllAssemblies() - { - return _allAssemblies.Value; - } + + private IEnumerable GetAllAssemblies() => _assemblyProvider.Assemblies; /// public IEnumerable AssembliesToScan @@ -522,6 +444,525 @@ namespace Umbraco.Core.Composing #endregion + } + /// + /// lazily load a reference to all local assemblies and gac assemblies + /// + /// + /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder + /// + /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been + /// loaded in the CLR, not all assemblies. + /// See these threads: + /// http://issues.umbraco.org/issue/U5-198 + /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app + /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl + /// + public class BruteForceAssemblyProvider : IAssemblyProvider + { + public BruteForceAssemblyProvider() + { + _allAssemblies = new Lazy>(() => + { + HashSet assemblies = null; + try + { + //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have + // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. + var binFolder = GetRootDirectorySafe(); + var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + assemblies = new HashSet(); + foreach (var a in binAssemblyFiles) + { + try + { + var assName = AssemblyName.GetAssemblyName(a); + var ass = Assembly.Load(assName); + assemblies.Add(ass); + } + catch (Exception e) + { + if (e is SecurityException || e is BadImageFormatException) + { + //swallow these exceptions + } + else + { + throw; + } + } + } + + //Since we are only loading in the /bin assemblies above, we will also load in anything that's already loaded (which will include gac items) + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + assemblies.Add(a); + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + return assemblies; + }); + } + + private readonly Lazy> _allAssemblies; + private string _rootDir = string.Empty; + + public IEnumerable Assemblies => _allAssemblies.Value; + + // FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here + private string GetRootDirectorySafe() + { + if (string.IsNullOrEmpty(_rootDir) == false) + { + return _rootDir; + } + + var codeBase = Assembly.GetExecutingAssembly().CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + var baseDirectory = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(baseDirectory)) + throw new PanicException("No root directory could be resolved."); + + _rootDir = baseDirectory.Contains("bin") + ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) + : baseDirectory; + + return _rootDir; + } + } + + /// + /// Provides a list of loaded assemblies that can be scanned + /// + public interface IAssemblyProvider + { + IEnumerable Assemblies { get; } + } + + /// + /// Discovers assemblies that are part of the Umbraco application using the DependencyContext. + /// + /// + /// Happily "borrowed" from aspnet: https://github.com/aspnet/Mvc/blob/230a13d0e13e4c7e192bc6623762bfd4cde726ef/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultAssemblyPartDiscoveryProvider.cs + /// + /// TODO: Happily borrow their unit tests too + /// + public class DefaultUmbracoAssemblyProvider : IAssemblyProvider + { + private readonly string _entryPointAssemblyName; + + public DefaultUmbracoAssemblyProvider(string entryPointAssemblyName) + { + if (string.IsNullOrWhiteSpace(entryPointAssemblyName)) + throw new ArgumentException($"{entryPointAssemblyName} cannot be null or empty", nameof(entryPointAssemblyName)); + + _entryPointAssemblyName = entryPointAssemblyName; + } + + public IEnumerable Assemblies + { + get + { + var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssemblyName }, new[] { "Umbraco.Core" }); + foreach(var found in finder.Find()) + { + yield return Assembly.Load(found); + } + } + } + + //public IEnumerable Assemblies => DiscoverAssemblyParts(_entryPointAssemblyName); + + //internal static HashSet ReferenceAssemblies { get; } = new HashSet(StringComparer.OrdinalIgnoreCase) + //{ + // "Umbraco.Core", + // "Umbraco.Web" + //}; + + //internal static IEnumerable DiscoverAssemblyParts(string entryPointAssemblyName) + //{ + // var entryAssembly = Assembly.Load(new AssemblyName(entryPointAssemblyName)); + // var context = DependencyContext.Load(entryAssembly); + + // var candidates = GetCandidateAssemblies(entryAssembly, context); + + // return candidates; + //} + + //internal static IEnumerable GetCandidateAssemblies(Assembly entryAssembly, DependencyContext dependencyContext) + //{ + // if (dependencyContext == null) + // { + // // Use the entry assembly as the sole candidate. + // return new[] { entryAssembly }; + // } + + // //includeRefLibs == true - so that Umbraco.Core is also returned! + // return GetCandidateLibraries(dependencyContext, includeRefLibs: true) + // .SelectMany(library => library.GetDefaultAssemblyNames(dependencyContext)) + // .Select(Assembly.Load); + //} + + ///// + ///// Returns a list of libraries that references the assemblies in . + ///// + ///// + ///// + ///// True to also include libs in the ReferenceAssemblies list + ///// + ///// + //internal static IEnumerable GetCandidateLibraries(DependencyContext dependencyContext, bool includeRefLibs) + //{ + // if (ReferenceAssemblies == null) + // { + // return Enumerable.Empty(); + // } + + // var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies, includeRefLibs); + // return candidatesResolver.GetCandidates(); + //} + + //private class CandidateResolver + //{ + // private readonly bool _includeRefLibs; + // private readonly IDictionary _dependencies; + + // /// + // /// Constructor + // /// + // /// + // /// + // /// + // /// True to also include libs in the ReferenceAssemblies list + // /// + // public CandidateResolver(IEnumerable dependencies, ISet referenceAssemblies, bool includeRefLibs) + // { + // _includeRefLibs = includeRefLibs; + + // _dependencies = dependencies + // .ToDictionary(d => d.Name, d => CreateDependency(d, referenceAssemblies), StringComparer.OrdinalIgnoreCase); + // } + + // /// + // /// Create a Dependency + // /// + // /// + // /// + // /// + // private static Dependency CreateDependency(RuntimeLibrary library, ISet referenceAssemblies) + // { + // var classification = DependencyClassification.Unknown; + // if (referenceAssemblies.Contains(library.Name)) + // { + // classification = DependencyClassification.UmbracoReference; + // } + + // return new Dependency(library, classification); + // } + + // private DependencyClassification ComputeClassification(string dependency) + // { + // Debug.Assert(_dependencies.ContainsKey(dependency)); + + // var candidateEntry = _dependencies[dependency]; + // if (candidateEntry.Classification != DependencyClassification.Unknown) + // { + // return candidateEntry.Classification; + // } + // else + // { + // var classification = DependencyClassification.NotCandidate; + // foreach (var candidateDependency in candidateEntry.Library.Dependencies) + // { + // var dependencyClassification = ComputeClassification(candidateDependency.Name); + // if (dependencyClassification == DependencyClassification.Candidate || + // dependencyClassification == DependencyClassification.UmbracoReference) + // { + // classification = DependencyClassification.Candidate; + // break; + // } + // } + + // candidateEntry.Classification = classification; + + // return classification; + // } + // } + + // public IEnumerable GetCandidates() + // { + // foreach (var dependency in _dependencies) + // { + // var classification = ComputeClassification(dependency.Key); + // if (classification == DependencyClassification.Candidate || + // //if the flag is set, also ensure to include any UmbracoReference classifications + // (_includeRefLibs && classification == DependencyClassification.UmbracoReference)) + // { + // yield return dependency.Value.Library; + // } + // } + // } + + // private class Dependency + // { + // public Dependency(RuntimeLibrary library, DependencyClassification classification) + // { + // Library = library; + // Classification = classification; + // } + + // public RuntimeLibrary Library { get; } + + // public DependencyClassification Classification { get; set; } + + // public override string ToString() + // { + // return $"Library: {Library.Name}, Classification: {Classification}"; + // } + // } + + // private enum DependencyClassification + // { + // Unknown = 0, + // Candidate = 1, + // NotCandidate = 2, + // UmbracoReference = 3 + // } + //} + } + + /// + /// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively. + /// + public class ReferenceResolver + { + private readonly HashSet _mvcAssemblies; + private readonly IReadOnlyList _assemblyItems; + private readonly Dictionary _classifications; + + public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList assemblyItems) + { + _mvcAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); + _assemblyItems = assemblyItems; + _classifications = new Dictionary(); + + Lookup = new Dictionary(StringComparer.Ordinal); + foreach (var item in assemblyItems) + { + Lookup[item.AssemblyName] = item; + } + } + + protected Dictionary Lookup { get; } + + public IReadOnlyList ResolveAssemblies() + { + var applicationParts = new List(); + + foreach (var item in _assemblyItems) + { + var classification = Resolve(item); + if (classification == Classification.ReferencesMvc) + { + applicationParts.Add(item.AssemblyName); + } + } + + return applicationParts; + } + + private Classification Resolve(AssemblyItem assemblyItem) + { + if (_classifications.TryGetValue(assemblyItem, out var classification)) + { + return classification; + } + + // Initialize the dictionary with a value to short-circuit recursive references. + classification = Classification.Unknown; + _classifications[assemblyItem] = classification; + + if (assemblyItem.Path == null) + { + // We encountered a dependency that isn't part of this assembly's dependency set. We'll see if it happens to be an MVC assembly + // since that's the only useful determination we can make given the assembly name. + classification = _mvcAssemblies.Contains(assemblyItem.AssemblyName) ? + Classification.IsMvc : + Classification.DoesNotReferenceMvc; + } + else if (assemblyItem.IsFrameworkReference) + { + // We do not allow transitive references to MVC via a framework reference to count. + // e.g. depending on Microsoft.AspNetCore.SomeThingNewThatDependsOnMvc would not result in an assembly being treated as + // referencing MVC. + classification = _mvcAssemblies.Contains(assemblyItem.AssemblyName) ? + Classification.IsMvc : + Classification.DoesNotReferenceMvc; + } + else if (_mvcAssemblies.Contains(assemblyItem.AssemblyName)) + { + classification = Classification.IsMvc; + } + else + { + classification = Classification.DoesNotReferenceMvc; + foreach (var reference in GetReferences(assemblyItem.Path)) + { + var referenceClassification = Resolve(reference); + + if (referenceClassification == Classification.IsMvc || referenceClassification == Classification.ReferencesMvc) + { + classification = Classification.ReferencesMvc; + break; + } + } + } + + Debug.Assert(classification != Classification.Unknown); + _classifications[assemblyItem] = classification; + return classification; + } + + protected virtual IReadOnlyList GetReferences(string file) + { + try + { + if (!File.Exists(file)) + { + throw new ReferenceAssemblyNotFoundException(file); + } + + using var peReader = new PEReader(File.OpenRead(file)); + if (!peReader.HasMetadata) + { + return Array.Empty(); // not a managed assembly + } + + var metadataReader = peReader.GetMetadataReader(); + + var references = new List(); + foreach (var handle in metadataReader.AssemblyReferences) + { + var reference = metadataReader.GetAssemblyReference(handle); + var referenceName = metadataReader.GetString(reference.Name); + + if (!Lookup.TryGetValue(referenceName, out var assemblyItem)) + { + // A dependency references an item that isn't referenced by this project. + // We'll construct an item for so that we can calculate the classification based on it's name. + assemblyItem = new AssemblyItem + { + AssemblyName = referenceName, + }; + + Lookup[referenceName] = assemblyItem; + } + + references.Add(assemblyItem); + } + + return references; + } + catch (BadImageFormatException) + { + // not a PE file, or invalid metadata + } + + return Array.Empty(); // not a managed assembly + } + + protected enum Classification + { + Unknown, + DoesNotReferenceMvc, + ReferencesMvc, + IsMvc, + } + } + + public class AssemblyItem + { + public string Path { get; set; } + + public bool IsFrameworkReference { get; set; } + + public string AssemblyName { get; set; } + } + + internal class ReferenceAssemblyNotFoundException : Exception + { + public ReferenceAssemblyNotFoundException(string fileName) + { + FileName = fileName; + } + + public string FileName { get; } + } + + // borrowed from here https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/FindAssembliesWithReferencesTo.cs + public class FindAssembliesWithReferencesTo + { + private readonly string[] _referenceAssemblies; + private readonly string[] _targetAssemblyNames; + + public FindAssembliesWithReferencesTo(string[] referenceAssemblies, string[] targetAssemblyNames) + { + _referenceAssemblies = referenceAssemblies; + _targetAssemblyNames = targetAssemblyNames; + } + + public IEnumerable Find() + { + var referenceItems = new List(); + foreach (var item in _referenceAssemblies) + { + //var assemblyName = new AssemblyName(item).Name; + var assembly = Assembly.Load(item); + referenceItems.Add(new AssemblyItem + { + AssemblyName = assembly.GetName().Name, //assemblyName, + IsFrameworkReference = false, + Path = GetAssemblyLocation(assembly) + }); + } + + var provider = new ReferenceResolver(_targetAssemblyNames, referenceItems); + try + { + var assemblyNames = provider.ResolveAssemblies(); + return assemblyNames.ToArray(); + } + catch (ReferenceAssemblyNotFoundException ex) + { + throw; + //// Print a warning and return. We cannot produce a correct document at this point. + //var warning = "Reference assembly {0} could not be found. This is typically caused by build errors in referenced projects."; + //Log.LogWarning(null, "RAZORSDK1007", null, null, 0, 0, 0, 0, warning, ex.FileName); + //return true; + } + catch (Exception ex) + { + throw; + //Log.LogErrorFromException(ex); + } + } + + internal static string GetAssemblyLocation(Assembly assembly) + { + if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) && + result.IsFile && string.IsNullOrWhiteSpace(result.Fragment)) + { + return result.LocalPath; + } + + return assembly.Location; + } } } diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 76d00c472d..4d8b5c984c 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -516,29 +516,29 @@ namespace Umbraco.Core.Composing #region Get Assembly Attributes - /// - /// Gets the assembly attributes of the specified type . - /// - /// The attribute type. - /// - /// The assembly attributes of the specified type . - /// - public IEnumerable GetAssemblyAttributes() - where T : Attribute - { - return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); - } + ///// + ///// Gets the assembly attributes of the specified type . + ///// + ///// The attribute type. + ///// + ///// The assembly attributes of the specified type . + ///// + //public IEnumerable GetAssemblyAttributes() + // where T : Attribute + //{ + // return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + //} - /// - /// Gets all the assembly attributes. - /// - /// - /// All assembly attributes. - /// - public IEnumerable GetAssemblyAttributes() - { - return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); - } + ///// + ///// Gets all the assembly attributes. + ///// + ///// + ///// All assembly attributes. + ///// + //public IEnumerable GetAssemblyAttributes() + //{ + // return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList(); + //} /// /// Gets the assembly attributes of the specified . diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7a15e7fbed..49d5090070 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 8e4401495d..2422e6018f 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Reflection; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; @@ -369,7 +370,7 @@ namespace Umbraco.Core.Runtime /// /// protected virtual ITypeFinder GetTypeFinder() - => new TypeFinder(Logger); + => new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(Assembly.GetEntryAssembly()?.GetName()?.Name)); /// diff --git a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index 3a504ad4e2..bba7ea0061 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); _memberCache = new ObjectCacheAppCache(typeFinder); _provider = new DeepCloneAppCache(_memberCache); diff --git a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs index b9c948c1de..d2d3b795d6 100644 --- a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); _ctx = new FakeHttpContextFactory("http://localhost/test"); _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items, typeFinder); } diff --git a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs index 772e92cabe..5d6808f678 100644 --- a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); _provider = new ObjectCacheAppCache(typeFinder); } diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index bef72e5fb7..9d44aa562e 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Components var mock = new Mock(); var logger = Mock.Of(); - var typeFinder = new TypeFinder(logger); + var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(typeof(ComponentTests).Assembly.GetName().Name)); var f = new UmbracoDatabaseFactory(logger, new Lazy(() => new MapperCollection(Enumerable.Empty())), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator); var fs = new FileSystems(mock.Object, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings()); var coreDebug = Mock.Of(); @@ -371,7 +371,7 @@ namespace Umbraco.Tests.Components public void AllComposers() { var ioHelper = TestHelper.IOHelper; - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var typeLoader = new TypeLoader(ioHelper, typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of()); var register = MockRegister(); diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index ac7dd8be2a..c4d048cd7b 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.Composing { ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var ioHelper = TestHelper.IOHelper; TypeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), ProfilingLogger, false, AssembliesToScan); } diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs index 4dfaf6871d..a720fe045f 100644 --- a/src/Umbraco.Tests/Composing/CompositionTests.cs +++ b/src/Umbraco.Tests/Composing/CompositionTests.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Composing .Returns(() => factoryFactory?.Invoke(mockedFactory)); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var ioHelper = TestHelper.IOHelper; var typeLoader = new TypeLoader(ioHelper, typeFinder, Mock.Of(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger); var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of(), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index 5fe4c241d6..969be8c0fd 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -57,7 +57,7 @@ namespace Umbraco.Tests.Composing [Test] public void Find_Class_Of_Type_With_Attribute() { - var typeFinder = new TypeFinder(GetTestProfilingLogger()); + var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var typesFound = typeFinder.FindClassesOfTypeWithAttribute(_assemblies); Assert.AreEqual(2, typesFound.Count()); } @@ -65,12 +65,15 @@ namespace Umbraco.Tests.Composing [Test] public void Find_Classes_With_Attribute() { - var typeFinder = new TypeFinder(GetTestProfilingLogger()); + var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var typesFound = typeFinder.FindClassesWithAttribute(_assemblies); Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] typesFound = typeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] + + typesFound = typeFinder.FindClassesWithAttribute(); + Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] } private static IProfilingLogger GetTestProfilingLogger() diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 6658c689e1..2d6f1b85e8 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); _typeLoader = new TypeLoader(TestHelper.IOHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(TestHelper.IOHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()), false, diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index 66adfc4a97..3cc77b6732 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -17,7 +17,7 @@ namespace Umbraco.Tests.Macros [SetUp] public void Setup() { - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); //we DO want cache enabled for these tests var cacheHelper = new AppCaches( new ObjectCacheAppCache(typeFinder), diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 7dec69e3be..9a7e25944f 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -269,7 +269,7 @@ namespace Umbraco.Tests.Models content.UpdateDate = DateTime.Now; content.WriterId = 23; - var runtimeCache = new ObjectCacheAppCache(new TypeFinder(Mock.Of())); + var runtimeCache = new ObjectCacheAppCache(new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name))); runtimeCache.Insert(content.Id.ToString(CultureInfo.InvariantCulture), () => content); var proflog = GetTestProfilingLogger(); diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index d3e6dae26b..329626a984 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -127,7 +127,7 @@ namespace Umbraco.Tests.Published var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var elementsCache = new FastDictionaryAppCache(typeFinder); var snapshotCache = new FastDictionaryAppCache(typeFinder); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index de12fcf7aa..d868e8bc2e 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -143,7 +143,7 @@ namespace Umbraco.Tests.PublishedContent // create a data source for NuCache _source = new TestDataSource(kits); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var settings = Mock.Of(); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 08da25ba9a..cf2be84f24 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -184,7 +184,7 @@ namespace Umbraco.Tests.PublishedContent // create a variation accessor _variationAccesor = new TestVariationContextAccessor(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var settings = Mock.Of(); // at last, create the complete NuCache snapshot service! diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 6811f9f8de..b97aae2e7e 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -63,7 +63,7 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance()), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator); - var typeFinder = new TypeFinder(logger); + var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var ioHelper = TestHelper.IOHelper; var hostingEnvironment = Mock.Of(); var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger); @@ -256,7 +256,7 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = Mock.Of(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var ioHelper = TestHelper.IOHelper; var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger); var runtimeState = Mock.Of(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 783beafb2e..5cc10d735d 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -85,7 +85,7 @@ namespace Umbraco.Tests.Scoping var memberRepository = Mock.Of(); var hostingEnvironment = TestHelper.GetHostingEnvironment(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var settings = Mock.Of(); return new PublishedSnapshotService( diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 1b6b632a10..a2855aa1bd 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -58,7 +58,7 @@ namespace Umbraco.Tests.Services var memberRepository = Mock.Of(); var hostingEnvironment = Mock.Of(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var settings = Mock.Of(); return new PublishedSnapshotService( diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index 2bd36947c6..0f54725246 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.TestHelpers var ioHelper = TestHelper.IOHelper; var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var typeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger, diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index c79a0f5c47..773ef85b1b 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs { if (_factory != null) return _factory(requestContext); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var types = typeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index f237fb0fc7..32b891f2eb 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -247,7 +247,7 @@ namespace Umbraco.Tests.TestHelpers databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, logger, new Lazy(() => mappers), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator); } - typeFinder = typeFinder ?? new TypeFinder(logger); + typeFinder = typeFinder ?? new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings()); var coreDebug = Current.Configs.CoreDebug(); var mediaFileSystem = Mock.Of(); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index de0db554f3..f064e7ffa9 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -171,7 +171,7 @@ namespace Umbraco.Tests.Testing var proflogger = new ProfilingLogger(logger, profiler); IOHelper = TestHelper.IOHelper; - TypeFinder = new TypeFinder(logger); + TypeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var appCaches = GetAppCaches(); var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); var settings = SettingsForTests.GetDefaultUmbracoSettings(); diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index b479961896..e4ae4c9a58 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Web { // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); - var typeFinder = new TypeFinder(Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); var ioHelper = TestHelper.IOHelper; container .Setup(x => x.GetInstance(typeof(TypeLoader))) diff --git a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs index 994e8c26c4..bd583bf29d 100644 --- a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs +++ b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs @@ -27,7 +27,8 @@ namespace Umbraco.Web.Composing IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILogger logger, - ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig) + IAssemblyProvider assemblyProvider, + ITypeFinderConfig typeFinderConfig = null) : base(logger, assemblyProvider, typeFinderConfig) { if (ioHelper == null) throw new ArgumentNullException(nameof(ioHelper)); if (hostingEnvironment == null) throw new ArgumentNullException(nameof(hostingEnvironment)); @@ -60,6 +61,8 @@ namespace Umbraco.Web.Composing logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code"); } } + + return assemblies; } } catch (InvalidOperationException e) diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 582f35db70..5fab9ff9d4 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -92,7 +92,7 @@ namespace Umbraco.Web.Runtime #region Getters - protected override ITypeFinder GetTypeFinder() => _typeFinder ?? (_typeFinder = new BuildManagerTypeFinder(IOHelper, HostingEnvironment, Logger, new BuildManagerTypeFinder.TypeFinderConfig(new TypeFinderSettings()))); + //protected override ITypeFinder GetTypeFinder() => _typeFinder ?? (_typeFinder = new BuildManagerTypeFinder(IOHelper, HostingEnvironment, Logger, new BuildManagerTypeFinder.TypeFinderConfig(new TypeFinderSettings()))); protected override AppCaches GetAppCaches() => new AppCaches( // we need to have the dep clone runtime cache provider to ensure From 0c8426f308fc2ace625195c68f6d0a55e74c2de5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 9 Mar 2020 13:14:16 +1100 Subject: [PATCH 02/10] Cleans up POC code (still more to do) --- src/Umbraco.Core/Composing/TypeFinder.cs | 407 ++++-------------- .../Runtime/CoreRuntime.cs | 2 +- .../Cache/DeepCloneAppCacheTests.cs | 2 +- .../Cache/HttpRequestAppCacheTests.cs | 2 +- .../Cache/ObjectAppCacheTests.cs | 2 +- .../Components/ComponentTests.cs | 4 +- .../Composing/ComposingTestBase.cs | 2 +- .../Composing/CompositionTests.cs | 2 +- .../Composing/TypeFinderTests.cs | 4 +- .../Composing/TypeLoaderTests.cs | 2 +- src/Umbraco.Tests/Macros/MacroTests.cs | 2 +- src/Umbraco.Tests/Models/ContentTests.cs | 2 +- .../Published/PropertyCacheLevelTests.cs | 2 +- .../PublishedContent/NuCacheChildrenTests.cs | 2 +- .../PublishedContent/NuCacheTests.cs | 2 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 4 +- .../Scoping/ScopedNuCacheTests.cs | 2 +- .../ContentTypeServiceVariantsTests.cs | 2 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 2 +- .../Stubs/TestControllerFactory.cs | 2 +- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 2 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 2 +- src/Umbraco.Tests/Web/UmbracoHelperTests.cs | 2 +- 23 files changed, 112 insertions(+), 345 deletions(-) diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 2f2d81a72c..a0aab7c088 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -103,7 +103,8 @@ namespace Umbraco.Core.Composing /// NOTE the comma vs period... comma delimits the name in an Assembly FullName property so if it ends with comma then its an exact name match /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" /// - private static readonly string[] KnownAssemblyExclusionFilter = { + internal static readonly string[] KnownAssemblyExclusionFilter = { + "mscorlib", "Antlr3.", "AutoMapper,", "AutoMapper.", @@ -539,7 +540,7 @@ namespace Umbraco.Core.Composing } /// - /// Provides a list of loaded assemblies that can be scanned + /// Provides a list of assemblies that can be scanned /// public interface IAssemblyProvider { @@ -547,238 +548,77 @@ namespace Umbraco.Core.Composing } /// - /// Discovers assemblies that are part of the Umbraco application using the DependencyContext. + /// Returns a list of scannable assemblies based on an entry point assembly and it's references /// /// - /// Happily "borrowed" from aspnet: https://github.com/aspnet/Mvc/blob/230a13d0e13e4c7e192bc6623762bfd4cde726ef/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultAssemblyPartDiscoveryProvider.cs - /// - /// TODO: Happily borrow their unit tests too + /// This will recursively search through the entry point's assemblies and Umbraco's core assemblies (Core/Web) and their references + /// to create a list of scannable assemblies based on whether they themselves or their transitive dependencies reference Umbraco core assemblies. /// public class DefaultUmbracoAssemblyProvider : IAssemblyProvider { - private readonly string _entryPointAssemblyName; + private readonly Assembly _entryPointAssembly; + private static readonly string[] UmbracoCoreAssemblyNames = new[] { "Umbraco.Core", "Umbraco.Web" }; - public DefaultUmbracoAssemblyProvider(string entryPointAssemblyName) + public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly) { - if (string.IsNullOrWhiteSpace(entryPointAssemblyName)) - throw new ArgumentException($"{entryPointAssemblyName} cannot be null or empty", nameof(entryPointAssemblyName)); - - _entryPointAssemblyName = entryPointAssemblyName; + _entryPointAssembly = entryPointAssembly ?? throw new ArgumentNullException(nameof(entryPointAssembly)); } public IEnumerable Assemblies { get { - var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssemblyName }, new[] { "Umbraco.Core" }); + var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, UmbracoCoreAssemblyNames, true); foreach(var found in finder.Find()) { - yield return Assembly.Load(found); + yield return found; } } } - - //public IEnumerable Assemblies => DiscoverAssemblyParts(_entryPointAssemblyName); - - //internal static HashSet ReferenceAssemblies { get; } = new HashSet(StringComparer.OrdinalIgnoreCase) - //{ - // "Umbraco.Core", - // "Umbraco.Web" - //}; - - //internal static IEnumerable DiscoverAssemblyParts(string entryPointAssemblyName) - //{ - // var entryAssembly = Assembly.Load(new AssemblyName(entryPointAssemblyName)); - // var context = DependencyContext.Load(entryAssembly); - - // var candidates = GetCandidateAssemblies(entryAssembly, context); - - // return candidates; - //} - - //internal static IEnumerable GetCandidateAssemblies(Assembly entryAssembly, DependencyContext dependencyContext) - //{ - // if (dependencyContext == null) - // { - // // Use the entry assembly as the sole candidate. - // return new[] { entryAssembly }; - // } - - // //includeRefLibs == true - so that Umbraco.Core is also returned! - // return GetCandidateLibraries(dependencyContext, includeRefLibs: true) - // .SelectMany(library => library.GetDefaultAssemblyNames(dependencyContext)) - // .Select(Assembly.Load); - //} - - ///// - ///// Returns a list of libraries that references the assemblies in . - ///// - ///// - ///// - ///// True to also include libs in the ReferenceAssemblies list - ///// - ///// - //internal static IEnumerable GetCandidateLibraries(DependencyContext dependencyContext, bool includeRefLibs) - //{ - // if (ReferenceAssemblies == null) - // { - // return Enumerable.Empty(); - // } - - // var candidatesResolver = new CandidateResolver(dependencyContext.RuntimeLibraries, ReferenceAssemblies, includeRefLibs); - // return candidatesResolver.GetCandidates(); - //} - - //private class CandidateResolver - //{ - // private readonly bool _includeRefLibs; - // private readonly IDictionary _dependencies; - - // /// - // /// Constructor - // /// - // /// - // /// - // /// - // /// True to also include libs in the ReferenceAssemblies list - // /// - // public CandidateResolver(IEnumerable dependencies, ISet referenceAssemblies, bool includeRefLibs) - // { - // _includeRefLibs = includeRefLibs; - - // _dependencies = dependencies - // .ToDictionary(d => d.Name, d => CreateDependency(d, referenceAssemblies), StringComparer.OrdinalIgnoreCase); - // } - - // /// - // /// Create a Dependency - // /// - // /// - // /// - // /// - // private static Dependency CreateDependency(RuntimeLibrary library, ISet referenceAssemblies) - // { - // var classification = DependencyClassification.Unknown; - // if (referenceAssemblies.Contains(library.Name)) - // { - // classification = DependencyClassification.UmbracoReference; - // } - - // return new Dependency(library, classification); - // } - - // private DependencyClassification ComputeClassification(string dependency) - // { - // Debug.Assert(_dependencies.ContainsKey(dependency)); - - // var candidateEntry = _dependencies[dependency]; - // if (candidateEntry.Classification != DependencyClassification.Unknown) - // { - // return candidateEntry.Classification; - // } - // else - // { - // var classification = DependencyClassification.NotCandidate; - // foreach (var candidateDependency in candidateEntry.Library.Dependencies) - // { - // var dependencyClassification = ComputeClassification(candidateDependency.Name); - // if (dependencyClassification == DependencyClassification.Candidate || - // dependencyClassification == DependencyClassification.UmbracoReference) - // { - // classification = DependencyClassification.Candidate; - // break; - // } - // } - - // candidateEntry.Classification = classification; - - // return classification; - // } - // } - - // public IEnumerable GetCandidates() - // { - // foreach (var dependency in _dependencies) - // { - // var classification = ComputeClassification(dependency.Key); - // if (classification == DependencyClassification.Candidate || - // //if the flag is set, also ensure to include any UmbracoReference classifications - // (_includeRefLibs && classification == DependencyClassification.UmbracoReference)) - // { - // yield return dependency.Value.Library; - // } - // } - // } - - // private class Dependency - // { - // public Dependency(RuntimeLibrary library, DependencyClassification classification) - // { - // Library = library; - // Classification = classification; - // } - - // public RuntimeLibrary Library { get; } - - // public DependencyClassification Classification { get; set; } - - // public override string ToString() - // { - // return $"Library: {Library.Name}, Classification: {Classification}"; - // } - // } - - // private enum DependencyClassification - // { - // Unknown = 0, - // Candidate = 1, - // NotCandidate = 2, - // UmbracoReference = 3 - // } - //} } /// /// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively. /// - public class ReferenceResolver + /// + /// Borrowed and modified from https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/ReferenceResolver.cs + /// + internal class ReferenceResolver { - private readonly HashSet _mvcAssemblies; - private readonly IReadOnlyList _assemblyItems; - private readonly Dictionary _classifications; + private readonly HashSet _umbracoAssemblies; + private readonly IReadOnlyList _assemblyItems; + private readonly Dictionary _classifications; + private readonly List _lookup = new List(); - public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList assemblyItems) + public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList assemblyItems) { - _mvcAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); + _umbracoAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); _assemblyItems = assemblyItems; - _classifications = new Dictionary(); + _classifications = new Dictionary(); - Lookup = new Dictionary(StringComparer.Ordinal); foreach (var item in assemblyItems) { - Lookup[item.AssemblyName] = item; + _lookup.Add(item); } } - protected Dictionary Lookup { get; } - - public IReadOnlyList ResolveAssemblies() + public IEnumerable ResolveAssemblies() { - var applicationParts = new List(); + var applicationParts = new List(); foreach (var item in _assemblyItems) { var classification = Resolve(item); - if (classification == Classification.ReferencesMvc) + if (classification == Classification.ReferencesUmbraco || classification == Classification.IsUmbraco) { - applicationParts.Add(item.AssemblyName); + applicationParts.Add(item); } } return applicationParts; } - private Classification Resolve(AssemblyItem assemblyItem) + private Classification Resolve(Assembly assemblyItem) { if (_classifications.TryGetValue(assemblyItem, out var classification)) { @@ -789,37 +629,21 @@ namespace Umbraco.Core.Composing classification = Classification.Unknown; _classifications[assemblyItem] = classification; - if (assemblyItem.Path == null) + if (_umbracoAssemblies.Contains(assemblyItem.GetName().Name)) { - // We encountered a dependency that isn't part of this assembly's dependency set. We'll see if it happens to be an MVC assembly - // since that's the only useful determination we can make given the assembly name. - classification = _mvcAssemblies.Contains(assemblyItem.AssemblyName) ? - Classification.IsMvc : - Classification.DoesNotReferenceMvc; - } - else if (assemblyItem.IsFrameworkReference) - { - // We do not allow transitive references to MVC via a framework reference to count. - // e.g. depending on Microsoft.AspNetCore.SomeThingNewThatDependsOnMvc would not result in an assembly being treated as - // referencing MVC. - classification = _mvcAssemblies.Contains(assemblyItem.AssemblyName) ? - Classification.IsMvc : - Classification.DoesNotReferenceMvc; - } - else if (_mvcAssemblies.Contains(assemblyItem.AssemblyName)) - { - classification = Classification.IsMvc; + classification = Classification.IsUmbraco; } else { - classification = Classification.DoesNotReferenceMvc; - foreach (var reference in GetReferences(assemblyItem.Path)) + classification = Classification.DoesNotReferenceUmbraco; + foreach (var reference in GetReferences(assemblyItem)) { + // recurse var referenceClassification = Resolve(reference); - if (referenceClassification == Classification.IsMvc || referenceClassification == Classification.ReferencesMvc) + if (referenceClassification == Classification.IsUmbraco || referenceClassification == Classification.ReferencesUmbraco) { - classification = Classification.ReferencesMvc; + classification = Classification.ReferencesUmbraco; break; } } @@ -830,139 +654,82 @@ namespace Umbraco.Core.Composing return classification; } - protected virtual IReadOnlyList GetReferences(string file) - { - try + protected virtual IEnumerable GetReferences(Assembly assembly) + { + foreach (var referenceName in assembly.GetReferencedAssemblies()) { - if (!File.Exists(file)) + // don't include if this is excluded + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => referenceName.FullName.StartsWith(f))) + continue; + + var reference = Assembly.Load(referenceName); + if (!_lookup.Contains(reference)) { - throw new ReferenceAssemblyNotFoundException(file); - } + // A dependency references an item that isn't referenced by this project. + // We'll construct an item for so that we can calculate the classification based on it's name. - using var peReader = new PEReader(File.OpenRead(file)); - if (!peReader.HasMetadata) - { - return Array.Empty(); // not a managed assembly - } + _lookup.Add(reference); - var metadataReader = peReader.GetMetadataReader(); - - var references = new List(); - foreach (var handle in metadataReader.AssemblyReferences) - { - var reference = metadataReader.GetAssemblyReference(handle); - var referenceName = metadataReader.GetString(reference.Name); - - if (!Lookup.TryGetValue(referenceName, out var assemblyItem)) - { - // A dependency references an item that isn't referenced by this project. - // We'll construct an item for so that we can calculate the classification based on it's name. - assemblyItem = new AssemblyItem - { - AssemblyName = referenceName, - }; - - Lookup[referenceName] = assemblyItem; - } - - references.Add(assemblyItem); - } - - return references; + yield return reference; + } } - catch (BadImageFormatException) - { - // not a PE file, or invalid metadata - } - - return Array.Empty(); // not a managed assembly } protected enum Classification { Unknown, - DoesNotReferenceMvc, - ReferencesMvc, - IsMvc, + DoesNotReferenceUmbraco, + ReferencesUmbraco, + IsUmbraco, } } - public class AssemblyItem + + /// + /// Finds Assemblies from the entry point assemblies, it's dependencies and it's transitive dependencies that reference that targetAssemblyNames + /// + /// + /// borrowed and modified from here https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/FindAssembliesWithReferencesTo.cs + /// + internal class FindAssembliesWithReferencesTo { - public string Path { get; set; } + private readonly Assembly[] _referenceAssemblies; + private readonly string[] _targetAssemblies; + private readonly bool _includeTargets; - public bool IsFrameworkReference { get; set; } - - public string AssemblyName { get; set; } - } - - internal class ReferenceAssemblyNotFoundException : Exception - { - public ReferenceAssemblyNotFoundException(string fileName) - { - FileName = fileName; - } - - public string FileName { get; } - } - - // borrowed from here https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/FindAssembliesWithReferencesTo.cs - public class FindAssembliesWithReferencesTo - { - private readonly string[] _referenceAssemblies; - private readonly string[] _targetAssemblyNames; - - public FindAssembliesWithReferencesTo(string[] referenceAssemblies, string[] targetAssemblyNames) + /// + /// Constructor + /// + /// Entry point assemblies + /// Used to check if the entry point or it's transitive assemblies reference these assembly names + /// If true will also use the target assembly names as entry point assemblies + public FindAssembliesWithReferencesTo(Assembly[] referenceAssemblies, string[] targetAssemblyNames, bool includeTargets) { _referenceAssemblies = referenceAssemblies; - _targetAssemblyNames = targetAssemblyNames; + _targetAssemblies = targetAssemblyNames; + _includeTargets = includeTargets; } - public IEnumerable Find() + public IEnumerable Find() { - var referenceItems = new List(); - foreach (var item in _referenceAssemblies) + var referenceItems = new List(); + foreach (var assembly in _referenceAssemblies) { - //var assemblyName = new AssemblyName(item).Name; - var assembly = Assembly.Load(item); - referenceItems.Add(new AssemblyItem + referenceItems.Add(assembly); + } + + if (_includeTargets) + { + foreach(var target in _targetAssemblies) { - AssemblyName = assembly.GetName().Name, //assemblyName, - IsFrameworkReference = false, - Path = GetAssemblyLocation(assembly) - }); + referenceItems.Add(Assembly.Load(target)); + } } - var provider = new ReferenceResolver(_targetAssemblyNames, referenceItems); - try - { - var assemblyNames = provider.ResolveAssemblies(); - return assemblyNames.ToArray(); - } - catch (ReferenceAssemblyNotFoundException ex) - { - throw; - //// Print a warning and return. We cannot produce a correct document at this point. - //var warning = "Reference assembly {0} could not be found. This is typically caused by build errors in referenced projects."; - //Log.LogWarning(null, "RAZORSDK1007", null, null, 0, 0, 0, 0, warning, ex.FileName); - //return true; - } - catch (Exception ex) - { - throw; - //Log.LogErrorFromException(ex); - } + var provider = new ReferenceResolver(_targetAssemblies, referenceItems); + var assemblyNames = provider.ResolveAssemblies(); + return assemblyNames.ToList(); } - internal static string GetAssemblyLocation(Assembly assembly) - { - if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) && - result.IsFile && string.IsNullOrWhiteSpace(result.Fragment)) - { - return result.LocalPath; - } - - return assembly.Location; - } } } diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 2422e6018f..e12a81be36 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -370,7 +370,7 @@ namespace Umbraco.Core.Runtime /// /// protected virtual ITypeFinder GetTypeFinder() - => new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(Assembly.GetEntryAssembly()?.GetName()?.Name)); + => new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(Assembly.GetEntryAssembly())); /// diff --git a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index bba7ea0061..dec895a432 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); _memberCache = new ObjectCacheAppCache(typeFinder); _provider = new DeepCloneAppCache(_memberCache); diff --git a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs index d2d3b795d6..28d9b42ab0 100644 --- a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); _ctx = new FakeHttpContextFactory("http://localhost/test"); _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items, typeFinder); } diff --git a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs index 5d6808f678..e8fc420f58 100644 --- a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); _provider = new ObjectCacheAppCache(typeFinder); } diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 9d44aa562e..0b179d6d85 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Components var mock = new Mock(); var logger = Mock.Of(); - var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(typeof(ComponentTests).Assembly.GetName().Name)); + var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(typeof(ComponentTests).Assembly)); var f = new UmbracoDatabaseFactory(logger, new Lazy(() => new MapperCollection(Enumerable.Empty())), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator); var fs = new FileSystems(mock.Object, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings()); var coreDebug = Mock.Of(); @@ -371,7 +371,7 @@ namespace Umbraco.Tests.Components public void AllComposers() { var ioHelper = TestHelper.IOHelper; - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var typeLoader = new TypeLoader(ioHelper, typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of()); var register = MockRegister(); diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index c4d048cd7b..5e90b777fe 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.Composing { ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var ioHelper = TestHelper.IOHelper; TypeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), ProfilingLogger, false, AssembliesToScan); } diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs index a720fe045f..7a71afa625 100644 --- a/src/Umbraco.Tests/Composing/CompositionTests.cs +++ b/src/Umbraco.Tests/Composing/CompositionTests.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Composing .Returns(() => factoryFactory?.Invoke(mockedFactory)); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var ioHelper = TestHelper.IOHelper; var typeLoader = new TypeLoader(ioHelper, typeFinder, Mock.Of(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger); var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of(), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index 969be8c0fd..3bdfd09752 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -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.GetName().Name)); + var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var typesFound = typeFinder.FindClassesOfTypeWithAttribute(_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.GetName().Name)); + var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var typesFound = typeFinder.FindClassesWithAttribute(_assemblies); Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree] diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 2d6f1b85e8..7d68f616fa 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); _typeLoader = new TypeLoader(TestHelper.IOHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(TestHelper.IOHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()), false, diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index 3cc77b6732..addc5afde0 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -17,7 +17,7 @@ namespace Umbraco.Tests.Macros [SetUp] public void Setup() { - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); //we DO want cache enabled for these tests var cacheHelper = new AppCaches( new ObjectCacheAppCache(typeFinder), diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 9a7e25944f..c25197d79e 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -269,7 +269,7 @@ namespace Umbraco.Tests.Models content.UpdateDate = DateTime.Now; content.WriterId = 23; - var runtimeCache = new ObjectCacheAppCache(new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name))); + var runtimeCache = new ObjectCacheAppCache(new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly))); runtimeCache.Insert(content.Id.ToString(CultureInfo.InvariantCulture), () => content); var proflog = GetTestProfilingLogger(); diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 329626a984..d0c348bc0c 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -127,7 +127,7 @@ namespace Umbraco.Tests.Published var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var elementsCache = new FastDictionaryAppCache(typeFinder); var snapshotCache = new FastDictionaryAppCache(typeFinder); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index d868e8bc2e..9947f76fb0 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -143,7 +143,7 @@ namespace Umbraco.Tests.PublishedContent // create a data source for NuCache _source = new TestDataSource(kits); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var settings = Mock.Of(); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index cf2be84f24..be2ba1b48b 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -184,7 +184,7 @@ namespace Umbraco.Tests.PublishedContent // create a variation accessor _variationAccesor = new TestVariationContextAccessor(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var settings = Mock.Of(); // at last, create the complete NuCache snapshot service! diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index b97aae2e7e..2bcfd2d98b 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -63,7 +63,7 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance()), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator); - var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var ioHelper = TestHelper.IOHelper; var hostingEnvironment = Mock.Of(); var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger); @@ -256,7 +256,7 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = Mock.Of(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var ioHelper = TestHelper.IOHelper; var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger); var runtimeState = Mock.Of(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index 5cc10d735d..b2252e705d 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -85,7 +85,7 @@ namespace Umbraco.Tests.Scoping var memberRepository = Mock.Of(); var hostingEnvironment = TestHelper.GetHostingEnvironment(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var settings = Mock.Of(); return new PublishedSnapshotService( diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index a2855aa1bd..a8d045c5e8 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -58,7 +58,7 @@ namespace Umbraco.Tests.Services var memberRepository = Mock.Of(); var hostingEnvironment = Mock.Of(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var settings = Mock.Of(); return new PublishedSnapshotService( diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index 0f54725246..19b45c8ad5 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.TestHelpers var ioHelper = TestHelper.IOHelper; var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var typeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger, diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index 773ef85b1b..b2650695ac 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs { if (_factory != null) return _factory(requestContext); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var types = typeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 32b891f2eb..81b7369119 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -247,7 +247,7 @@ namespace Umbraco.Tests.TestHelpers databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, logger, new Lazy(() => mappers), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator); } - typeFinder = typeFinder ?? new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + typeFinder = typeFinder ?? new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly)); fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings()); var coreDebug = Current.Configs.CoreDebug(); var mediaFileSystem = Mock.Of(); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index f064e7ffa9..483b4033f7 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -171,7 +171,7 @@ namespace Umbraco.Tests.Testing var proflogger = new ProfilingLogger(logger, profiler); IOHelper = TestHelper.IOHelper; - TypeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + TypeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var appCaches = GetAppCaches(); var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); var settings = SettingsForTests.GetDefaultUmbracoSettings(); diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index e4ae4c9a58..7846bf366b 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Web { // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly.GetName().Name)); + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); var ioHelper = TestHelper.IOHelper; container .Setup(x => x.GetInstance(typeof(TypeLoader))) From d92fc8736ab136c67acadade9b1143d31ec57ca2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 9 Mar 2020 13:31:56 +1100 Subject: [PATCH 03/10] More cleanup, cleans up tests, cleans up the BuildManagerAssemblyProvider (even though it's not used) --- .../Composing/BruteForceAssemblyProvider.cs | 102 ++++++ .../DefaultUmbracoAssemblyProvider.cs | 36 +++ .../FindAssembliesWithReferencesTo.cs | 55 ++++ .../Composing/IAssemblyProvider.cs | 13 + .../Composing/ReferenceResolver.cs | 115 +++++++ src/Umbraco.Core/Composing/TypeFinder.cs | 300 +----------------- src/Umbraco.Core/Umbraco.Core.csproj | 1 - .../Cache/DeepCloneAppCacheTests.cs | 3 +- .../Cache/HttpRequestAppCacheTests.cs | 2 +- .../Cache/ObjectAppCacheTests.cs | 9 +- .../Components/ComponentTests.cs | 4 +- .../Composing/ComposingTestBase.cs | 2 +- .../Composing/CompositionTests.cs | 2 +- .../Composing/TypeLoaderTests.cs | 2 +- src/Umbraco.Tests/Macros/MacroTests.cs | 2 +- src/Umbraco.Tests/Models/ContentTests.cs | 4 +- .../Published/PropertyCacheLevelTests.cs | 2 +- .../PublishedContent/NuCacheChildrenTests.cs | 2 +- .../PublishedContent/NuCacheTests.cs | 2 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 2 +- .../Scoping/ScopedNuCacheTests.cs | 2 +- .../ContentTypeServiceVariantsTests.cs | 2 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 2 +- .../Stubs/TestControllerFactory.cs | 2 +- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 6 + src/Umbraco.Tests/Web/UmbracoHelperTests.cs | 2 +- .../Composing/BuildManagerAssemblyProvider.cs | 70 ++++ .../Composing/BuildManagerTypeFinder.cs | 57 +--- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 29 files changed, 426 insertions(+), 378 deletions(-) create mode 100644 src/Umbraco.Core/Composing/BruteForceAssemblyProvider.cs create mode 100644 src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs create mode 100644 src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs create mode 100644 src/Umbraco.Core/Composing/IAssemblyProvider.cs create mode 100644 src/Umbraco.Core/Composing/ReferenceResolver.cs create mode 100644 src/Umbraco.Web/Composing/BuildManagerAssemblyProvider.cs diff --git a/src/Umbraco.Core/Composing/BruteForceAssemblyProvider.cs b/src/Umbraco.Core/Composing/BruteForceAssemblyProvider.cs new file mode 100644 index 0000000000..04d0d70475 --- /dev/null +++ b/src/Umbraco.Core/Composing/BruteForceAssemblyProvider.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Composing +{ + /// + /// lazily load a reference to all local assemblies and gac assemblies + /// + /// + /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder + /// + /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been + /// loaded in the CLR, not all assemblies. + /// See these threads: + /// http://issues.umbraco.org/issue/U5-198 + /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app + /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl + /// + public class BruteForceAssemblyProvider : IAssemblyProvider + { + public BruteForceAssemblyProvider() + { + _allAssemblies = new Lazy>(() => + { + HashSet assemblies = null; + try + { + //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have + // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. + var binFolder = GetRootDirectorySafe(); + var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + assemblies = new HashSet(); + foreach (var a in binAssemblyFiles) + { + try + { + var assName = AssemblyName.GetAssemblyName(a); + var ass = Assembly.Load(assName); + assemblies.Add(ass); + } + catch (Exception e) + { + if (e is SecurityException || e is BadImageFormatException) + { + //swallow these exceptions + } + else + { + throw; + } + } + } + + //Since we are only loading in the /bin assemblies above, we will also load in anything that's already loaded (which will include gac items) + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + assemblies.Add(a); + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + return assemblies; + }); + } + + private readonly Lazy> _allAssemblies; + private string _rootDir = string.Empty; + + public IEnumerable Assemblies => _allAssemblies.Value; + + // FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here + private string GetRootDirectorySafe() + { + if (string.IsNullOrEmpty(_rootDir) == false) + { + return _rootDir; + } + + var codeBase = Assembly.GetExecutingAssembly().CodeBase; + var uri = new Uri(codeBase); + var path = uri.LocalPath; + var baseDirectory = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(baseDirectory)) + throw new PanicException("No root directory could be resolved."); + + _rootDir = baseDirectory.Contains("bin") + ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) + : baseDirectory; + + return _rootDir; + } + } +} diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs new file mode 100644 index 0000000000..1322dcbfa2 --- /dev/null +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Returns a list of scannable assemblies based on an entry point assembly and it's references + /// + /// + /// This will recursively search through the entry point's assemblies and Umbraco's core assemblies (Core/Web) and their references + /// to create a list of scannable assemblies based on whether they themselves or their transitive dependencies reference Umbraco core assemblies. + /// + public class DefaultUmbracoAssemblyProvider : IAssemblyProvider + { + private readonly Assembly _entryPointAssembly; + private static readonly string[] UmbracoCoreAssemblyNames = new[] { "Umbraco.Core", "Umbraco.Web" }; + + public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly) + { + _entryPointAssembly = entryPointAssembly ?? throw new ArgumentNullException(nameof(entryPointAssembly)); + } + + public IEnumerable Assemblies + { + get + { + var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, UmbracoCoreAssemblyNames, true); + foreach(var found in finder.Find()) + { + yield return found; + } + } + } + } +} diff --git a/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs new file mode 100644 index 0000000000..91225ff973 --- /dev/null +++ b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Finds Assemblies from the entry point assemblies, it's dependencies and it's transitive dependencies that reference that targetAssemblyNames + /// + /// + /// borrowed and modified from here https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/FindAssembliesWithReferencesTo.cs + /// + internal class FindAssembliesWithReferencesTo + { + private readonly Assembly[] _referenceAssemblies; + private readonly string[] _targetAssemblies; + private readonly bool _includeTargets; + + /// + /// Constructor + /// + /// Entry point assemblies + /// Used to check if the entry point or it's transitive assemblies reference these assembly names + /// If true will also use the target assembly names as entry point assemblies + public FindAssembliesWithReferencesTo(Assembly[] referenceAssemblies, string[] targetAssemblyNames, bool includeTargets) + { + _referenceAssemblies = referenceAssemblies; + _targetAssemblies = targetAssemblyNames; + _includeTargets = includeTargets; + } + + public IEnumerable Find() + { + var referenceItems = new List(); + foreach (var assembly in _referenceAssemblies) + { + referenceItems.Add(assembly); + } + + if (_includeTargets) + { + foreach(var target in _targetAssemblies) + { + referenceItems.Add(Assembly.Load(target)); + } + } + + var provider = new ReferenceResolver(_targetAssemblies, referenceItems); + var assemblyNames = provider.ResolveAssemblies(); + return assemblyNames.ToList(); + } + + } +} diff --git a/src/Umbraco.Core/Composing/IAssemblyProvider.cs b/src/Umbraco.Core/Composing/IAssemblyProvider.cs new file mode 100644 index 0000000000..bde97a9556 --- /dev/null +++ b/src/Umbraco.Core/Composing/IAssemblyProvider.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Provides a list of assemblies that can be scanned + /// + public interface IAssemblyProvider + { + IEnumerable Assemblies { get; } + } +} diff --git a/src/Umbraco.Core/Composing/ReferenceResolver.cs b/src/Umbraco.Core/Composing/ReferenceResolver.cs new file mode 100644 index 0000000000..6d77afd414 --- /dev/null +++ b/src/Umbraco.Core/Composing/ReferenceResolver.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + /// + /// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively. + /// + /// + /// Borrowed and modified from https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/ReferenceResolver.cs + /// + internal class ReferenceResolver + { + private readonly HashSet _umbracoAssemblies; + private readonly IReadOnlyList _assemblyItems; + private readonly Dictionary _classifications; + private readonly List _lookup = new List(); + + public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList assemblyItems) + { + _umbracoAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); + _assemblyItems = assemblyItems; + _classifications = new Dictionary(); + + foreach (var item in assemblyItems) + { + _lookup.Add(item); + } + } + + public IEnumerable ResolveAssemblies() + { + var applicationParts = new List(); + + foreach (var item in _assemblyItems) + { + var classification = Resolve(item); + if (classification == Classification.ReferencesUmbraco || classification == Classification.IsUmbraco) + { + applicationParts.Add(item); + } + } + + return applicationParts; + } + + private Classification Resolve(Assembly assemblyItem) + { + if (_classifications.TryGetValue(assemblyItem, out var classification)) + { + return classification; + } + + // Initialize the dictionary with a value to short-circuit recursive references. + classification = Classification.Unknown; + _classifications[assemblyItem] = classification; + + if (_umbracoAssemblies.Contains(assemblyItem.GetName().Name)) + { + classification = Classification.IsUmbraco; + } + else + { + classification = Classification.DoesNotReferenceUmbraco; + foreach (var reference in GetReferences(assemblyItem)) + { + // recurse + var referenceClassification = Resolve(reference); + + if (referenceClassification == Classification.IsUmbraco || referenceClassification == Classification.ReferencesUmbraco) + { + classification = Classification.ReferencesUmbraco; + break; + } + } + } + + Debug.Assert(classification != Classification.Unknown); + _classifications[assemblyItem] = classification; + return classification; + } + + protected virtual IEnumerable GetReferences(Assembly assembly) + { + foreach (var referenceName in assembly.GetReferencedAssemblies()) + { + // don't include if this is excluded + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => referenceName.FullName.StartsWith(f))) + continue; + + var reference = Assembly.Load(referenceName); + if (!_lookup.Contains(reference)) + { + // A dependency references an item that isn't referenced by this project. + // We'll construct an item for so that we can calculate the classification based on it's name. + + _lookup.Add(reference); + + yield return reference; + } + } + } + + protected enum Classification + { + Unknown, + DoesNotReferenceUmbraco, + ReferencesUmbraco, + IsUmbraco, + } + } +} diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index a0aab7c088..bc15cb63f2 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -1,16 +1,11 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; using System.Linq; using System.Reflection; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; using System.Security; using System.Text; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; namespace Umbraco.Core.Composing @@ -21,11 +16,6 @@ namespace Umbraco.Core.Composing private readonly ILogger _logger; private readonly IAssemblyProvider _assemblyProvider; - //public TypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) - // : this(logger, new DefaultUmbracoAssemblyProvider(Assembly.GetEntryAssembly()?.GetName()?.Name), typeFinderConfig) - //{ - //} - public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig typeFinderConfig = null) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -151,7 +141,9 @@ namespace Umbraco.Core.Composing "WebDriver,", "itextsharp,", "mscorlib,", - "nunit.framework,", + "NUnit,", + "NUnit3TestAdapter,", + "Selenium.", }; /// @@ -446,290 +438,4 @@ namespace Umbraco.Core.Composing #endregion } - - /// - /// lazily load a reference to all local assemblies and gac assemblies - /// - /// - /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder - /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been - /// loaded in the CLR, not all assemblies. - /// See these threads: - /// http://issues.umbraco.org/issue/U5-198 - /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app - /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl - /// - public class BruteForceAssemblyProvider : IAssemblyProvider - { - public BruteForceAssemblyProvider() - { - _allAssemblies = new Lazy>(() => - { - HashSet assemblies = null; - try - { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = GetRootDirectorySafe(); - var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new HashSet(); - foreach (var a in binAssemblyFiles) - { - try - { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); - } - catch (Exception e) - { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } - } - } - - //Since we are only loading in the /bin assemblies above, we will also load in anything that's already loaded (which will include gac items) - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - { - assemblies.Add(a); - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - return assemblies; - }); - } - - private readonly Lazy> _allAssemblies; - private string _rootDir = string.Empty; - - public IEnumerable Assemblies => _allAssemblies.Value; - - // FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here - private string GetRootDirectorySafe() - { - if (string.IsNullOrEmpty(_rootDir) == false) - { - return _rootDir; - } - - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - var baseDirectory = Path.GetDirectoryName(path); - if (string.IsNullOrEmpty(baseDirectory)) - throw new PanicException("No root directory could be resolved."); - - _rootDir = baseDirectory.Contains("bin") - ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) - : baseDirectory; - - return _rootDir; - } - } - - /// - /// Provides a list of assemblies that can be scanned - /// - public interface IAssemblyProvider - { - IEnumerable Assemblies { get; } - } - - /// - /// Returns a list of scannable assemblies based on an entry point assembly and it's references - /// - /// - /// This will recursively search through the entry point's assemblies and Umbraco's core assemblies (Core/Web) and their references - /// to create a list of scannable assemblies based on whether they themselves or their transitive dependencies reference Umbraco core assemblies. - /// - public class DefaultUmbracoAssemblyProvider : IAssemblyProvider - { - private readonly Assembly _entryPointAssembly; - private static readonly string[] UmbracoCoreAssemblyNames = new[] { "Umbraco.Core", "Umbraco.Web" }; - - public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly) - { - _entryPointAssembly = entryPointAssembly ?? throw new ArgumentNullException(nameof(entryPointAssembly)); - } - - public IEnumerable Assemblies - { - get - { - var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, UmbracoCoreAssemblyNames, true); - foreach(var found in finder.Find()) - { - yield return found; - } - } - } - } - - /// - /// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively. - /// - /// - /// Borrowed and modified from https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/ReferenceResolver.cs - /// - internal class ReferenceResolver - { - private readonly HashSet _umbracoAssemblies; - private readonly IReadOnlyList _assemblyItems; - private readonly Dictionary _classifications; - private readonly List _lookup = new List(); - - public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList assemblyItems) - { - _umbracoAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); - _assemblyItems = assemblyItems; - _classifications = new Dictionary(); - - foreach (var item in assemblyItems) - { - _lookup.Add(item); - } - } - - public IEnumerable ResolveAssemblies() - { - var applicationParts = new List(); - - foreach (var item in _assemblyItems) - { - var classification = Resolve(item); - if (classification == Classification.ReferencesUmbraco || classification == Classification.IsUmbraco) - { - applicationParts.Add(item); - } - } - - return applicationParts; - } - - private Classification Resolve(Assembly assemblyItem) - { - if (_classifications.TryGetValue(assemblyItem, out var classification)) - { - return classification; - } - - // Initialize the dictionary with a value to short-circuit recursive references. - classification = Classification.Unknown; - _classifications[assemblyItem] = classification; - - if (_umbracoAssemblies.Contains(assemblyItem.GetName().Name)) - { - classification = Classification.IsUmbraco; - } - else - { - classification = Classification.DoesNotReferenceUmbraco; - foreach (var reference in GetReferences(assemblyItem)) - { - // recurse - var referenceClassification = Resolve(reference); - - if (referenceClassification == Classification.IsUmbraco || referenceClassification == Classification.ReferencesUmbraco) - { - classification = Classification.ReferencesUmbraco; - break; - } - } - } - - Debug.Assert(classification != Classification.Unknown); - _classifications[assemblyItem] = classification; - return classification; - } - - protected virtual IEnumerable GetReferences(Assembly assembly) - { - foreach (var referenceName in assembly.GetReferencedAssemblies()) - { - // don't include if this is excluded - if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => referenceName.FullName.StartsWith(f))) - continue; - - var reference = Assembly.Load(referenceName); - if (!_lookup.Contains(reference)) - { - // A dependency references an item that isn't referenced by this project. - // We'll construct an item for so that we can calculate the classification based on it's name. - - _lookup.Add(reference); - - yield return reference; - } - } - } - - protected enum Classification - { - Unknown, - DoesNotReferenceUmbraco, - ReferencesUmbraco, - IsUmbraco, - } - } - - - /// - /// Finds Assemblies from the entry point assemblies, it's dependencies and it's transitive dependencies that reference that targetAssemblyNames - /// - /// - /// borrowed and modified from here https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/FindAssembliesWithReferencesTo.cs - /// - internal class FindAssembliesWithReferencesTo - { - private readonly Assembly[] _referenceAssemblies; - private readonly string[] _targetAssemblies; - private readonly bool _includeTargets; - - /// - /// Constructor - /// - /// Entry point assemblies - /// Used to check if the entry point or it's transitive assemblies reference these assembly names - /// If true will also use the target assembly names as entry point assemblies - public FindAssembliesWithReferencesTo(Assembly[] referenceAssemblies, string[] targetAssemblyNames, bool includeTargets) - { - _referenceAssemblies = referenceAssemblies; - _targetAssemblies = targetAssemblyNames; - _includeTargets = includeTargets; - } - - public IEnumerable Find() - { - var referenceItems = new List(); - foreach (var assembly in _referenceAssemblies) - { - referenceItems.Add(assembly); - } - - if (_includeTargets) - { - foreach(var target in _targetAssemblies) - { - referenceItems.Add(Assembly.Load(target)); - } - } - - var provider = new ReferenceResolver(_targetAssemblies, referenceItems); - var assemblyNames = provider.ResolveAssemblies(); - return assemblyNames.ToList(); - } - - } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 49d5090070..7a15e7fbed 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -13,7 +13,6 @@ - diff --git a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index dec895a432..e4844cc6be 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Tests.Collections; +using Umbraco.Tests.TestHelpers; using Umbraco.Web.Cache; namespace Umbraco.Tests.Cache @@ -28,7 +29,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); _memberCache = new ObjectCacheAppCache(typeFinder); _provider = new DeepCloneAppCache(_memberCache); diff --git a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs index 28d9b42ab0..dbda6fb429 100644 --- a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); _ctx = new FakeHttpContextFactory("http://localhost/test"); _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items, typeFinder); } diff --git a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs index e8fc420f58..7957026ad8 100644 --- a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs @@ -1,10 +1,7 @@ -using System.Collections.Generic; -using System.Linq; -using Moq; +using System.Linq; using NUnit.Framework; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; -using Umbraco.Core.Logging; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Cache { @@ -21,7 +18,7 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); _provider = new ObjectCacheAppCache(typeFinder); } diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 0b179d6d85..cf54b8e9ec 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Components var mock = new Mock(); var logger = Mock.Of(); - var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(typeof(ComponentTests).Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var f = new UmbracoDatabaseFactory(logger, new Lazy(() => new MapperCollection(Enumerable.Empty())), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator); var fs = new FileSystems(mock.Object, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings()); var coreDebug = Mock.Of(); @@ -371,7 +371,7 @@ namespace Umbraco.Tests.Components public void AllComposers() { var ioHelper = TestHelper.IOHelper; - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var typeLoader = new TypeLoader(ioHelper, typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of()); var register = MockRegister(); diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index 5e90b777fe..6c5ccd5510 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -22,7 +22,7 @@ namespace Umbraco.Tests.Composing { ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + 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); } diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs index 7a71afa625..ce3cdfac17 100644 --- a/src/Umbraco.Tests/Composing/CompositionTests.cs +++ b/src/Umbraco.Tests/Composing/CompositionTests.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Composing .Returns(() => factoryFactory?.Invoke(mockedFactory)); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var ioHelper = TestHelper.IOHelper; var typeLoader = new TypeLoader(ioHelper, typeFinder, Mock.Of(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger); var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of(), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache); diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 7d68f616fa..d0181563a8 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); _typeLoader = new TypeLoader(TestHelper.IOHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(TestHelper.IOHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()), false, diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index addc5afde0..d6ed8f33c2 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -17,7 +17,7 @@ namespace Umbraco.Tests.Macros [SetUp] public void Setup() { - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); //we DO want cache enabled for these tests var cacheHelper = new AppCaches( new ObjectCacheAppCache(typeFinder), diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index c25197d79e..e33f707ee1 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -22,6 +22,7 @@ using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Tests.Testing; using Umbraco.Web.PropertyEditors; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Models { @@ -269,7 +270,8 @@ namespace Umbraco.Tests.Models content.UpdateDate = DateTime.Now; content.WriterId = 23; - var runtimeCache = new ObjectCacheAppCache(new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly))); + var typeFinder = TestHelper.GetTypeFinder(); + var runtimeCache = new ObjectCacheAppCache(typeFinder); runtimeCache.Insert(content.Id.ToString(CultureInfo.InvariantCulture), () => content); var proflog = GetTestProfilingLogger(); diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index d0c348bc0c..769985d515 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -127,7 +127,7 @@ namespace Umbraco.Tests.Published var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var elementsCache = new FastDictionaryAppCache(typeFinder); var snapshotCache = new FastDictionaryAppCache(typeFinder); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index 9947f76fb0..e8296320a5 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -143,7 +143,7 @@ namespace Umbraco.Tests.PublishedContent // create a data source for NuCache _source = new TestDataSource(kits); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var settings = Mock.Of(); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index be2ba1b48b..1f6895296f 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -184,7 +184,7 @@ namespace Umbraco.Tests.PublishedContent // create a variation accessor _variationAccesor = new TestVariationContextAccessor(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var settings = Mock.Of(); // at last, create the complete NuCache snapshot service! diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 2bcfd2d98b..ecc5094166 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -256,7 +256,7 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = Mock.Of(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + 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 runtimeState = Mock.Of(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index b2252e705d..aada99da71 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -85,7 +85,7 @@ namespace Umbraco.Tests.Scoping var memberRepository = Mock.Of(); var hostingEnvironment = TestHelper.GetHostingEnvironment(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var settings = Mock.Of(); return new PublishedSnapshotService( diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index a8d045c5e8..33e8b0010e 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -58,7 +58,7 @@ namespace Umbraco.Tests.Services var memberRepository = Mock.Of(); var hostingEnvironment = Mock.Of(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var settings = Mock.Of(); return new PublishedSnapshotService( diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index 19b45c8ad5..e035eaa807 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.TestHelpers var ioHelper = TestHelper.IOHelper; var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var typeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger, diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index b2650695ac..f5d18e05ba 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -40,7 +40,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs { if (_factory != null) return _factory(requestContext); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var types = typeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 41b97ac580..177fc2d518 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -43,6 +43,12 @@ namespace Umbraco.Tests.TestHelpers public static class TestHelper { + public static ITypeFinder GetTypeFinder() + { + var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(typeof(TestHelper).Assembly)); + return typeFinder; + } + public static TypeLoader GetMockedTypeLoader() { return new TypeLoader(IOHelper, Mock.Of(), Mock.Of(), new DirectoryInfo(IOHelper.MapPath("~/App_Data/TEMP")), Mock.Of()); diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index 7846bf366b..62d7e941d7 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.Web { // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var typeFinder = TestHelper.GetTypeFinder(); var ioHelper = TestHelper.IOHelper; container .Setup(x => x.GetInstance(typeof(TypeLoader))) diff --git a/src/Umbraco.Web/Composing/BuildManagerAssemblyProvider.cs b/src/Umbraco.Web/Composing/BuildManagerAssemblyProvider.cs new file mode 100644 index 0000000000..3c5cebe03e --- /dev/null +++ b/src/Umbraco.Web/Composing/BuildManagerAssemblyProvider.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Web.Compilation; +using Umbraco.Core.Composing; +using Umbraco.Core.Hosting; +using Umbraco.Core.IO; +using Umbraco.Core.Logging; + +namespace Umbraco.Web.Composing +{ + /// + /// Uses the BuildManager to provide a list of assemblies to scan + /// + internal class BuildManagerAssemblyProvider : BruteForceAssemblyProvider, IAssemblyProvider + { + private readonly Lazy> _allAssemblies; + + public BuildManagerAssemblyProvider(IIOHelper ioHelper, + IHostingEnvironment hostingEnvironment, + ILogger logger) + { + _allAssemblies = new Lazy>(() => + { + var isHosted = hostingEnvironment.IsHosted; + try + { + if (isHosted) + { + var assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + + //here we are trying to get the App_Code assembly + var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported + var appCodeFolder = new DirectoryInfo(ioHelper.MapPath(ioHelper.ResolveUrl("~/App_code"))); + //check if the folder exists and if there are any files in it with the supported file extensions + if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) + { + try + { + var appCodeAssembly = Assembly.Load("App_Code"); + if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already + assemblies.Add(appCodeAssembly); + } + catch (FileNotFoundException ex) + { + //this will occur if it cannot load the assembly + logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code"); + } + } + + return assemblies; + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + // Not hosted, just use the default implementation + return new HashSet(base.Assemblies); + }); + } + + IEnumerable IAssemblyProvider.Assemblies => _allAssemblies.Value; + } +} diff --git a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs index bd583bf29d..d3502c36fb 100644 --- a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs +++ b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs @@ -1,19 +1,15 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Reflection; -using System.Security; using System.Web.Compilation; using Umbraco.Core.Configuration; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Hosting; -using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Web.Composing { + /// /// An implementation of TypeFinder that uses the BuildManager to resolve references for aspnet framework hosted websites /// @@ -24,60 +20,13 @@ namespace Umbraco.Web.Composing { public BuildManagerTypeFinder( - IIOHelper ioHelper, - IHostingEnvironment hostingEnvironment, ILogger logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig typeFinderConfig = null) : base(logger, assemblyProvider, typeFinderConfig) { - if (ioHelper == null) throw new ArgumentNullException(nameof(ioHelper)); - if (hostingEnvironment == null) throw new ArgumentNullException(nameof(hostingEnvironment)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - - _allAssemblies = new Lazy>(() => - { - var isHosted = hostingEnvironment.IsHosted; - try - { - if (isHosted) - { - var assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); - - //here we are trying to get the App_Code assembly - var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(ioHelper.MapPath(ioHelper.ResolveUrl("~/App_code"))); - //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) - { - try - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } - catch (FileNotFoundException ex) - { - //this will occur if it cannot load the assembly - logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code"); - } - } - - return assemblies; - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - // Not hosted, just use the default implementation - return new HashSet(base.AssembliesToScan); - }); } - private readonly Lazy> _allAssemblies; - /// /// Explicitly implement and return result from BuildManager /// @@ -85,10 +34,6 @@ namespace Umbraco.Web.Composing /// Type ITypeFinder.GetTypeByName (string name) => BuildManager.GetType(name, false); - /// - /// Explicitly implement and return result from BuildManager - /// - IEnumerable ITypeFinder.AssembliesToScan => _allAssemblies.Value; /// /// TypeFinder config via appSettings diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index fad79b0296..27ea9443a0 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -143,6 +143,7 @@ + From fce27fd42d91f46458e9e28fb995cd1b70826193 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 9 Mar 2020 14:15:02 +1100 Subject: [PATCH 04/10] Ensures all assemblies at the same location as the entry point assemblies are queried. --- .../Composing/ReferenceResolver.cs | 67 +++++++++++++++++-- src/Umbraco.Core/Composing/TypeFinder.cs | 6 +- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Composing/ReferenceResolver.cs b/src/Umbraco.Core/Composing/ReferenceResolver.cs index 6d77afd414..c705af685a 100644 --- a/src/Umbraco.Core/Composing/ReferenceResolver.cs +++ b/src/Umbraco.Core/Composing/ReferenceResolver.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Reflection; @@ -15,27 +16,63 @@ namespace Umbraco.Core.Composing internal class ReferenceResolver { private readonly HashSet _umbracoAssemblies; - private readonly IReadOnlyList _assemblyItems; + private readonly IReadOnlyList _assemblies; private readonly Dictionary _classifications; private readonly List _lookup = new List(); - public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList assemblyItems) + public ReferenceResolver(IReadOnlyList targetAssemblies, IReadOnlyList entryPointAssemblies) { _umbracoAssemblies = new HashSet(targetAssemblies, StringComparer.Ordinal); - _assemblyItems = assemblyItems; + _assemblies = entryPointAssemblies; _classifications = new Dictionary(); - foreach (var item in assemblyItems) + foreach (var item in entryPointAssemblies) { _lookup.Add(item); } } + /// + /// Returns a list of assemblies that directly reference or transitively reference the targetAssemblies + /// + /// + /// + /// This includes all assemblies in the same location as the entry point assemblies + /// public IEnumerable ResolveAssemblies() { var applicationParts = new List(); - foreach (var item in _assemblyItems) + var assemblies = new HashSet(_assemblies); + + // Get the unique directories of the assemblies + var assemblyLocations = GetAssemblyLocations(assemblies).ToList(); + + // Load in each assembly in the directory of the entry assembly to be included in the search + // for Umbraco dependencies/transitive dependencies + foreach(var location in assemblyLocations) + { + var dir = Path.GetDirectoryName(location); + + foreach(var dll in Directory.EnumerateFiles(dir, "*.dll")) + { + var assemblyName = AssemblyName.GetAssemblyName(dll); + + // don't include if this is excluded + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => assemblyName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) + continue; + + // don't include this item if it's Umbraco + // TODO: We should maybe pass an explicit list of these names in? + if (assemblyName.FullName.StartsWith("Umbraco.")) + continue; + + var assembly = Assembly.Load(assemblyName); + assemblies.Add(assembly); + } + } + + foreach (var item in assemblies) { var classification = Resolve(item); if (classification == Classification.ReferencesUmbraco || classification == Classification.IsUmbraco) @@ -47,6 +84,24 @@ namespace Umbraco.Core.Composing return applicationParts; } + + private IEnumerable GetAssemblyLocations(IEnumerable assemblies) + { + return assemblies.Select(x => GetAssemblyLocation(x).ToLowerInvariant()).Distinct(); + } + + // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ApplicationParts/RelatedAssemblyAttribute.cs + private string GetAssemblyLocation(Assembly assembly) + { + if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) && + result.IsFile && string.IsNullOrWhiteSpace(result.Fragment)) + { + return result.LocalPath; + } + + return assembly.Location; + } + private Classification Resolve(Assembly assemblyItem) { if (_classifications.TryGetValue(assemblyItem, out var classification)) @@ -88,7 +143,7 @@ namespace Umbraco.Core.Composing foreach (var referenceName in assembly.GetReferencedAssemblies()) { // don't include if this is excluded - if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => referenceName.FullName.StartsWith(f))) + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => referenceName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) continue; var reference = Assembly.Load(referenceName); diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index bc15cb63f2..1813c8d8f4 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -142,8 +142,12 @@ namespace Umbraco.Core.Composing "itextsharp,", "mscorlib,", "NUnit,", - "NUnit3TestAdapter,", + "NUnit.", + "NUnit3.", "Selenium.", + "ImageProcessor", + "MiniProfiler.", + "Owin,", }; /// From 8cb39ba58300ae3ce3d7b1c004adfe5a630ad59e Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 9 Mar 2020 21:23:38 +1100 Subject: [PATCH 05/10] Fixes tests and some issues discovered --- .../DefaultUmbracoAssemblyProvider.cs | 17 +++++--- .../Composing/ReferenceResolver.cs | 41 ++++++++++--------- src/Umbraco.Core/Composing/TypeFinder.cs | 5 ++- .../Components/ComponentTests.cs | 3 +- .../Runtimes/CoreRuntimeTests.cs | 4 ++ src/Umbraco.Tests/TestHelpers/TestHelper.cs | 4 +- 6 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index 1322dcbfa2..c13a42ece7 100644 --- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -8,13 +8,21 @@ namespace Umbraco.Core.Composing /// Returns a list of scannable assemblies based on an entry point assembly and it's references /// /// - /// This will recursively search through the entry point's assemblies and Umbraco's core assemblies (Core/Web) and their references + /// This will recursively search through the entry point's assemblies and Umbraco's core assemblies and their references /// to create a list of scannable assemblies based on whether they themselves or their transitive dependencies reference Umbraco core assemblies. /// public class DefaultUmbracoAssemblyProvider : IAssemblyProvider { private readonly Assembly _entryPointAssembly; - private static readonly string[] UmbracoCoreAssemblyNames = new[] { "Umbraco.Core", "Umbraco.Web" }; + private static readonly string[] UmbracoCoreAssemblyNames = new[] + { + "Umbraco.Core", + "Umbraco.Web", + "Umbraco.Infrastructure", + "Umbraco.PublishedCache.NuCache", + "Umbraco.ModelsBuilder.Embedded", + "Umbraco.Examine.Lucene", + }; public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly) { @@ -26,10 +34,7 @@ namespace Umbraco.Core.Composing get { var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, UmbracoCoreAssemblyNames, true); - foreach(var found in finder.Find()) - { - yield return found; - } + return finder.Find(); } } } diff --git a/src/Umbraco.Core/Composing/ReferenceResolver.cs b/src/Umbraco.Core/Composing/ReferenceResolver.cs index c705af685a..65dba8bf23 100644 --- a/src/Umbraco.Core/Composing/ReferenceResolver.cs +++ b/src/Umbraco.Core/Composing/ReferenceResolver.cs @@ -46,14 +46,12 @@ namespace Umbraco.Core.Composing var assemblies = new HashSet(_assemblies); // Get the unique directories of the assemblies - var assemblyLocations = GetAssemblyLocations(assemblies).ToList(); + var assemblyLocations = GetAssemblyFolders(assemblies).ToList(); // Load in each assembly in the directory of the entry assembly to be included in the search // for Umbraco dependencies/transitive dependencies - foreach(var location in assemblyLocations) - { - var dir = Path.GetDirectoryName(location); - + foreach(var dir in assemblyLocations) + { foreach(var dll in Directory.EnumerateFiles(dir, "*.dll")) { var assemblyName = AssemblyName.GetAssemblyName(dll); @@ -85,9 +83,9 @@ namespace Umbraco.Core.Composing } - private IEnumerable GetAssemblyLocations(IEnumerable assemblies) + private IEnumerable GetAssemblyFolders(IEnumerable assemblies) { - return assemblies.Select(x => GetAssemblyLocation(x).ToLowerInvariant()).Distinct(); + return assemblies.Select(x => Path.GetDirectoryName(GetAssemblyLocation(x)).ToLowerInvariant()).Distinct(); } // borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ApplicationParts/RelatedAssemblyAttribute.cs @@ -102,25 +100,30 @@ namespace Umbraco.Core.Composing return assembly.Location; } - private Classification Resolve(Assembly assemblyItem) + private Classification Resolve(Assembly assembly) { - if (_classifications.TryGetValue(assemblyItem, out var classification)) + if (_classifications.TryGetValue(assembly, out var classification)) { return classification; } // Initialize the dictionary with a value to short-circuit recursive references. classification = Classification.Unknown; - _classifications[assemblyItem] = classification; - - if (_umbracoAssemblies.Contains(assemblyItem.GetName().Name)) + _classifications[assembly] = classification; + + if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => assembly.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase))) + { + // if its part of the filter it doesn't reference umbraco + classification = Classification.DoesNotReferenceUmbraco; + } + else if (_umbracoAssemblies.Contains(assembly.GetName().Name)) { classification = Classification.IsUmbraco; } else { classification = Classification.DoesNotReferenceUmbraco; - foreach (var reference in GetReferences(assemblyItem)) + foreach (var reference in GetReferences(assembly)) { // recurse var referenceClassification = Resolve(reference); @@ -134,7 +137,7 @@ namespace Umbraco.Core.Composing } Debug.Assert(classification != Classification.Unknown); - _classifications[assemblyItem] = classification; + _classifications[assembly] = classification; return classification; } @@ -147,15 +150,15 @@ namespace Umbraco.Core.Composing continue; var reference = Assembly.Load(referenceName); + if (!_lookup.Contains(reference)) { // A dependency references an item that isn't referenced by this project. - // We'll construct an item for so that we can calculate the classification based on it's name. + // We'll add this reference so that we can calculate the classification. - _lookup.Add(reference); - - yield return reference; - } + _lookup.Add(reference); + } + yield return reference; } } diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 1813c8d8f4..27b13a8365 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -94,7 +94,9 @@ namespace Umbraco.Core.Composing /// NOTE this means that "foo." will NOT exclude "foo.dll" but only "foo.*.dll" /// internal static readonly string[] KnownAssemblyExclusionFilter = { - "mscorlib", + "mscorlib,", + "netstandard,", + "System,", "Antlr3.", "AutoMapper,", "AutoMapper.", @@ -148,6 +150,7 @@ namespace Umbraco.Core.Composing "ImageProcessor", "MiniProfiler.", "Owin,", + "SQLite", }; /// diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index cf54b8e9ec..7dc6025b0a 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -378,7 +378,8 @@ namespace Umbraco.Tests.Components var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run), Configs, TestHelper.IOHelper, AppCaches.NoCache); - var types = typeLoader.GetTypes().Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web")); + var allComposers = typeLoader.GetTypes().ToList(); + var types = allComposers.Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web")).ToList(); var composers = new Composers(composition, types, Enumerable.Empty(), Mock.Of()); var requirements = composers.GetRequirements(); var report = Composers.GetComposersReport(requirements); diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs index 729184eb1d..4dc94cc50a 100644 --- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs @@ -125,6 +125,10 @@ namespace Umbraco.Tests.Runtimes } + // override because we cannot use Assembly.GetEntryAssembly in Nunit tests since that is always null + protected override ITypeFinder GetTypeFinder() + => new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + // must override the database factory // else BootFailedException because U cannot connect to the configured db protected internal override IUmbracoDatabaseFactory GetDatabaseFactory() diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 177fc2d518..2475072bd6 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -45,7 +45,9 @@ namespace Umbraco.Tests.TestHelpers public static ITypeFinder GetTypeFinder() { - var typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(typeof(TestHelper).Assembly)); + + var typeFinder = new TypeFinder(Mock.Of(), + new DefaultUmbracoAssemblyProvider(typeof(TestHelper).Assembly)); return typeFinder; } From 5ee22864367cb1628ae2720cf484d05f3579669f Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 11 Mar 2020 12:43:46 +1100 Subject: [PATCH 06/10] Removes build manager type finder and friends --- .../Composing/BruteForceAssemblyProvider.cs | 102 ------------------ src/Umbraco.Core/Composing/TypeFinder.cs | 6 ++ .../Composing/TypeFinderConfig.cs | 36 +++++++ .../Runtime/CoreRuntime.cs | 2 + .../Composing/BuildManagerAssemblyProvider.cs | 70 ------------ .../Composing/BuildManagerTypeFinder.cs | 66 ------------ src/Umbraco.Web/Runtime/WebRuntime.cs | 4 - src/Umbraco.Web/Umbraco.Web.csproj | 2 - 8 files changed, 44 insertions(+), 244 deletions(-) delete mode 100644 src/Umbraco.Core/Composing/BruteForceAssemblyProvider.cs create mode 100644 src/Umbraco.Core/Composing/TypeFinderConfig.cs delete mode 100644 src/Umbraco.Web/Composing/BuildManagerAssemblyProvider.cs delete mode 100644 src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs diff --git a/src/Umbraco.Core/Composing/BruteForceAssemblyProvider.cs b/src/Umbraco.Core/Composing/BruteForceAssemblyProvider.cs deleted file mode 100644 index 04d0d70475..0000000000 --- a/src/Umbraco.Core/Composing/BruteForceAssemblyProvider.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security; -using Umbraco.Core.Exceptions; - -namespace Umbraco.Core.Composing -{ - /// - /// lazily load a reference to all local assemblies and gac assemblies - /// - /// - /// This is a modified version of: http://www.dominicpettifer.co.uk/Blog/44/how-to-get-a-reference-to-all-assemblies-in-the--bin-folder - /// - /// We do this because we cannot use AppDomain.Current.GetAssemblies() as this will return only assemblies that have been - /// loaded in the CLR, not all assemblies. - /// See these threads: - /// http://issues.umbraco.org/issue/U5-198 - /// http://stackoverflow.com/questions/3552223/asp-net-appdomain-currentdomain-getassemblies-assemblies-missing-after-app - /// http://stackoverflow.com/questions/2477787/difference-between-appdomain-getassemblies-and-buildmanager-getreferencedassembl - /// - public class BruteForceAssemblyProvider : IAssemblyProvider - { - public BruteForceAssemblyProvider() - { - _allAssemblies = new Lazy>(() => - { - HashSet assemblies = null; - try - { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = GetRootDirectorySafe(); - var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new HashSet(); - foreach (var a in binAssemblyFiles) - { - try - { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); - } - catch (Exception e) - { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } - } - } - - //Since we are only loading in the /bin assemblies above, we will also load in anything that's already loaded (which will include gac items) - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - { - assemblies.Add(a); - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - return assemblies; - }); - } - - private readonly Lazy> _allAssemblies; - private string _rootDir = string.Empty; - - public IEnumerable Assemblies => _allAssemblies.Value; - - // FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here - private string GetRootDirectorySafe() - { - if (string.IsNullOrEmpty(_rootDir) == false) - { - return _rootDir; - } - - var codeBase = Assembly.GetExecutingAssembly().CodeBase; - var uri = new Uri(codeBase); - var path = uri.LocalPath; - var baseDirectory = Path.GetDirectoryName(path); - if (string.IsNullOrEmpty(baseDirectory)) - throw new PanicException("No root directory could be resolved."); - - _rootDir = baseDirectory.Contains("bin") - ? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1) - : baseDirectory; - - return _rootDir; - } - } -} diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 27b13a8365..ee900756c8 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Logging; namespace Umbraco.Core.Composing { + /// public class TypeFinder : ITypeFinder { @@ -212,6 +213,11 @@ namespace Umbraco.Core.Composing /// public virtual Type GetTypeByName(string name) { + + //NOTE: This will not find types in dynamic assemblies unless those assemblies are already loaded + //into the appdomain. + + // This is exactly what the BuildManager does, if the type is an assembly qualified type // name it will find it. if (TypeNameContainsAssembly(name)) diff --git a/src/Umbraco.Core/Composing/TypeFinderConfig.cs b/src/Umbraco.Core/Composing/TypeFinderConfig.cs new file mode 100644 index 0000000000..3dc672b27c --- /dev/null +++ b/src/Umbraco.Core/Composing/TypeFinderConfig.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.UmbracoSettings; + +namespace Umbraco.Core.Composing +{ + /// + /// TypeFinder config via appSettings + /// + internal class TypeFinderConfig : ITypeFinderConfig + { + private readonly ITypeFinderSettings _settings; + private IEnumerable _assembliesAcceptingLoadExceptions; + + public TypeFinderConfig(ITypeFinderSettings settings) + { + _settings = settings; + } + + public IEnumerable AssembliesAcceptingLoadExceptions + { + get + { + if (_assembliesAcceptingLoadExceptions != null) + return _assembliesAcceptingLoadExceptions; + + var s = _settings.AssembliesAcceptingLoadExceptions; + return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) + ? Array.Empty() + : s.Split(',').Select(x => x.Trim()).ToArray(); + } + } + } +} diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index e12a81be36..39f5a4209f 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -370,6 +370,8 @@ namespace Umbraco.Core.Runtime /// /// 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(Assembly.GetEntryAssembly())); diff --git a/src/Umbraco.Web/Composing/BuildManagerAssemblyProvider.cs b/src/Umbraco.Web/Composing/BuildManagerAssemblyProvider.cs deleted file mode 100644 index 3c5cebe03e..0000000000 --- a/src/Umbraco.Web/Composing/BuildManagerAssemblyProvider.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security; -using System.Web.Compilation; -using Umbraco.Core.Composing; -using Umbraco.Core.Hosting; -using Umbraco.Core.IO; -using Umbraco.Core.Logging; - -namespace Umbraco.Web.Composing -{ - /// - /// Uses the BuildManager to provide a list of assemblies to scan - /// - internal class BuildManagerAssemblyProvider : BruteForceAssemblyProvider, IAssemblyProvider - { - private readonly Lazy> _allAssemblies; - - public BuildManagerAssemblyProvider(IIOHelper ioHelper, - IHostingEnvironment hostingEnvironment, - ILogger logger) - { - _allAssemblies = new Lazy>(() => - { - var isHosted = hostingEnvironment.IsHosted; - try - { - if (isHosted) - { - var assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); - - //here we are trying to get the App_Code assembly - var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(ioHelper.MapPath(ioHelper.ResolveUrl("~/App_code"))); - //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any())) - { - try - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } - catch (FileNotFoundException ex) - { - //this will occur if it cannot load the assembly - logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code"); - } - } - - return assemblies; - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - // Not hosted, just use the default implementation - return new HashSet(base.Assemblies); - }); - } - - IEnumerable IAssemblyProvider.Assemblies => _allAssemblies.Value; - } -} diff --git a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs deleted file mode 100644 index d3502c36fb..0000000000 --- a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Compilation; -using Umbraco.Core.Configuration; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Logging; - -namespace Umbraco.Web.Composing -{ - - /// - /// An implementation of TypeFinder that uses the BuildManager to resolve references for aspnet framework hosted websites - /// - /// - /// This finder will also try to resolve dynamic assemblies created from App_Code - /// - internal class BuildManagerTypeFinder : TypeFinder, ITypeFinder - { - - public BuildManagerTypeFinder( - ILogger logger, - IAssemblyProvider assemblyProvider, - ITypeFinderConfig typeFinderConfig = null) : base(logger, assemblyProvider, typeFinderConfig) - { - if (logger == null) throw new ArgumentNullException(nameof(logger)); - } - - /// - /// Explicitly implement and return result from BuildManager - /// - /// - /// - Type ITypeFinder.GetTypeByName (string name) => BuildManager.GetType(name, false); - - - /// - /// TypeFinder config via appSettings - /// - internal class TypeFinderConfig : ITypeFinderConfig - { - private readonly ITypeFinderSettings _settings; - private IEnumerable _assembliesAcceptingLoadExceptions; - - public TypeFinderConfig(ITypeFinderSettings settings) - { - _settings = settings; - } - - public IEnumerable AssembliesAcceptingLoadExceptions - { - get - { - if (_assembliesAcceptingLoadExceptions != null) - return _assembliesAcceptingLoadExceptions; - - var s = _settings.AssembliesAcceptingLoadExceptions; - return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) - ? Array.Empty() - : s.Split(',').Select(x => x.Trim()).ToArray(); - } - } - } - } -} diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 5fab9ff9d4..ea08dc9135 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -21,8 +21,6 @@ namespace Umbraco.Web.Runtime /// On top of CoreRuntime, handles all of the web-related runtime aspects of Umbraco. public class WebRuntime : CoreRuntime { - private BuildManagerTypeFinder _typeFinder; - /// /// Initializes a new instance of the class. /// @@ -92,8 +90,6 @@ namespace Umbraco.Web.Runtime #region Getters - //protected override ITypeFinder GetTypeFinder() => _typeFinder ?? (_typeFinder = new BuildManagerTypeFinder(IOHelper, HostingEnvironment, Logger, new BuildManagerTypeFinder.TypeFinderConfig(new TypeFinderSettings()))); - protected override AppCaches GetAppCaches() => new AppCaches( // we need to have the dep clone runtime cache provider to ensure // all entities are cached properly (cloned in and cloned out) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 27ea9443a0..e9a4e04e58 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -143,8 +143,6 @@ - - From 66cd25d8f56e0e459ed881e9fa3e633fb8b1ed1f Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 11 Mar 2020 13:10:34 +1100 Subject: [PATCH 07/10] Adds notes --- .../Composing/DefaultUmbracoAssemblyProvider.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs index c13a42ece7..61d7cff240 100644 --- a/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs +++ b/src/Umbraco.Core/Composing/DefaultUmbracoAssemblyProvider.cs @@ -29,6 +29,12 @@ namespace Umbraco.Core.Composing _entryPointAssembly = entryPointAssembly ?? throw new ArgumentNullException(nameof(entryPointAssembly)); } + // TODO: It would be worth investigating a netcore3 version of this which would use + // var allAssemblies = System.Runtime.Loader.AssemblyLoadContext.All.SelectMany(x => x.Assemblies); + // that will still only resolve Assemblies that are already loaded but it would also make it possible to + // query dynamically generated assemblies once they are added. It would also provide the ability to probe + // assembly locations that are not in the same place as the entry point assemblies. + public IEnumerable Assemblies { get From 3bfa2e76cb91902af18215e34bf8ef00ccdb847d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 11 Mar 2020 15:28:08 +1100 Subject: [PATCH 08/10] Adjust type finder and adds benchmark --- src/Umbraco.Core/Composing/TypeFinder.cs | 51 +++++++++++++------ src/Umbraco.Core/Composing/TypeHelper.cs | 6 +-- .../TypeFinderBenchmarks.cs | 30 +++++++++++ .../Umbraco.Tests.Benchmarks.csproj | 1 + 4 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index ee900756c8..cfed155d83 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -16,6 +16,13 @@ namespace Umbraco.Core.Composing { private readonly ILogger _logger; private readonly IAssemblyProvider _assemblyProvider; + private volatile HashSet _localFilteredAssemblyCache; + private readonly object _localFilteredAssemblyCacheLocker = new object(); + private readonly List _notifiedLoadExceptionAssemblies = new List(); + private static readonly ConcurrentDictionary TypeNamesCache = new ConcurrentDictionary(); + private readonly string[] _assembliesAcceptingLoadExceptions; + + internal bool QueryWithReferencingAssemblies = true; public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig typeFinderConfig = null) { @@ -24,12 +31,6 @@ namespace Umbraco.Core.Composing _assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty(); } - private volatile HashSet _localFilteredAssemblyCache; - private readonly object _localFilteredAssemblyCacheLocker = new object(); - private readonly List _notifiedLoadExceptionAssemblies = new List(); - private static readonly ConcurrentDictionary TypeNamesCache= new ConcurrentDictionary(); - private readonly string[] _assembliesAcceptingLoadExceptions; - private bool AcceptsLoadExceptions(Assembly a) { if (_assembliesAcceptingLoadExceptions.Length == 0) @@ -268,18 +269,24 @@ namespace Umbraco.Core.Composing var stack = new Stack(); stack.Push(attributeType.Assembly); + if (!QueryWithReferencingAssemblies) + { + foreach (var a in candidateAssemblies) + stack.Push(a); + } + while (stack.Count > 0) { var assembly = stack.Pop(); - Type[] assemblyTypes = null; + IReadOnlyList assemblyTypes = null; if (assembly != attributeType.Assembly || attributeAssemblyIsCandidate) { // get all assembly types that can be assigned to baseType try { assemblyTypes = GetTypesWithFormattedException(assembly) - .ToArray(); // in try block + .ToList(); // in try block } catch (TypeLoadException ex) { @@ -299,10 +306,13 @@ namespace Umbraco.Core.Composing if (assembly != attributeType.Assembly && assemblyTypes.Where(attributeType.IsAssignableFrom).Any() == false) continue; - foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + if (QueryWithReferencingAssemblies) { - candidateAssemblies.Remove(referencing); - stack.Push(referencing); + foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + { + candidateAssemblies.Remove(referencing); + stack.Push(referencing); + } } } @@ -333,19 +343,25 @@ namespace Umbraco.Core.Composing var stack = new Stack(); stack.Push(baseType.Assembly); + if (!QueryWithReferencingAssemblies) + { + foreach (var a in candidateAssemblies) + stack.Push(a); + } + while (stack.Count > 0) { var assembly = stack.Pop(); // get all assembly types that can be assigned to baseType - Type[] assemblyTypes = null; + IReadOnlyList assemblyTypes = null; if (assembly != baseType.Assembly || baseTypeAssemblyIsCandidate) { try { assemblyTypes = GetTypesWithFormattedException(assembly) .Where(baseType.IsAssignableFrom) - .ToArray(); // in try block + .ToList(); // in try block } catch (TypeLoadException ex) { @@ -365,10 +381,13 @@ namespace Umbraco.Core.Composing if (assembly != baseType.Assembly && assemblyTypes.All(x => x.IsSealed)) continue; - foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + if (QueryWithReferencingAssemblies) { - candidateAssemblies.Remove(referencing); - stack.Push(referencing); + foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies)) + { + candidateAssemblies.Remove(referencing); + stack.Push(referencing); + } } } diff --git a/src/Umbraco.Core/Composing/TypeHelper.cs b/src/Umbraco.Core/Composing/TypeHelper.cs index 28eab6a5ec..1987a4059c 100644 --- a/src/Umbraco.Core/Composing/TypeHelper.cs +++ b/src/Umbraco.Core/Composing/TypeHelper.cs @@ -82,9 +82,9 @@ namespace Umbraco.Core.Composing /// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot /// reference that assembly, same with the global.asax assembly. /// - public static Assembly[] GetReferencingAssemblies(Assembly assembly, IEnumerable assemblies) + public static IReadOnlyList GetReferencingAssemblies(Assembly assembly, IEnumerable assemblies) { - if (assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly()) + if (assembly.IsDynamic || assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly()) return EmptyAssemblies; @@ -92,7 +92,7 @@ namespace Umbraco.Core.Composing // should only be scanning those assemblies because any other assembly will definitely not // contain sub type's of the one we're currently looking for var name = assembly.GetName().Name; - return assemblies.Where(x => x == assembly || HasReference(x, name)).ToArray(); + return assemblies.Where(x => x == assembly || HasReference(x, name)).ToList(); } /// diff --git a/src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs new file mode 100644 index 0000000000..7b4322bfac --- /dev/null +++ b/src/Umbraco.Tests.Benchmarks/TypeFinderBenchmarks.cs @@ -0,0 +1,30 @@ +using BenchmarkDotNet.Attributes; +using System; +using System.Linq; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; +using Umbraco.Tests.Benchmarks.Config; + +namespace Umbraco.Tests.Benchmarks +{ + [MediumRunJob] + [MemoryDiagnoser] + public class TypeFinderBenchmarks + { + + [Benchmark(Baseline = true)] + public void WithGetReferencingAssembliesCheck() + { + var typeFinder1 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + var found = typeFinder1.FindClassesOfType().Count(); + } + + [Benchmark] + public void WithoutGetReferencingAssembliesCheck() + { + var typeFinder2 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly)); + typeFinder2.QueryWithReferencingAssemblies = false; + var found = typeFinder2.FindClassesOfType().Count(); + } + } +} diff --git a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj index 7566d8ab85..84ec535b9d 100644 --- a/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj +++ b/src/Umbraco.Tests.Benchmarks/Umbraco.Tests.Benchmarks.csproj @@ -61,6 +61,7 @@ + From e3997a4ab2455d65f97a2b0cb07055d32c44e285 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 11 Mar 2020 15:32:26 +1100 Subject: [PATCH 09/10] adds notes --- src/Umbraco.Core/Composing/TypeFinder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index cfed155d83..79fddad1ca 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -22,6 +22,7 @@ namespace Umbraco.Core.Composing private static readonly ConcurrentDictionary TypeNamesCache = new ConcurrentDictionary(); private readonly string[] _assembliesAcceptingLoadExceptions; + // used for benchmark tests internal bool QueryWithReferencingAssemblies = true; public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig typeFinderConfig = null) From 84847267ba7dec6e8863da83a0a14cd6476355ab Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 12 Mar 2020 15:26:06 +1100 Subject: [PATCH 10/10] updates CoreRuntime with notes and fallback --- src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs index 39f5a4209f..fe51609c88 100644 --- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs @@ -372,7 +372,19 @@ namespace Umbraco.Core.Runtime 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(Assembly.GetEntryAssembly())); + => 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() + // which will just return Umbraco.Infrastructure (currently with netframework) and for our purposes that is OK. + // If you are curious... There is really no way to get the entry assembly in netframework without the hosting website having it's own + // code compiled for the global.asax which is the entry point. Because the default global.asax for umbraco websites is just a file inheriting + // from Umbraco.Web.UmbracoApplication, the global.asax file gets dynamically compiled into a DLL in the dynamic folder (we can get an instance + // of that, but this doesn't really help us) but the actually entry execution is still Umbraco.Web. So that is the 'highest' level entry point + // 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())); ///