From 6398f1e2166fb42acdd8528580a403c16595eb30 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 14:26:06 +1100 Subject: [PATCH 01/11] Creates ITypeFinder interface and simplifies type finder with TypeFinderExtensions, moves GetTypeByName to TypeHelper using assembly name parsing instead of scanning every assembly. --- .../Cache/FastDictionaryAppCache.cs | 6 +- .../Cache/FastDictionaryAppCacheBase.cs | 4 +- .../Cache/ObjectCacheAppCache.cs | 4 +- .../Composing/TypeHelper.cs | 61 +++ src/Umbraco.Core/Composing/ITypeFinder.cs | 50 ++ src/Umbraco.Core/Composing/TypeFinder.cs | 412 ++++++--------- .../Composing/TypeFinderExtensions.cs | 45 ++ src/Umbraco.Core/Composing/TypeLoader.cs | 19 +- src/Umbraco.Core/Runtime/CoreRuntime.cs | 5 +- src/Umbraco.Core/Umbraco.Core.csproj | 5 +- src/Umbraco.Examine/LuceneIndexCreator.cs | 2 +- .../Components/ComponentTests.cs | 3 +- .../Composing/ComposingTestBase.cs | 3 +- .../Composing/CompositionTests.cs | 3 +- .../Composing/TypeFinderTests.cs | 472 +----------------- .../Composing/TypeLoaderTests.cs | 3 +- src/Umbraco.Tests/CoreThings/UdiTests.cs | 3 +- .../Published/PropertyCacheLevelTests.cs | 28 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 6 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 3 +- .../Stubs/TestControllerFactory.cs | 4 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 3 +- .../Web/TemplateUtilitiesTests.cs | 3 +- src/Umbraco.Tests/Web/UmbracoHelperTests.cs | 3 +- src/Umbraco.Web/Editors/EntityController.cs | 1 + 25 files changed, 377 insertions(+), 774 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Cache/FastDictionaryAppCache.cs (97%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Cache/FastDictionaryAppCacheBase.cs (98%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Cache/ObjectCacheAppCache.cs (99%) create mode 100644 src/Umbraco.Core/Composing/ITypeFinder.cs create mode 100644 src/Umbraco.Core/Composing/TypeFinderExtensions.cs diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs similarity index 97% rename from src/Umbraco.Core/Cache/FastDictionaryAppCache.cs rename to src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs index b38f36a7d8..3bd29c3f5f 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs @@ -10,13 +10,15 @@ namespace Umbraco.Core.Cache /// /// Implements a fast on top of a concurrent dictionary. /// - internal class FastDictionaryAppCache : IAppCache + public class FastDictionaryAppCache : IAppCache { /// /// Gets the internal items dictionary, for tests only! /// internal readonly ConcurrentDictionary> Items = new ConcurrentDictionary>(); + public int Count => Items.Count; + /// public object Get(string cacheKey) { @@ -76,7 +78,7 @@ namespace Umbraco.Core.Cache /// public void ClearOfType(string typeName) { - var type = TypeFinder.GetTypeByName(typeName); + var type = TypeHelper.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; diff --git a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs similarity index 98% rename from src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs rename to src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs index eb84423f32..b49ecea671 100644 --- a/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Cache /// /// Provides a base class to fast, dictionary-based implementations. /// - internal abstract class FastDictionaryAppCacheBase : IAppCache + public abstract class FastDictionaryAppCacheBase : IAppCache { // prefix cache keys so we know which one are ours protected const string CacheItemPrefix = "umbrtmche"; @@ -116,7 +116,7 @@ namespace Umbraco.Core.Cache /// public virtual void ClearOfType(string typeName) { - var type = TypeFinder.GetTypeByName(typeName); + var type = TypeHelper.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; try diff --git a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs similarity index 99% rename from src/Umbraco.Core/Cache/ObjectCacheAppCache.cs rename to src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs index ad7f3d6bf7..7d5081baa5 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Cache /// /// Gets the internal memory cache, for tests only! /// - internal ObjectCache MemoryCache { get; private set; } + public ObjectCache MemoryCache { get; private set; } /// public object Get(string key) @@ -178,7 +178,7 @@ namespace Umbraco.Core.Cache /// public virtual void ClearOfType(string typeName) { - var type = TypeFinder.GetTypeByName(typeName); + var type = TypeHelper.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; try diff --git a/src/Umbraco.Abstractions/Composing/TypeHelper.cs b/src/Umbraco.Abstractions/Composing/TypeHelper.cs index 40a1adc118..768709c44c 100644 --- a/src/Umbraco.Abstractions/Composing/TypeHelper.cs +++ b/src/Umbraco.Abstractions/Composing/TypeHelper.cs @@ -8,6 +8,7 @@ using System.Reflection; namespace Umbraco.Core.Composing { + /// /// A utility class for type checking, this provides internal caching so that calls to these methods will be faster /// than doing a manual type check in c# @@ -21,6 +22,66 @@ namespace Umbraco.Core.Composing private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; + /// + /// Returns a Type for the string type name + /// + /// + /// + public static Type GetTypeByName(string name) + { + var type = Type.GetType(name); + if (type != null) return type; + + // now try fall back procedures. null may be returned because Type.GetName only returns types that have already been loaded in the appdomain + // and this type might not be there yet, so we need to parse the type name and then try to load in via assembly. + + var typeName = TypeName.Parse(name); + try + { + var assembly = Assembly.Load(typeName.AssemblyName); + type = assembly.GetType(typeName.Name); + return type; + } + catch (NotSupportedException) + { + throw; + } + catch (Exception) + { + return null; + } + } + + /// + /// Used to parse a fully qualified type name + /// + /// + /// Does not support generics. + /// This is a simple utility class and is not an exhaustive type name parser (which doesn't exist in .net strangely) + /// + private class TypeName + { + private TypeName(string name, AssemblyName assemblyName) + { + Name = name; + AssemblyName = assemblyName; + } + + public static TypeName Parse(string name) + { + if (name.Contains("[[")) + throw new NotSupportedException($"{nameof(TypeName)} does not support generic types"); + + var index = name.IndexOf(','); + return index > 0 + ? new TypeName(name.Substring(0, index).Trim(), new AssemblyName(name.Substring(index + 1).Trim())) + : new TypeName(name, null); + } + + public string Name { get; } + public AssemblyName AssemblyName { get; } + } + /// /// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also deal with array types and return List{T} for those too. /// If it cannot be done, null is returned. diff --git a/src/Umbraco.Core/Composing/ITypeFinder.cs b/src/Umbraco.Core/Composing/ITypeFinder.cs new file mode 100644 index 0000000000..844719b782 --- /dev/null +++ b/src/Umbraco.Core/Composing/ITypeFinder.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + public interface ITypeFinder + { + /// + /// Return a list of found local Assemblies that Umbraco should scan for type finding + /// + /// + IEnumerable AssembliesToScan { get; } + + /// + /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + Type attributeType, + IEnumerable assemblies = null, + bool onlyConcreteClasses = true); + + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + /// + IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable assemblies = null, bool onlyConcreteClasses = true); + + /// + /// Finds any classes with the attribute. + /// + /// The attribute type + /// The assemblies. + /// if set to true only concrete classes. + /// + IEnumerable FindClassesWithAttribute( + Type attributeType, + IEnumerable assemblies, + bool onlyConcreteClasses); + } +} diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 5ad1e43580..a1b1bf2fef 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -6,11 +6,9 @@ using System.Linq; using System.Reflection; using System.Security; using System.Text; -using System.Web; using System.Web.Compilation; -using System.Web.Hosting; -using Umbraco.Core.Composing; using Umbraco.Core.IO; +using Umbraco.Core.Logging; namespace Umbraco.Core.Composing { @@ -18,14 +16,107 @@ namespace Umbraco.Core.Composing /// A utility class to find all classes of a certain type by reflection in the current bin folder /// of the web application. /// - public static class TypeFinder + public class TypeFinder : ITypeFinder { - private static volatile HashSet _localFilteredAssemblyCache; - private static readonly object LocalFilteredAssemblyCacheLocker = new object(); - private static readonly List NotifiedLoadExceptionAssemblies = new List(); - private static string[] _assembliesAcceptingLoadExceptions; + private readonly ILogger _logger; - private static string[] AssembliesAcceptingLoadExceptions + public TypeFinder(ILogger logger) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _allAssemblies = new Lazy>(() => + { + HashSet assemblies = null; + try + { + var isHosted = IOHelper.IsHosted; + + try + { + if (isHosted) + { + assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + if (assemblies == null) + { + //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 = IOHelper.GetRootDirectoryBinFolder(); + 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); + } + + //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"); + } + } + } + catch (InvalidOperationException e) + { + if (e.InnerException is SecurityException == false) + throw; + } + + return assemblies; + }); + } + + //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 string[] _assembliesAcceptingLoadExceptions; + + private string[] AssembliesAcceptingLoadExceptions { get { @@ -39,7 +130,7 @@ namespace Umbraco.Core.Composing } } - private static bool AcceptsLoadExceptions(Assembly a) + private bool AcceptsLoadExceptions(Assembly a) { if (AssembliesAcceptingLoadExceptions.Length == 0) return false; @@ -67,118 +158,25 @@ namespace Umbraco.Core.Composing /// 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 /// - internal static HashSet GetAllAssemblies() + private IEnumerable GetAllAssemblies() { - return AllAssemblies.Value; + return _allAssemblies.Value; } - //Lazy access to the all assemblies list - private static readonly Lazy> AllAssemblies = new Lazy>(() => + /// + public IEnumerable AssembliesToScan { - HashSet assemblies = null; - try + get { - var isHosted = IOHelper.IsHosted; - - try + lock (_localFilteredAssemblyCacheLocker) { - if (isHosted) - { - assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } + if (_localFilteredAssemblyCache != null) + return _localFilteredAssemblyCache; - if (assemblies == null) - { - //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 = IOHelper.GetRootDirectoryBinFolder(); - 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; - } - } - } - } - - //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. - if (assemblies.Any() == false) - { - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) - { - assemblies.Add(a); - } - } - - //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 - Current.Logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code"); - } - } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - return assemblies; - }); - - /// - /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan - /// and excluding the ones passed in and excluding the exclusion list filter, the results of this are - /// cached for performance reasons. - /// - /// - /// - internal static HashSet GetAssembliesWithKnownExclusions( - IEnumerable excludeFromResults = null) - { - lock (LocalFilteredAssemblyCacheLocker) - { - if (_localFilteredAssemblyCache != null) + var assemblies = GetFilteredAssemblies(null, KnownAssemblyExclusionFilter); + _localFilteredAssemblyCache = new HashSet(assemblies); return _localFilteredAssemblyCache; - - var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); - _localFilteredAssemblyCache = new HashSet(assemblies); - return _localFilteredAssemblyCache; + } } } @@ -188,7 +186,7 @@ namespace Umbraco.Core.Composing /// /// /// - private static IEnumerable GetFilteredAssemblies( + private IEnumerable GetFilteredAssemblies( IEnumerable excludeFromResults = null, string[] exclusionFilter = null) { @@ -210,7 +208,7 @@ 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" /// - internal static readonly string[] KnownAssemblyExclusionFilter = { + private static readonly string[] KnownAssemblyExclusionFilter = { "Antlr3.", "AutoMapper,", "AutoMapper.", @@ -261,115 +259,40 @@ namespace Umbraco.Core.Composing }; /// - /// Finds any classes derived from the type T that contain the attribute TAttribute + /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute() - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); - } - - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(assemblies, true); - } - - /// - /// Finds any classes derived from the type T that contain the attribute TAttribute - /// - /// - /// + /// + /// /// /// /// - public static IEnumerable FindClassesOfTypeWithAttribute( - IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute + public IEnumerable FindClassesOfTypeWithAttribute( + Type assignTypeFrom, + Type attributeType, + IEnumerable assemblies = null, + bool onlyConcreteClasses = true) { - return FindClassesOfTypeWithAttribute(typeof(T), assemblies, onlyConcreteClasses); + var assemblyList = (assemblies ?? AssembliesToScan).ToList(); + + return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses, + //the additional filter will ensure that any found types also have the attribute applied. + t => t.GetCustomAttributes(attributeType, false).Any()); } /// - /// Finds any classes derived from the assignTypeFrom Type that contain the attribute TAttribute + /// Returns all types found of in the assemblies specified of type T /// - /// /// /// /// /// - public static IEnumerable FindClassesOfTypeWithAttribute( - Type assignTypeFrom, - IEnumerable assemblies, - bool onlyConcreteClasses) - where TAttribute : Attribute + public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable assemblies = null, bool onlyConcreteClasses = true) { - if (assemblies == null) throw new ArgumentNullException(nameof(assemblies)); + var assemblyList = (assemblies ?? AssembliesToScan).ToList(); - return GetClassesWithBaseType(assignTypeFrom, assemblies, onlyConcreteClasses, - //the additional filter will ensure that any found types also have the attribute applied. - t => t.GetCustomAttributes(false).Any()); + return GetClassesWithBaseType(assignTypeFrom, assemblyList, onlyConcreteClasses); } - - /// - /// Searches all filtered local assemblies specified for classes of the type passed in. - /// - /// - /// - public static IEnumerable FindClassesOfType() - { - return FindClassesOfType(GetAssembliesWithKnownExclusions(), true); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException(nameof(assemblies)); - - return GetClassesWithBaseType(typeof(T), assemblies, onlyConcreteClasses); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies) - { - return FindClassesOfType(assemblies, true); - } - - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) - where T : Attribute - { - return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); - } - + /// /// Finds any classes with the attribute. /// @@ -377,40 +300,19 @@ namespace Umbraco.Core.Composing /// The assemblies. /// if set to true only concrete classes. /// - public static IEnumerable FindClassesWithAttribute( + public IEnumerable FindClassesWithAttribute( Type attributeType, - IEnumerable assemblies, - bool onlyConcreteClasses) + IEnumerable assemblies = null, + bool onlyConcreteClasses = true) { - return GetClassesWithAttribute(attributeType, assemblies, onlyConcreteClasses); - } + var assemblyList = (assemblies ?? AssembliesToScan).ToList(); - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies) - where T : Attribute - { - return FindClassesWithAttribute(assemblies, true); - } - - /// - /// Finds the classes with attribute in filtered local assemblies - /// - /// - /// - public static IEnumerable FindClassesWithAttribute() - where T : Attribute - { - return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); + return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses); } #region Private methods - private static IEnumerable GetClassesWithAttribute( + private IEnumerable GetClassesWithAttribute( Type attributeType, IEnumerable assemblies, bool onlyConcreteClasses) @@ -441,7 +343,7 @@ namespace Umbraco.Core.Composing } catch (TypeLoadException ex) { - Current.Logger.Error(typeof(TypeFinder), ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); + _logger.Error(typeof(TypeFinder), ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); continue; } @@ -477,7 +379,7 @@ namespace Umbraco.Core.Composing /// /// An additional filter to apply for what types will actually be included in the return value /// - private static IEnumerable GetClassesWithBaseType( + private IEnumerable GetClassesWithBaseType( Type baseType, IEnumerable assemblies, bool onlyConcreteClasses, @@ -507,7 +409,7 @@ namespace Umbraco.Core.Composing } catch (TypeLoadException ex) { - Current.Logger.Error(typeof(TypeFinder), ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); + _logger.Error(typeof(TypeFinder), ex, "Could not query types on {Assembly} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly); continue; } @@ -533,7 +435,7 @@ namespace Umbraco.Core.Composing return types; } - internal static IEnumerable GetTypesWithFormattedException(Assembly a) + private IEnumerable GetTypesWithFormattedException(Assembly a) { //if the assembly is dynamic, do not try to scan it if (a.IsDynamic) @@ -569,12 +471,12 @@ namespace Umbraco.Core.Composing if (AcceptsLoadExceptions(a) == false) throw ex; // log a warning, and return what we can - lock (NotifiedLoadExceptionAssemblies) + lock (_notifiedLoadExceptionAssemblies) { - if (NotifiedLoadExceptionAssemblies.Contains(a.FullName) == false) + if (_notifiedLoadExceptionAssemblies.Contains(a.FullName) == false) { - NotifiedLoadExceptionAssemblies.Add(a.FullName); - Current.Logger.Warn(typeof (TypeFinder), ex, "Could not load all types from {TypeName}.", a.GetName().Name); + _notifiedLoadExceptionAssemblies.Add(a.FullName); + _logger.Warn(typeof (TypeFinder), ex, "Could not load all types from {TypeName}.", a.GetName().Name); } } return rex.Types.WhereNotNull().ToArray(); @@ -595,8 +497,7 @@ namespace Umbraco.Core.Composing sb.Append(". "); sb.Append(loaderException.GetType().FullName); - var tloadex = loaderException as TypeLoadException; - if (tloadex != null) + if (loaderException is TypeLoadException tloadex) { sb.Append(" on "); sb.Append(tloadex.TypeName); @@ -609,24 +510,5 @@ namespace Umbraco.Core.Composing #endregion - public static Type GetTypeByName(string typeName) - { - var type = BuildManager.GetType(typeName, false); - if (type != null) return type; - - // TODO: This isn't very elegant, and will have issues since the AppDomain.CurrentDomain - // doesn't actualy load in all assemblies, only the types that have been referenced so far. - // However, in a web context, the BuildManager will have executed which will force all assemblies - // to be loaded so it's fine for now. - // It could be fairly easy to parse the typeName to get the assembly name and then do Assembly.Load and - // find the type from there. - - //now try fall back procedures. - type = Type.GetType(typeName); - if (type != null) return type; - return AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.GetType(typeName)) - .FirstOrDefault(x => x != null); - } } } diff --git a/src/Umbraco.Core/Composing/TypeFinderExtensions.cs b/src/Umbraco.Core/Composing/TypeFinderExtensions.cs new file mode 100644 index 0000000000..e364790556 --- /dev/null +++ b/src/Umbraco.Core/Composing/TypeFinderExtensions.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Umbraco.Core.Composing +{ + public static class TypeFinderExtensions + { + /// + /// Finds any classes derived from the type T that contain the attribute TAttribute + /// + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfTypeWithAttribute(this ITypeFinder typeFinder, IEnumerable assemblies = null, bool onlyConcreteClasses = true) + where TAttribute : Attribute + => typeFinder.FindClassesOfTypeWithAttribute(typeof(T), typeof(TAttribute), assemblies, onlyConcreteClasses); + + /// + /// Returns all types found of in the assemblies specified of type T + /// + /// + /// + /// + /// + /// + public static IEnumerable FindClassesOfType(this ITypeFinder typeFinder, IEnumerable assemblies = null, bool onlyConcreteClasses = true) + => typeFinder.FindClassesOfType(typeof(T), assemblies, onlyConcreteClasses); + + /// + /// Finds the classes with attribute. + /// + /// + /// + /// The assemblies. + /// if set to true only concrete classes. + /// + public static IEnumerable FindClassesWithAttribute(this ITypeFinder typeFinder, IEnumerable assemblies = null, bool onlyConcreteClasses = true) + where T : Attribute + => typeFinder.FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); + } +} diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index fe7a561eca..daa95ede49 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.Composing /// Provides methods to find and instantiate types. /// /// - /// This class should be used to get all types, the class should never be used directly. + /// This class should be used to get all types, the class should never be used directly. /// In most cases this class is not used directly but through extension methods that retrieve specific types. /// This class caches the types it knows to avoid excessive assembly scanning and shorten startup times, relying /// on a hash of the DLLs in the ~/bin folder to check for cache expiration. @@ -48,22 +48,25 @@ namespace Umbraco.Core.Composing /// /// Initializes a new instance of the class. /// + /// /// The application runtime cache. /// Files storage location. /// A profiling logger. - public TypeLoader(IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger) - : this(runtimeCache, localTempPath, logger, true) + public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger) + : this(typeFinder, runtimeCache, localTempPath, logger, true) { } /// /// Initializes a new instance of the class. /// + /// /// The application runtime cache. /// Files storage location. /// A profiling logger. /// Whether to detect changes using hashes. - internal TypeLoader(IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges) + internal TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges) { + TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); _localTempPath = localTempPath; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -105,6 +108,12 @@ namespace Umbraco.Core.Composing internal TypeLoader() { } + /// + /// Returns the underlying + /// + // ReSharper disable once MemberCanBePrivate.Global + public ITypeFinder TypeFinder { get; } + /// /// Gets or sets the set of assemblies to scan. /// @@ -117,7 +126,7 @@ namespace Umbraco.Core.Composing // internal for tests internal IEnumerable AssembliesToScan { - get => _assemblies ?? (_assemblies = TypeFinder.GetAssembliesWithKnownExclusions()); + get => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan); set => _assemblies = value; } diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 5b069641c4..8ccb740cc2 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -111,8 +111,9 @@ namespace Umbraco.Core.Runtime // configs var configs = GetConfigs(); - // type loader - var typeLoader = new TypeLoader(appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); + // type finder/loader + var typeFinder = new TypeFinder(Logger); + var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 5f18cb3d95..87f20eb070 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -125,11 +125,8 @@ --> - - - @@ -142,7 +139,9 @@ + + diff --git a/src/Umbraco.Examine/LuceneIndexCreator.cs b/src/Umbraco.Examine/LuceneIndexCreator.cs index 806d0edc7a..97a929c9c8 100644 --- a/src/Umbraco.Examine/LuceneIndexCreator.cs +++ b/src/Umbraco.Examine/LuceneIndexCreator.cs @@ -38,7 +38,7 @@ namespace Umbraco.Examine if (!configuredDirectoryFactory.IsNullOrWhiteSpace()) { //this should be a fully qualified type - var factoryType = TypeFinder.GetTypeByName(configuredDirectoryFactory); + var factoryType = TypeHelper.GetTypeByName(configuredDirectoryFactory); if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory); var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType); return directoryFactory.CreateDirectory(dirInfo); diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 2ba94d8c78..2bd0d5d471 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -361,7 +361,8 @@ namespace Umbraco.Tests.Components [Test] public void AllComposers() { - var typeLoader = new TypeLoader(AppCaches.Disabled.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of()); + var typeFinder = new TypeFinder(Mock.Of()); + var typeLoader = new TypeLoader(typeFinder, AppCaches.Disabled.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of()); var register = MockRegister(); var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index ef364afd38..4b5be30cd9 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -22,7 +22,8 @@ namespace Umbraco.Tests.Composing { ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); - TypeLoader = new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, detectChanges: false) + var typeFinder = new TypeFinder(Mock.Of()); + TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, detectChanges: false) { AssembliesToScan = AssembliesToScan }; diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs index 33855a8bfb..9668d7619a 100644 --- a/src/Umbraco.Tests/Composing/CompositionTests.cs +++ b/src/Umbraco.Tests/Composing/CompositionTests.cs @@ -36,7 +36,8 @@ namespace Umbraco.Tests.Composing .Returns(() => factoryFactory?.Invoke(mockedFactory)); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeLoader = new TypeLoader(Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), logger); + var typeFinder = new TypeFinder(Mock.Of()); + var typeLoader = new TypeLoader(typeFinder, Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), logger); var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of()); // create the factory, ensure it is the mocked factory diff --git a/src/Umbraco.Tests/Composing/TypeFinderTests.cs b/src/Umbraco.Tests/Composing/TypeFinderTests.cs index ca622e9288..fd070dee2a 100644 --- a/src/Umbraco.Tests/Composing/TypeFinderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeFinderTests.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading; using System.Web; using System.Web.Compilation; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Composing; @@ -56,39 +57,19 @@ namespace Umbraco.Tests.Composing [Test] public void Find_Class_Of_Type_With_Attribute() { - - var typesFound = TypeFinder.FindClassesOfTypeWithAttribute(_assemblies); + var typeFinder = new TypeFinder(GetTestProfilingLogger()); + var typesFound = typeFinder.FindClassesOfTypeWithAttribute(_assemblies); Assert.AreEqual(2, typesFound.Count()); } - //[Test] - //public void Find_Classes_Of_Type() - //{ - // var typesFound = TypeFinder.FindClassesOfType(_assemblies); - // var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies); - - // foreach (var type in typesFound) - // Console.WriteLine(type); - // Console.WriteLine(); - // foreach (var type in originalTypesFound) - // Console.WriteLine(type); - - // // 6 classes in _assemblies implement IApplicationEventHandler - // Assert.AreEqual(6, typesFound.Count()); - - // // however, - // // Umbraco.Core.Profiling.WebProfiler is internal and is not returned by TypeFinderOriginal, - // // that's a known issue of the legacy type finder, so we have to tweak the count here. - // Assert.AreEqual(5, originalTypesFound.Count()); - //} - [Test] public void Find_Classes_With_Attribute() { - var typesFound = TypeFinder.FindClassesWithAttribute(_assemblies); + var typeFinder = new TypeFinder(GetTestProfilingLogger()); + 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 }); + typesFound = typeFinder.FindClassesWithAttribute(new[] { typeof (UmbracoContext).Assembly }); Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree] } @@ -122,447 +103,6 @@ namespace Umbraco.Tests.Composing } - //USED FOR THE ABOVE TESTS - // see this issue for details: http://issues.umbraco.org/issue/U4-1187 - internal static class TypeFinderOriginal - { - - private static readonly ConcurrentBag LocalFilteredAssemblyCache = new ConcurrentBag(); - private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim(); - private static ReadOnlyCollection _allAssemblies = null; - private static ReadOnlyCollection _binFolderAssemblies = null; - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); - - /// - /// 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 - /// - internal static IEnumerable GetAllAssemblies() - { - if (_allAssemblies == null) - { - using (new WriteLock(Locker)) - { - List assemblies = null; - try - { - var isHosted = HttpContext.Current != null; - - try - { - if (isHosted) - { - assemblies = new List(BuildManager.GetReferencedAssemblies().Cast()); - } - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; - } - - - if (assemblies == null) - { - //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 = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new List(); - 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; - } - } - } - } - - //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. - if (!assemblies.Any()) - { - assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().ToList()); - } - - //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()))) - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } - - //now set the _allAssemblies - _allAssemblies = new ReadOnlyCollection(assemblies); - - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; - - _binFolderAssemblies = _allAssemblies; - } - } - } - - return _allAssemblies; - } - - /// - /// Returns only assemblies found in the bin folder that have been loaded into the app domain. - /// - /// - /// - /// This will be used if we implement App_Plugins from Umbraco v5 but currently it is not used. - /// - internal static IEnumerable GetBinAssemblies() - { - - if (_binFolderAssemblies == null) - { - using (new WriteLock(Locker)) - { - var assemblies = GetAssembliesWithKnownExclusions().ToArray(); - var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - var domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName); - var safeDomainAssemblies = new List(); - var binFolderAssemblies = new List(); - - foreach (var a in assemblies) - { - try - { - //do a test to see if its queryable in med trust - var assemblyFile = a.GetAssemblyFile(); - safeDomainAssemblies.Add(a); - } - catch (SecurityException) - { - //we will just ignore this because this will fail - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to - //an assembly that is ok. - } - } - - foreach (var assemblyName in domainAssemblyNames) - { - try - { - var foundAssembly = safeDomainAssemblies.FirstOrDefault(a => a.GetAssemblyFile() == assemblyName.GetAssemblyFile()); - if (foundAssembly != null) - { - binFolderAssemblies.Add(foundAssembly); - } - } - catch (SecurityException) - { - //we will just ignore this because if we are trying to do a call to: - // AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName))) - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to - //an assembly that is ok. - } - } - - _binFolderAssemblies = new ReadOnlyCollection(binFolderAssemblies); - } - } - return _binFolderAssemblies; - } - - /// - /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan - /// and exluding the ones passed in and excluding the exclusion list filter, the results of this are - /// cached for perforance reasons. - /// - /// - /// - internal static IEnumerable GetAssembliesWithKnownExclusions( - IEnumerable excludeFromResults = null) - { - if (LocalFilteredAssemblyCache.Any()) return LocalFilteredAssemblyCache; - using (new WriteLock(LocalFilteredAssemblyCacheLocker)) - { - var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); - foreach (var assembly in assemblies) LocalFilteredAssemblyCache.Add(assembly); - } - return LocalFilteredAssemblyCache; - } - - /// - /// Return a list of found local Assemblies and exluding the ones passed in and excluding the exclusion list filter - /// - /// - /// - /// - private static IEnumerable GetFilteredAssemblies( - IEnumerable excludeFromResults = null, - string[] exclusionFilter = null) - { - if (excludeFromResults == null) - excludeFromResults = new List(); - if (exclusionFilter == null) - exclusionFilter = new string[] { }; - - return GetAllAssemblies() - .Where(x => !excludeFromResults.Contains(x) - && !x.GlobalAssemblyCache - && !exclusionFilter.Any(f => x.FullName.StartsWith(f))); - } - - /// - /// this is our assembly filter to filter out known types that def dont contain types we'd like to find or plugins - /// - /// - /// 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 - /// - internal static readonly string[] KnownAssemblyExclusionFilter = new[] - { - "mscorlib,", - "System.", - "Antlr3.", - "Autofac.", - "Autofac,", - "Castle.", - "ClientDependency.", - "DataAnnotationsExtensions.", - "DataAnnotationsExtensions,", - "Dynamic,", - "HtmlDiff,", - "Iesi.Collections,", - "Microsoft.", - "Newtonsoft.", - "NHibernate.", - "NHibernate,", - "NuGet.", - "RouteDebugger,", - "SqlCE4Umbraco,", - "umbraco.datalayer,", - "umbraco.interfaces,", - "umbraco.providers,", - "Umbraco.Web.UI,", - "Lucene.", - "Examine,", - "Examine.", - "ServiceStack.", - "MySql.", - "HtmlAgilityPack.", - "TidyNet.", - "ICSharpCode.", - "CookComputing.", - /* Mono */ - "MonoDevelop.NUnit", - "Serilog." - }; - - public static IEnumerable FindClassesOfTypeWithAttribute() - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(GetAssembliesWithKnownExclusions(), true); - } - - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies) - where TAttribute : Attribute - { - return FindClassesOfTypeWithAttribute(assemblies, true); - } - - public static IEnumerable FindClassesOfTypeWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) - where TAttribute : Attribute - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - var l = new List(); - foreach (var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where !t.IsInterface - && typeof(T).IsAssignableFrom(t) - && t.GetCustomAttributes(false).Any() - && (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); - } - - return l; - } - - /// - /// Searches all filtered local assemblies specified for classes of the type passed in. - /// - /// - /// - public static IEnumerable FindClassesOfType() - { - return FindClassesOfType(GetAssembliesWithKnownExclusions(), true); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies, bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - - return GetAssignablesFromType(assemblies, onlyConcreteClasses); - } - - /// - /// Returns all types found of in the assemblies specified of type T - /// - /// - /// - /// - public static IEnumerable FindClassesOfType(IEnumerable assemblies) - { - return FindClassesOfType(assemblies, true); - } - - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies, bool onlyConcreteClasses) - where T : Attribute - { - return FindClassesWithAttribute(typeof(T), assemblies, onlyConcreteClasses); - } - - /// - /// Finds the classes with attribute. - /// - /// The attribute type - /// The assemblies. - /// if set to true only concrete classes. - /// - public static IEnumerable FindClassesWithAttribute(Type type, IEnumerable assemblies, bool onlyConcreteClasses) - { - if (assemblies == null) throw new ArgumentNullException("assemblies"); - if (!TypeHelper.IsTypeAssignableFrom(type)) - throw new ArgumentException("The type specified: " + type + " is not an Attribute type"); - - var l = new List(); - foreach (var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where !t.IsInterface && t.GetCustomAttributes(type, false).Any() && (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); - } - - return l; - } - - /// - /// Finds the classes with attribute. - /// - /// - /// The assemblies. - /// - public static IEnumerable FindClassesWithAttribute(IEnumerable assemblies) - where T : Attribute - { - return FindClassesWithAttribute(assemblies, true); - } - - /// - /// Finds the classes with attribute in filtered local assemblies - /// - /// - /// - public static IEnumerable FindClassesWithAttribute() - where T : Attribute - { - return FindClassesWithAttribute(GetAssembliesWithKnownExclusions()); - } - - - #region Private methods - - /// - /// Gets a collection of assignables of type T from a collection of assemblies - /// - /// - /// - /// - /// - private static IEnumerable GetAssignablesFromType(IEnumerable assemblies, bool onlyConcreteClasses) - { - return GetTypes(typeof(T), assemblies, onlyConcreteClasses); - } - - private static IEnumerable GetTypes(Type assignTypeFrom, IEnumerable assemblies, bool onlyConcreteClasses) - { - var l = new List(); - foreach (var a in assemblies) - { - var types = from t in GetTypesWithFormattedException(a) - where !t.IsInterface && assignTypeFrom.IsAssignableFrom(t) && (!onlyConcreteClasses || (t.IsClass && !t.IsAbstract)) - select t; - l.AddRange(types); - } - return l; - } - - private static IEnumerable GetTypesWithFormattedException(Assembly a) - { - //if the assembly is dynamic, do not try to scan it - if (a.IsDynamic) - return Enumerable.Empty(); - - try - { - return a.GetExportedTypes(); - } - catch (ReflectionTypeLoadException ex) - { - var sb = new StringBuilder(); - sb.AppendLine("Could not load types from assembly " + a.FullName + ", errors:"); - foreach (var loaderException in ex.LoaderExceptions.WhereNotNull()) - { - sb.AppendLine("Exception: " + loaderException.ToString()); - } - throw new ReflectionTypeLoadException(ex.Types, ex.LoaderExceptions, sb.ToString()); - } - } - - #endregion - - - - } } diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 7459ae848b..b135405615 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -27,7 +27,8 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - _typeLoader = new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()), false); + var typeFinder = new TypeFinder(Mock.Of()); + _typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()), false); // for testing, we'll specify which assemblies are scanned for the PluginTypeResolver // TODO: Should probably update this so it only searches this assembly and add custom types to be found diff --git a/src/Umbraco.Tests/CoreThings/UdiTests.cs b/src/Umbraco.Tests/CoreThings/UdiTests.cs index 2770803bea..a6b87ad57b 100644 --- a/src/Umbraco.Tests/CoreThings/UdiTests.cs +++ b/src/Umbraco.Tests/CoreThings/UdiTests.cs @@ -26,8 +26,9 @@ namespace Umbraco.Tests.CoreThings // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); + var typeFinder = new TypeFinder(Mock.Of()); container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); Current.Factory = container.Object; Udi.ResetUdiTypes(); diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 9db539d142..2f009d147e 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -144,39 +144,39 @@ namespace Umbraco.Tests.Published Assert.AreEqual(1, converter.SourceConverts); Assert.AreEqual(1, converter.InterConverts); - Assert.AreEqual(elementsCount1, elementsCache.Items.Count); - Assert.AreEqual(snapshotCount1, snapshotCache.Items.Count); + Assert.AreEqual(elementsCount1, elementsCache.Count); + Assert.AreEqual(snapshotCount1, snapshotCache.Count); Assert.AreEqual(1234, set1.Value("prop1")); Assert.AreEqual(1, converter.SourceConverts); Assert.AreEqual(interConverts, converter.InterConverts); - Assert.AreEqual(elementsCount2, elementsCache.Items.Count); - Assert.AreEqual(snapshotCount2, snapshotCache.Items.Count); + Assert.AreEqual(elementsCount2, elementsCache.Count); + Assert.AreEqual(snapshotCount2, snapshotCache.Count); var oldSnapshotCache = snapshotCache; - snapshotCache.Items.Clear(); + snapshotCache.Clear(); Assert.AreEqual(1234, set1.Value("prop1")); Assert.AreEqual(1, converter.SourceConverts); - Assert.AreEqual(elementsCount2, elementsCache.Items.Count); - Assert.AreEqual(snapshotCount2, snapshotCache.Items.Count); - Assert.AreEqual(snapshotCount2, oldSnapshotCache.Items.Count); + Assert.AreEqual(elementsCount2, elementsCache.Count); + Assert.AreEqual(snapshotCount2, snapshotCache.Count); + Assert.AreEqual(snapshotCount2, oldSnapshotCache.Count); - Assert.AreEqual((interConverts == 1 ? 1 : 3) + snapshotCache.Items.Count, converter.InterConverts); + Assert.AreEqual((interConverts == 1 ? 1 : 3) + snapshotCache.Count, converter.InterConverts); var oldElementsCache = elementsCache; - elementsCache.Items.Clear(); + elementsCache.Clear(); Assert.AreEqual(1234, set1.Value("prop1")); Assert.AreEqual(1, converter.SourceConverts); - Assert.AreEqual(elementsCount2, elementsCache.Items.Count); - Assert.AreEqual(elementsCount2, oldElementsCache.Items.Count); - Assert.AreEqual(snapshotCount2, snapshotCache.Items.Count); + Assert.AreEqual(elementsCount2, elementsCache.Count); + Assert.AreEqual(elementsCount2, oldElementsCache.Count); + Assert.AreEqual(snapshotCount2, snapshotCache.Count); - Assert.AreEqual((interConverts == 1 ? 1 : 4) + snapshotCache.Items.Count + elementsCache.Items.Count, converter.InterConverts); + Assert.AreEqual((interConverts == 1 ? 1 : 4) + snapshotCache.Count + elementsCache.Count, converter.InterConverts); } [Test] diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index 1f1500137f..a205478246 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -61,7 +61,8 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance())); - var typeLoader = new TypeLoader(appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); + var typeFinder = new TypeFinder(logger); + var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); var mainDom = new SimpleMainDom(); var runtimeState = new RuntimeState(logger, null, null, new Lazy(() => mainDom), new Lazy(() => factory.GetInstance())); @@ -249,7 +250,8 @@ namespace Umbraco.Tests.Runtimes var profilingLogger = new ProfilingLogger(logger, profiler); var appCaches = AppCaches.Disabled; var databaseFactory = Mock.Of(); - var typeLoader = new TypeLoader(appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); + var typeFinder = new TypeFinder(Mock.Of()); + var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); var runtimeState = Mock.Of(); Mock.Get(runtimeState).Setup(x => x.Level).Returns(RuntimeLevel.Run); var mainDom = Mock.Of(); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index f62effcb62..a2f08604b5 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -37,7 +37,8 @@ namespace Umbraco.Tests.TestHelpers var container = RegisterFactory.Create(); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); - var typeLoader = new TypeLoader(NoAppCache.Instance, + var typeFinder = new TypeFinder(Mock.Of()); + var typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), logger, false); diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs index e27fe17bbe..273f7a996e 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestControllerFactory.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Web.Mvc; using System.Web.Routing; using System.Web.SessionState; +using Moq; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Core.Composing; @@ -38,7 +39,8 @@ namespace Umbraco.Tests.TestHelpers.Stubs { if (_factory != null) return _factory(requestContext); - var types = TypeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); + var typeFinder = new TypeFinder(Mock.Of()); + var types = typeFinder.FindClassesOfType(new[] { Assembly.GetExecutingAssembly() }); var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase)); var t = controllerTypes.SingleOrDefault(); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index b9fd0f6640..4a911680ab 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -288,7 +288,8 @@ namespace Umbraco.Tests.Testing // common to all tests = cannot be overriden private static TypeLoader CreateCommonTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - return new TypeLoader(runtimeCache, globalSettings.LocalTempPath, logger, false) + var typeFinder = new TypeFinder(Mock.Of()); + return new TypeLoader(typeFinder, runtimeCache, globalSettings.LocalTempPath, logger, false) { AssembliesToScan = new[] { diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 3a5405548b..907c5efdd2 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -39,8 +39,9 @@ namespace Umbraco.Tests.Web // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var factory = new Mock(); + var typeFinder = new TypeFinder(Mock.Of()); factory.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); factory.Setup(x => x.GetInstance(typeof (ServiceContext))).Returns(serviceContext); var settings = SettingsForTests.GetDefaultUmbracoSettings(); diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index 26d85f60cf..c1147ed5a7 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -260,10 +260,11 @@ namespace Umbraco.Tests.Web // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - + var typeFinder = new TypeFinder(Mock.Of()); container .Setup(x => x.GetInstance(typeof(TypeLoader))) .Returns(new TypeLoader( + typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()) diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 7113abd6eb..aa8f735cf2 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -486,6 +486,7 @@ namespace Umbraco.Web.Editors /// /// /// + /// /// public PagedResult GetPagedChildren( string id, From 74d12a4d62493c3b252eaa1e3520e02f75aecd5d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 14:51:20 +1100 Subject: [PATCH 02/11] Moves more cache items to the abstractions project, decouples the HttpRequestAppCache from System.Web --- .../Cache/CacheRefresherCollectionBuilder.cs | 0 .../Cache/FastDictionaryAppCache.cs | 38 +++++------ .../Cache/HttpRequestAppCache.cs | 67 +++++++++---------- src/Umbraco.Core/Umbraco.Core.csproj | 2 - .../Cache/HttpRequestAppCacheTests.cs | 2 +- src/Umbraco.Web/Runtime/WebRuntime.cs | 2 +- 6 files changed, 51 insertions(+), 60 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Cache/CacheRefresherCollectionBuilder.cs (100%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Cache/HttpRequestAppCache.cs (61%) diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs b/src/Umbraco.Abstractions/Cache/CacheRefresherCollectionBuilder.cs similarity index 100% rename from src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs rename to src/Umbraco.Abstractions/Cache/CacheRefresherCollectionBuilder.cs diff --git a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs index 3bd29c3f5f..35ba4d08ca 100644 --- a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs @@ -15,21 +15,21 @@ namespace Umbraco.Core.Cache /// /// Gets the internal items dictionary, for tests only! /// - internal readonly ConcurrentDictionary> Items = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _items = new ConcurrentDictionary>(); - public int Count => Items.Count; + public int Count => _items.Count; /// public object Get(string cacheKey) { - Items.TryGetValue(cacheKey, out var result); // else null + _items.TryGetValue(cacheKey, out var result); // else null return result == null ? null : SafeLazy.GetSafeLazyValue(result); // return exceptions as null } /// public object Get(string cacheKey, Func getCacheItem) { - var result = Items.GetOrAdd(cacheKey, k => SafeLazy.GetSafeLazy(getCacheItem)); + var result = _items.GetOrAdd(cacheKey, k => SafeLazy.GetSafeLazy(getCacheItem)); var value = result.Value; // will not throw (safe lazy) if (!(value is SafeLazy.ExceptionHolder eh)) @@ -39,7 +39,7 @@ namespace Umbraco.Core.Cache // which would trick with GetSafeLazyValue, we need to remove by ourselves, // in order NOT to cache exceptions - Items.TryRemove(cacheKey, out result); + _items.TryRemove(cacheKey, out result); eh.Exception.Throw(); // throw once! return null; // never reached } @@ -47,7 +47,7 @@ namespace Umbraco.Core.Cache /// public IEnumerable SearchByKey(string keyStartsWith) { - return Items + return _items .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)) .Select(kvp => SafeLazy.GetSafeLazyValue(kvp.Value)) .Where(x => x != null); @@ -57,7 +57,7 @@ namespace Umbraco.Core.Cache public IEnumerable SearchByRegex(string regex) { var compiled = new Regex(regex, RegexOptions.Compiled); - return Items + return _items .Where(kvp => compiled.IsMatch(kvp.Key)) .Select(kvp => SafeLazy.GetSafeLazyValue(kvp.Value)) .Where(x => x != null); @@ -66,13 +66,13 @@ namespace Umbraco.Core.Cache /// public void Clear() { - Items.Clear(); + _items.Clear(); } /// public void Clear(string key) { - Items.TryRemove(key, out _); + _items.TryRemove(key, out _); } /// @@ -82,7 +82,7 @@ namespace Umbraco.Core.Cache if (type == null) return; var isInterface = type.IsInterface; - foreach (var kvp in Items + foreach (var kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -94,7 +94,7 @@ namespace Umbraco.Core.Cache // otherwise remove exact types (not inherited types) return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); })) - Items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } /// @@ -103,7 +103,7 @@ namespace Umbraco.Core.Cache var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; - foreach (var kvp in Items + foreach (var kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -116,7 +116,7 @@ namespace Umbraco.Core.Cache // otherwise remove exact types (not inherited types) return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); })) - Items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } /// @@ -125,7 +125,7 @@ namespace Umbraco.Core.Cache var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; - foreach (var kvp in Items + foreach (var kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -141,24 +141,24 @@ namespace Umbraco.Core.Cache // run predicate on the 'public key' part only, ie without prefix && predicate(x.Key, (T)value); })) - Items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } /// public void ClearByKey(string keyStartsWith) { - foreach (var ikvp in Items + foreach (var ikvp in _items .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith))) - Items.TryRemove(ikvp.Key, out _); + _items.TryRemove(ikvp.Key, out _); } /// public void ClearByRegex(string regex) { var compiled = new Regex(regex, RegexOptions.Compiled); - foreach (var ikvp in Items + foreach (var ikvp in _items .Where(kvp => compiled.IsMatch(kvp.Key))) - Items.TryRemove(ikvp.Key, out _); + _items.TryRemove(ikvp.Key, out _); } } } diff --git a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs similarity index 61% rename from src/Umbraco.Core/Cache/HttpRequestAppCache.cs rename to src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs index 018726538b..4fad382cc8 100644 --- a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs @@ -15,37 +15,29 @@ namespace Umbraco.Core.Cache /// or no Items...) then this cache acts as a pass-through and does not cache /// anything. /// - internal class HttpRequestAppCache : FastDictionaryAppCacheBase + public class HttpRequestAppCache : FastDictionaryAppCacheBase { - private readonly HttpContextBase _context; - /// /// Initializes a new instance of the class with a context, for unit tests! /// - public HttpRequestAppCache(HttpContextBase context) + public HttpRequestAppCache(Func requestItems) { - _context = context; + ContextItems = requestItems; } - /// - /// Initializes a new instance of the class. - /// - /// - /// Will use HttpContext.Current. - /// TODO: https://github.com/umbraco/Umbraco-CMS/issues/4239 - use IHttpContextAccessor NOT HttpContext.Current - /// - public HttpRequestAppCache() - { } + private Func ContextItems { get; } - private IDictionary ContextItems => _context?.Items ?? HttpContext.Current?.Items; - - private bool HasContextItems => _context?.Items != null || HttpContext.Current != null; + private bool TryGetContextItems(out IDictionary items) + { + items = ContextItems?.Invoke(); + return items != null; + } /// public override object Get(string key, Func factory) { //no place to cache so just return the callback result - if (HasContextItems == false) return factory(); + if (!TryGetContextItems(out var items)) return factory(); key = GetCacheKey(key); @@ -54,7 +46,7 @@ namespace Umbraco.Core.Cache try { EnterWriteLock(); - result = ContextItems[key] as Lazy; // null if key not found + result = items[key] as Lazy; // null if key not found // cannot create value within the lock, so if result.IsValueCreated is false, just // do nothing here - means that if creation throws, a race condition could cause @@ -63,7 +55,7 @@ namespace Umbraco.Core.Cache if (result == null || SafeLazy.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null { result = SafeLazy.GetSafeLazy(factory); - ContextItems[key] = result; + items[key] = result; } } finally @@ -89,22 +81,22 @@ namespace Umbraco.Core.Cache { const string prefix = CacheItemPrefix + "-"; - if (HasContextItems == false) return Enumerable.Empty(); + if (!TryGetContextItems(out var items)) return Enumerable.Empty(); - return ContextItems.Cast() + return items.Cast() .Where(x => x.Key is string s && s.StartsWith(prefix)); } protected override void RemoveEntry(string key) { - if (HasContextItems == false) return; + if (!TryGetContextItems(out var items)) return; - ContextItems.Remove(key); + items.Remove(key); } protected override object GetEntry(string key) { - return HasContextItems ? ContextItems[key] : null; + return !TryGetContextItems(out var items) ? null : items[key]; } #endregion @@ -117,26 +109,27 @@ namespace Umbraco.Core.Cache protected override void EnterWriteLock() { - if (HasContextItems) - { - // note: cannot keep 'entered' as a class variable here, - // since there is one per request - so storing it within - // ContextItems - which is locked, so this should be safe + if (!TryGetContextItems(out var items)) return; - var entered = false; - Monitor.Enter(ContextItems.SyncRoot, ref entered); - ContextItems[ContextItemsLockKey] = entered; - } + // note: cannot keep 'entered' as a class variable here, + // since there is one per request - so storing it within + // ContextItems - which is locked, so this should be safe + + var entered = false; + Monitor.Enter(items.SyncRoot, ref entered); + items[ContextItemsLockKey] = entered; } protected override void ExitReadLock() => ExitWriteLock(); protected override void ExitWriteLock() { - var entered = (bool?) ContextItems[ContextItemsLockKey] ?? false; + if (!TryGetContextItems(out var items)) return; + + var entered = (bool?)items[ContextItemsLockKey] ?? false; if (entered) - Monitor.Exit(ContextItems.SyncRoot); - ContextItems.Remove(ContextItemsLockKey); + Monitor.Exit(items.SyncRoot); + items.Remove(ContextItemsLockKey); } #endregion diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 87f20eb070..76460493c8 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -126,9 +126,7 @@ --> - - diff --git a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs index 042830e059..81a784fc01 100644 --- a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs @@ -14,7 +14,7 @@ namespace Umbraco.Tests.Cache { base.Setup(); _ctx = new FakeHttpContextFactory("http://localhost/test"); - _appCache = new HttpRequestAppCache(_ctx.HttpContext); + _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items); } internal override IAppCache AppCache diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 13cd717fd1..afbaa36cb4 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -63,7 +63,7 @@ namespace Umbraco.Web.Runtime // all entities are cached properly (cloned in and cloned out) new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache)), // we need request based cache when running in web-based context - new HttpRequestAppCache(), + new HttpRequestAppCache(() => HttpContext.Current?.Items), new IsolatedCaches(type => // we need to have the dep clone runtime cache provider to ensure // all entities are cached properly (cloned in and cloned out) From db216e43b7708d69345687a7c454195d9a67341b Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 15:00:15 +1100 Subject: [PATCH 03/11] Moves the WebCachingAppCache to the web project --- src/Umbraco.Core/Umbraco.Core.csproj | 1 - src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs | 1 + src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs | 1 + src/{Umbraco.Core => Umbraco.Web}/Cache/WebCachingAppCache.cs | 4 +++- src/Umbraco.Web/Runtime/WebRuntime.cs | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 6 files changed, 7 insertions(+), 2 deletions(-) rename src/{Umbraco.Core => Umbraco.Web}/Cache/WebCachingAppCache.cs (99%) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 76460493c8..bfffc037ce 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -127,7 +127,6 @@ - diff --git a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index 13fabe5ca2..c17d29a88f 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Collections; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Tests.Collections; +using Umbraco.Web.Cache; namespace Umbraco.Tests.Cache { diff --git a/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs b/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs index e732ae5766..84a946036a 100644 --- a/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Web; using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Web.Cache; namespace Umbraco.Tests.Cache { diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Web/Cache/WebCachingAppCache.cs similarity index 99% rename from src/Umbraco.Core/Cache/WebCachingAppCache.cs rename to src/Umbraco.Web/Cache/WebCachingAppCache.cs index 362df8f603..3d169699f0 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Web/Cache/WebCachingAppCache.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Web.Caching; +using Umbraco.Core; +using Umbraco.Core.Cache; -namespace Umbraco.Core.Cache +namespace Umbraco.Web.Cache { /// /// Implements on top of a . diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index afbaa36cb4..09446e1dab 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Runtime; +using Umbraco.Web.Cache; using Umbraco.Web.Logging; namespace Umbraco.Web.Runtime diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 317786b970..7097b0e6f5 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -131,6 +131,7 @@ + From c812e3aec92103f9a2f35386e2cbd3a7e541d0dc Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 15:31:38 +1100 Subject: [PATCH 04/11] fixing tests, reverting change to TypeHelper, just leaving it as is but with caching --- .../Composing/TypeHelper.cs | 56 +++---------------- 1 file changed, 8 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Abstractions/Composing/TypeHelper.cs b/src/Umbraco.Abstractions/Composing/TypeHelper.cs index 768709c44c..1752521b5d 100644 --- a/src/Umbraco.Abstractions/Composing/TypeHelper.cs +++ b/src/Umbraco.Abstractions/Composing/TypeHelper.cs @@ -19,6 +19,8 @@ namespace Umbraco.Core.Composing = new ConcurrentDictionary, PropertyInfo[]>(); private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary TypeNamesCache + = new ConcurrentDictionary(); private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; @@ -29,57 +31,15 @@ namespace Umbraco.Core.Composing /// public static Type GetTypeByName(string name) { + // First try using the basic functionality var type = Type.GetType(name); if (type != null) return type; - // now try fall back procedures. null may be returned because Type.GetName only returns types that have already been loaded in the appdomain - // and this type might not be there yet, so we need to parse the type name and then try to load in via assembly. - - var typeName = TypeName.Parse(name); - try - { - var assembly = Assembly.Load(typeName.AssemblyName); - type = assembly.GetType(typeName.Name); - return type; - } - catch (NotSupportedException) - { - throw; - } - catch (Exception) - { - return null; - } - } - - /// - /// Used to parse a fully qualified type name - /// - /// - /// Does not support generics. - /// This is a simple utility class and is not an exhaustive type name parser (which doesn't exist in .net strangely) - /// - private class TypeName - { - private TypeName(string name, AssemblyName assemblyName) - { - Name = name; - AssemblyName = assemblyName; - } - - public static TypeName Parse(string name) - { - if (name.Contains("[[")) - throw new NotSupportedException($"{nameof(TypeName)} does not support generic types"); - - var index = name.IndexOf(','); - return index > 0 - ? new TypeName(name.Substring(0, index).Trim(), new AssemblyName(name.Substring(index + 1).Trim())) - : new TypeName(name, null); - } - - public string Name { get; } - public AssemblyName AssemblyName { get; } + // It didn't parse, so try loading from each already loaded assembly and cache it + return TypeNamesCache.GetOrAdd(name, s => + AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.GetType(s)) + .FirstOrDefault(x => x != null)); } /// From d84963dac516fab3a9b21767b5570da012beb4b1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 16:07:47 +1100 Subject: [PATCH 05/11] Uses ITypeFinder abstraction where required --- .../Cache/FastDictionaryAppCache.cs | 9 +++- .../Cache/FastDictionaryAppCacheBase.cs | 9 +++- .../Cache/HttpRequestAppCache.cs | 3 +- .../Cache/ObjectCacheAppCache.cs | 6 ++- .../Composing/ITypeFinder.cs | 2 + .../Composing/TypeFinderExtensions.cs | 0 .../Composing/TypeHelper.cs | 22 +--------- src/Umbraco.Core/Composing/TypeFinder.cs | 42 +++++++++++++++++++ src/Umbraco.Core/Composing/TypeLoader.cs | 17 +++----- .../CompositionExtensions_Essentials.cs | 4 +- src/Umbraco.Core/Runtime/CoreRuntime.cs | 25 ++++++++--- src/Umbraco.Core/Scoping/Scope.cs | 14 ++++--- src/Umbraco.Core/Scoping/ScopeProvider.cs | 10 +++-- src/Umbraco.Core/Umbraco.Core.csproj | 2 - src/Umbraco.Examine/LuceneIndexCreator.cs | 9 +++- .../Cache/DeepCloneAppCacheTests.cs | 6 ++- .../Cache/HttpRequestAppCacheTests.cs | 8 +++- .../Cache/ObjectAppCacheTests.cs | 6 ++- .../Cache/WebCachingAppCacheTests.cs | 6 ++- .../Components/ComponentTests.cs | 3 +- src/Umbraco.Tests/Macros/MacroTests.cs | 9 ++-- src/Umbraco.Tests/Models/ContentTests.cs | 3 +- .../Repositories/DocumentRepositoryTest.cs | 4 +- .../Repositories/MediaRepositoryTest.cs | 4 +- .../Published/PropertyCacheLevelTests.cs | 6 ++- .../PublishedContent/NuCacheChildrenTests.cs | 5 ++- .../PublishedContent/NuCacheTests.cs | 5 ++- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 4 +- .../Scoping/ScopedNuCacheTests.cs | 5 ++- .../Scoping/ScopedRepositoryTests.cs | 4 +- .../ContentTypeServiceVariantsTests.cs | 6 ++- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 5 ++- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 5 ++- src/Umbraco.Web/Cache/WebCachingAppCache.cs | 3 +- .../NuCache/PublishedSnapshotService.cs | 8 +++- src/Umbraco.Web/Runtime/WebRuntime.cs | 6 +-- .../Search/UmbracoIndexesCreator.cs | 7 +++- 37 files changed, 200 insertions(+), 92 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Composing/ITypeFinder.cs (97%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Composing/TypeFinderExtensions.cs (100%) diff --git a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs index 35ba4d08ca..159f9cd7cb 100644 --- a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs @@ -12,6 +12,13 @@ namespace Umbraco.Core.Cache /// public class FastDictionaryAppCache : IAppCache { + private readonly ITypeFinder _typeFinder; + + public FastDictionaryAppCache(ITypeFinder typeFinder) + { + _typeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); + } + /// /// Gets the internal items dictionary, for tests only! /// @@ -78,7 +85,7 @@ namespace Umbraco.Core.Cache /// public void ClearOfType(string typeName) { - var type = TypeHelper.GetTypeByName(typeName); + var type = _typeFinder.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; diff --git a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs index b49ecea671..57f4e7b9a2 100644 --- a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs @@ -12,6 +12,13 @@ namespace Umbraco.Core.Cache /// public abstract class FastDictionaryAppCacheBase : IAppCache { + private readonly ITypeFinder _typeFinder; + + protected FastDictionaryAppCacheBase(ITypeFinder typeFinder) + { + _typeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); + } + // prefix cache keys so we know which one are ours protected const string CacheItemPrefix = "umbrtmche"; @@ -116,7 +123,7 @@ namespace Umbraco.Core.Cache /// public virtual void ClearOfType(string typeName) { - var type = TypeHelper.GetTypeByName(typeName); + var type = _typeFinder.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; try diff --git a/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs index 4fad382cc8..41c930ae86 100644 --- a/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Web; +using Umbraco.Core.Composing; namespace Umbraco.Core.Cache { @@ -20,7 +21,7 @@ namespace Umbraco.Core.Cache /// /// Initializes a new instance of the class with a context, for unit tests! /// - public HttpRequestAppCache(Func requestItems) + public HttpRequestAppCache(Func requestItems, ITypeFinder typeFinder) : base(typeFinder) { ContextItems = requestItems; } diff --git a/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs b/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs index 7d5081baa5..208390276a 100644 --- a/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/ObjectCacheAppCache.cs @@ -13,13 +13,15 @@ namespace Umbraco.Core.Cache /// public class ObjectCacheAppCache : IAppPolicyCache { + private readonly ITypeFinder _typeFinder; private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); /// /// Initializes a new instance of the . /// - public ObjectCacheAppCache() + public ObjectCacheAppCache(ITypeFinder typeFinder) { + _typeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); // the MemoryCache is created with name "in-memory". That name is // used to retrieve configuration options. It does not identify the memory cache, i.e. // each instance of this class has its own, independent, memory cache. @@ -178,7 +180,7 @@ namespace Umbraco.Core.Cache /// public virtual void ClearOfType(string typeName) { - var type = TypeHelper.GetTypeByName(typeName); + var type = _typeFinder.GetTypeByName(typeName); if (type == null) return; var isInterface = type.IsInterface; try diff --git a/src/Umbraco.Core/Composing/ITypeFinder.cs b/src/Umbraco.Abstractions/Composing/ITypeFinder.cs similarity index 97% rename from src/Umbraco.Core/Composing/ITypeFinder.cs rename to src/Umbraco.Abstractions/Composing/ITypeFinder.cs index 844719b782..d5908f9c54 100644 --- a/src/Umbraco.Core/Composing/ITypeFinder.cs +++ b/src/Umbraco.Abstractions/Composing/ITypeFinder.cs @@ -6,6 +6,8 @@ namespace Umbraco.Core.Composing { public interface ITypeFinder { + Type GetTypeByName(string name); + /// /// Return a list of found local Assemblies that Umbraco should scan for type finding /// diff --git a/src/Umbraco.Core/Composing/TypeFinderExtensions.cs b/src/Umbraco.Abstractions/Composing/TypeFinderExtensions.cs similarity index 100% rename from src/Umbraco.Core/Composing/TypeFinderExtensions.cs rename to src/Umbraco.Abstractions/Composing/TypeFinderExtensions.cs diff --git a/src/Umbraco.Abstractions/Composing/TypeHelper.cs b/src/Umbraco.Abstractions/Composing/TypeHelper.cs index 1752521b5d..28eab6a5ec 100644 --- a/src/Umbraco.Abstractions/Composing/TypeHelper.cs +++ b/src/Umbraco.Abstractions/Composing/TypeHelper.cs @@ -19,28 +19,10 @@ namespace Umbraco.Core.Composing = new ConcurrentDictionary, PropertyInfo[]>(); private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary TypeNamesCache - = new ConcurrentDictionary(); - + private static readonly Assembly[] EmptyAssemblies = new Assembly[0]; - /// - /// Returns a Type for the string type name - /// - /// - /// - public static Type GetTypeByName(string name) - { - // First try using the basic functionality - var type = Type.GetType(name); - if (type != null) return type; - - // It didn't parse, so try loading from each already loaded assembly and cache it - return TypeNamesCache.GetOrAdd(name, s => - AppDomain.CurrentDomain.GetAssemblies() - .Select(x => x.GetType(s)) - .FirstOrDefault(x => x != null)); - } + /// /// Based on a type we'll check if it is IEnumerable{T} (or similar) and if so we'll return a List{T}, this will also deal with array types and return List{T} for those too. diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index a1b1bf2fef..03fe115bbc 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; using System.IO; @@ -115,6 +116,7 @@ namespace Umbraco.Core.Composing private readonly object _localFilteredAssemblyCacheLocker = new object(); private readonly List _notifiedLoadExceptionAssemblies = new List(); private string[] _assembliesAcceptingLoadExceptions; + private static readonly ConcurrentDictionary TypeNamesCache= new ConcurrentDictionary(); private string[] AssembliesAcceptingLoadExceptions { @@ -310,8 +312,47 @@ namespace Umbraco.Core.Composing return GetClassesWithAttribute(attributeType, assemblyList, onlyConcreteClasses); } + /// + /// Returns a Type for the string type name + /// + /// + /// + public virtual Type GetTypeByName(string name) + { + // This is exactly what the BuildManager does, if the type is an assembly qualified type + // name it will find it. + if (TypeNameContainsAssembly(name)) + { + return Type.GetType(name); + } + + // It didn't parse, so try loading from each already loaded assembly and cache it + return TypeNamesCache.GetOrAdd(name, s => + AppDomain.CurrentDomain.GetAssemblies() + .Select(x => x.GetType(s)) + .FirstOrDefault(x => x != null)); + } + #region Private methods + // borrowed from aspnet System.Web.UI.Util + private static bool TypeNameContainsAssembly(string typeName) + { + return CommaIndexInTypeName(typeName) > 0; + } + + // borrowed from aspnet System.Web.UI.Util + private static int CommaIndexInTypeName(string typeName) + { + var num1 = typeName.LastIndexOf(','); + if (num1 < 0) + return -1; + var num2 = typeName.LastIndexOf(']'); + if (num2 > num1) + return -1; + return typeName.IndexOf(',', num2 + 1); + } + private IEnumerable GetClassesWithAttribute( Type attributeType, IEnumerable assemblies, @@ -510,5 +551,6 @@ namespace Umbraco.Core.Composing #endregion + } } diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index daa95ede49..1ee302aa6c 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -5,11 +5,9 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading; -using System.Web; using System.Web.Compilation; using Umbraco.Core.Cache; using Umbraco.Core.Collections; -using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using File = System.IO.File; @@ -729,18 +727,13 @@ namespace Umbraco.Core.Composing // successfully retrieved types from the file cache: load foreach (var type in cacheResult.Result) { - try - { - // we use the build manager to ensure we get all types loaded, this is slightly slower than - // Type.GetType but if the types in the assembly aren't loaded yet it would fail whereas - // BuildManager will load them - this is how eg MVC loads types, etc - no need to make it - // more complicated - typeList.Add(BuildManager.GetType(type, true)); - } - catch (Exception ex) + var resolvedType = TypeFinder.GetTypeByName(type); + if (resolvedType != null) + typeList.Add(resolvedType); + else { // in case of any exception, we have to exit, and revert to scanning - _logger.Error(ex, "Getting {TypeName}: failed to load cache file type {CacheType}, reverting to scanning assemblies.", GetName(baseType, attributeType), type); + _logger.Warn("Getting {TypeName}: failed to load cache file type {CacheType}, reverting to scanning assemblies.", GetName(baseType, attributeType), type); scan = true; break; } diff --git a/src/Umbraco.Core/CompositionExtensions_Essentials.cs b/src/Umbraco.Core/CompositionExtensions_Essentials.cs index b85479716c..e4be927f71 100644 --- a/src/Umbraco.Core/CompositionExtensions_Essentials.cs +++ b/src/Umbraco.Core/CompositionExtensions_Essentials.cs @@ -19,7 +19,8 @@ namespace Umbraco.Core AppCaches appCaches, IUmbracoDatabaseFactory databaseFactory, TypeLoader typeLoader, - IRuntimeState state) + IRuntimeState state, + ITypeFinder typeFinder) { composition.RegisterUnique(logger); composition.RegisterUnique(profiler); @@ -30,6 +31,7 @@ namespace Umbraco.Core composition.RegisterUnique(factory => factory.GetInstance().SqlContext); composition.RegisterUnique(typeLoader); composition.RegisterUnique(state); + composition.RegisterUnique(typeFinder); } } } diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 8ccb740cc2..192d449d12 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -42,6 +42,11 @@ namespace Umbraco.Core.Runtime /// protected IProfilingLogger ProfilingLogger { get; private set; } + /// + /// Gets the + /// + protected ITypeFinder TypeFinder { get; private set; } + /// public IRuntimeState State => _state; @@ -56,6 +61,9 @@ namespace Umbraco.Core.Runtime var profiler = Profiler = GetProfiler(); var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); + // type finder + TypeFinder = GetTypeFinder(); + // the boot loader boots using a container scope, so anything that is PerScope will // be disposed after the boot loader has booted, and anything else will remain. // note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else @@ -112,8 +120,7 @@ namespace Umbraco.Core.Runtime var configs = GetConfigs(); // type finder/loader - var typeFinder = new TypeFinder(Logger); - var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); + var typeLoader = new TypeLoader(TypeFinder, appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' @@ -131,7 +138,7 @@ namespace Umbraco.Core.Runtime // create the composition composition = new Composition(register, typeLoader, ProfilingLogger, _state, configs); - composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state); + composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, mainDom, appCaches, databaseFactory, typeLoader, _state, TypeFinder); // run handlers RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory); @@ -289,7 +296,6 @@ namespace Umbraco.Core.Runtime /// public virtual void Compose(Composition composition) { - // nothing } #region Getters @@ -314,6 +320,13 @@ namespace Umbraco.Core.Runtime protected virtual IProfiler GetProfiler() => new LogProfiler(Logger); + /// + /// Gets a + /// + /// + protected virtual ITypeFinder GetTypeFinder() + => new TypeFinder(Logger); + /// /// Gets the application caches. /// @@ -324,9 +337,9 @@ namespace Umbraco.Core.Runtime // is overridden by the web runtime return new AppCaches( - new DeepCloneAppCache(new ObjectCacheAppCache()), + new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)), NoAppCache.Instance, - new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)))); } // by default, returns null, meaning that Umbraco should auto-detect the application root path. diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index 84273e23da..3eabfbca9b 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -17,6 +17,7 @@ namespace Umbraco.Core.Scoping { private readonly ScopeProvider _scopeProvider; private readonly ILogger _logger; + private readonly ITypeFinder _typeFinder; private readonly IsolationLevel _isolationLevel; private readonly RepositoryCacheMode _repositoryCacheMode; @@ -35,7 +36,7 @@ namespace Umbraco.Core.Scoping // initializes a new scope private Scope(ScopeProvider scopeProvider, - ILogger logger, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable, + ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, @@ -45,6 +46,7 @@ namespace Umbraco.Core.Scoping { _scopeProvider = scopeProvider; _logger = logger; + _typeFinder = typeFinder; Context = scopeContext; @@ -109,26 +111,26 @@ namespace Umbraco.Core.Scoping // initializes a new scope public Scope(ScopeProvider scopeProvider, - ILogger logger, FileSystems fileSystems, bool detachable, ScopeContext scopeContext, + ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, bool detachable, ScopeContext scopeContext, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, logger, typeFinder, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } // initializes a new scope in a nested scopes chain, with its parent public Scope(ScopeProvider scopeProvider, - ILogger logger, FileSystems fileSystems, Scope parent, + ILogger logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, IsolationLevel isolationLevel = IsolationLevel.Unspecified, RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified, IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null, bool callContext = false, bool autoComplete = false) - : this(scopeProvider, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) + : this(scopeProvider, logger, typeFinder, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete) { } public Guid InstanceId { get; } = Guid.NewGuid(); @@ -175,7 +177,7 @@ namespace Umbraco.Core.Scoping if (ParentScope != null) return ParentScope.IsolatedCaches; return _isolatedCaches ?? (_isolatedCaches - = new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); + = new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache(_typeFinder)))); } } diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index 3c0fa94327..60cb83fcd5 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -22,13 +22,15 @@ namespace Umbraco.Core.Scoping internal class ScopeProvider : IScopeProvider, IScopeAccessor { private readonly ILogger _logger; + private readonly ITypeFinder _typeFinder; private readonly FileSystems _fileSystems; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, ILogger logger) + public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, ILogger logger, ITypeFinder typeFinder) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; _logger = logger; + _typeFinder = typeFinder; // take control of the FileSystems _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; @@ -323,7 +325,7 @@ namespace Umbraco.Core.Scoping IEventDispatcher eventDispatcher = null, bool? scopeFileSystems = null) { - return new Scope(this, _logger, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); + return new Scope(this, _logger, _typeFinder, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems); } /// @@ -379,13 +381,13 @@ namespace Umbraco.Core.Scoping { var ambientContext = AmbientContext; var newContext = ambientContext == null ? new ScopeContext() : null; - var scope = new Scope(this, _logger, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var scope = new Scope(this, _logger, _typeFinder, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); // assign only if scope creation did not throw! SetAmbient(scope, newContext ?? ambientContext); return scope; } - var nested = new Scope(this, _logger, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); + var nested = new Scope(this, _logger, _typeFinder, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete); SetAmbient(nested, AmbientContext); return nested; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index bfffc037ce..6643164020 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -136,9 +136,7 @@ - - diff --git a/src/Umbraco.Examine/LuceneIndexCreator.cs b/src/Umbraco.Examine/LuceneIndexCreator.cs index 97a929c9c8..47c4cce143 100644 --- a/src/Umbraco.Examine/LuceneIndexCreator.cs +++ b/src/Umbraco.Examine/LuceneIndexCreator.cs @@ -17,6 +17,13 @@ namespace Umbraco.Examine /// public abstract class LuceneIndexCreator : IIndexCreator { + private readonly ITypeFinder _typeFinder; + + protected LuceneIndexCreator(ITypeFinder typeFinder) + { + _typeFinder = typeFinder; + } + public abstract IEnumerable Create(); /// @@ -38,7 +45,7 @@ namespace Umbraco.Examine if (!configuredDirectoryFactory.IsNullOrWhiteSpace()) { //this should be a fully qualified type - var factoryType = TypeHelper.GetTypeByName(configuredDirectoryFactory); + var factoryType = _typeFinder.GetTypeByName(configuredDirectoryFactory); if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory); var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType); return directoryFactory.CreateDirectory(dirInfo); diff --git a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs index c17d29a88f..77200be86e 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneAppCacheTests.cs @@ -2,10 +2,13 @@ using System.Diagnostics; using System.Reflection; using System.Web; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Collections; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Tests.Collections; @@ -23,7 +26,8 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - _provider = new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache)); + var typeFinder = new TypeFinder(Mock.Of()); + _provider = new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache, typeFinder)); } internal override IAppCache AppCache => _provider; diff --git a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs index 81a784fc01..b9c948c1de 100644 --- a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs @@ -1,5 +1,8 @@ -using NUnit.Framework; +using Moq; +using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Cache @@ -13,8 +16,9 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); + var typeFinder = new TypeFinder(Mock.Of()); _ctx = new FakeHttpContextFactory("http://localhost/test"); - _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items); + _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items, typeFinder); } internal override IAppCache AppCache diff --git a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs index b9c729f891..0fb8e574a8 100644 --- a/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/ObjectAppCacheTests.cs @@ -1,9 +1,12 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; namespace Umbraco.Tests.Cache { @@ -20,7 +23,8 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - _provider = new ObjectCacheAppCache(); + var typeFinder = new TypeFinder(Mock.Of()); + _provider = new ObjectCacheAppCache(typeFinder); } internal override IAppCache AppCache diff --git a/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs b/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs index 84a946036a..02986e2f78 100644 --- a/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/WebCachingAppCacheTests.cs @@ -1,8 +1,11 @@ using System; using System.Diagnostics; using System.Web; +using Moq; using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Web.Cache; namespace Umbraco.Tests.Cache @@ -17,7 +20,8 @@ namespace Umbraco.Tests.Cache public override void Setup() { base.Setup(); - _appCache = new WebCachingAppCache(HttpRuntime.Cache); + var typeFinder = new TypeFinder(Mock.Of()); + _appCache = new WebCachingAppCache(HttpRuntime.Cache, typeFinder); } internal override IAppCache AppCache => _appCache; diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 2bd0d5d471..4fa25c2121 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -31,9 +31,10 @@ namespace Umbraco.Tests.Components var mock = new Mock(); var logger = Mock.Of(); + var typeFinder = new TypeFinder(logger); var f = new UmbracoDatabaseFactory(logger, new Lazy(() => new MapperCollection(Enumerable.Empty()))); var fs = new FileSystems(mock.Object, logger); - var p = new ScopeProvider(f, fs, logger); + var p = new ScopeProvider(f, fs, logger, typeFinder); mock.Setup(x => x.GetInstance(typeof (ILogger))).Returns(logger); mock.Setup(x => x.GetInstance(typeof (IProfilingLogger))).Returns(new ProfilingLogger(Mock.Of(), Mock.Of())); diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index 9cc1d14954..0c57c70742 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -1,7 +1,9 @@ -using NUnit.Framework; +using Moq; +using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Macros; @@ -15,11 +17,12 @@ namespace Umbraco.Tests.Macros [SetUp] public void Setup() { + var typeFinder = new TypeFinder(Mock.Of()); //we DO want cache enabled for these tests var cacheHelper = new AppCaches( - new ObjectCacheAppCache(), + new ObjectCacheAppCache(typeFinder), NoAppCache.Instance, - new IsolatedCaches(type => new ObjectCacheAppCache())); + new IsolatedCaches(type => new ObjectCacheAppCache(typeFinder))); //Current.ApplicationContext = new ApplicationContext(cacheHelper, new ProfilingLogger(Mock.Of(), Mock.Of())); Current.Reset(); diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 524d6fe5b1..ffbf462b8e 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using Umbraco.Core; using NUnit.Framework; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Composing.CompositionExtensions; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; @@ -269,7 +270,7 @@ namespace Umbraco.Tests.Models content.UpdateDate = DateTime.Now; content.WriterId = 23; - var runtimeCache = new ObjectCacheAppCache(); + var runtimeCache = new ObjectCacheAppCache(new TypeFinder(Mock.Of())); runtimeCache.Insert(content.Id.ToString(CultureInfo.InvariantCulture), () => content); var proflog = GetTestProfilingLogger(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 4d62ec8301..af27d47f97 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -77,9 +77,9 @@ namespace Umbraco.Tests.Persistence.Repositories public void CacheActiveForIntsAndGuids() { var realCache = new AppCaches( - new ObjectCacheAppCache(), + new ObjectCacheAppCache(TypeFinder), new DictionaryAppCache(), - new IsolatedCaches(t => new ObjectCacheAppCache())); + new IsolatedCaches(t => new ObjectCacheAppCache(TypeFinder))); var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index d04382e32e..fa916b554d 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -49,9 +49,9 @@ namespace Umbraco.Tests.Persistence.Repositories MediaTypeRepository mediaTypeRepository; var realCache = new AppCaches( - new ObjectCacheAppCache(), + new ObjectCacheAppCache(TypeFinder), new DictionaryAppCache(), - new IsolatedCaches(t => new ObjectCacheAppCache())); + new IsolatedCaches(t => new ObjectCacheAppCache(TypeFinder))); var provider = TestObjects.GetScopeProvider(Logger); using (var scope = provider.CreateScope()) diff --git a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs index 2f009d147e..fe2b080310 100644 --- a/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs +++ b/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs @@ -4,6 +4,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; @@ -124,8 +125,9 @@ namespace Umbraco.Tests.Published var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes); - var elementsCache = new FastDictionaryAppCache(); - var snapshotCache = new FastDictionaryAppCache(); + var typeFinder = new TypeFinder(Mock.Of()); + var elementsCache = new FastDictionaryAppCache(typeFinder); + var snapshotCache = new FastDictionaryAppCache(typeFinder); var publishedSnapshot = new Mock(); publishedSnapshot.Setup(x => x.SnapshotCache).Returns(snapshotCache); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index 5d32606ee7..564e129650 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -138,6 +138,8 @@ namespace Umbraco.Tests.PublishedContent // create a data source for NuCache _source = new TestDataSource(kits); + var typeFinder = new TypeFinder(Mock.Of()); + // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; _snapshotService = new PublishedSnapshotService(options, @@ -158,7 +160,8 @@ namespace Umbraco.Tests.PublishedContent globalSettings, Mock.Of(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + typeFinder); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index 0e05e6baad..d3a4a0d082 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -181,6 +181,8 @@ namespace Umbraco.Tests.PublishedContent // create a variation accessor _variationAccesor = new TestVariationContextAccessor(); + var typeFinder = new TypeFinder(Mock.Of()); + // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; _snapshotService = new PublishedSnapshotService(options, @@ -201,7 +203,8 @@ namespace Umbraco.Tests.PublishedContent globalSettings, Mock.Of(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + typeFinder); // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index a205478246..d1f2f48ed7 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -69,7 +69,7 @@ namespace Umbraco.Tests.Runtimes // create the register and the composition var register = RegisterFactory.Create(); var composition = new Composition(register, typeLoader, profilingLogger, runtimeState); - composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState); + composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder); // create the core runtime and have it compose itself var coreRuntime = new CoreRuntime(); @@ -260,7 +260,7 @@ namespace Umbraco.Tests.Runtimes // create the register and the composition var register = RegisterFactory.Create(); var composition = new Composition(register, typeLoader, profilingLogger, runtimeState); - composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState); + composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder); // create the core runtime and have it compose itself var coreRuntime = new CoreRuntime(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index c7c403b260..0d2243c6dc 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -82,6 +82,8 @@ namespace Umbraco.Tests.Scoping var mediaRepository = Mock.Of(); var memberRepository = Mock.Of(); + var typeFinder = new TypeFinder(Mock.Of()); + return new PublishedSnapshotService( options, null, @@ -99,7 +101,8 @@ namespace Umbraco.Tests.Scoping Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + typeFinder); } protected UmbracoContext GetUmbracoContextNu(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable urlProviders = null) diff --git a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs index c7e4ddcb19..ae4544bf87 100644 --- a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs @@ -43,9 +43,9 @@ namespace Umbraco.Tests.Scoping { // this is what's created core web runtime return new AppCaches( - new DeepCloneAppCache(new ObjectCacheAppCache()), + new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)), NoAppCache.Instance, - new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); + new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)))); } [TearDown] diff --git a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs index 0fe05f385f..bc515cfea3 100644 --- a/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs +++ b/src/Umbraco.Tests/Services/ContentTypeServiceVariantsTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Persistence; @@ -53,6 +54,8 @@ namespace Umbraco.Tests.Services var mediaRepository = Mock.Of(); var memberRepository = Mock.Of(); + var typeFinder = new TypeFinder(Mock.Of()); + return new PublishedSnapshotService( options, null, @@ -70,7 +73,8 @@ namespace Umbraco.Tests.Services Factory.GetInstance(), Factory.GetInstance(), Mock.Of(), - new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() })); + new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), + typeFinder); } public class LocalServerMessenger : ServerMessengerBase diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 56ad22d414..4464a98511 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -230,7 +230,7 @@ namespace Umbraco.Tests.TestHelpers return container?.TryGetInstance() ?? Mock.Of(); } - public IScopeProvider GetScopeProvider(ILogger logger, FileSystems fileSystems = null, IUmbracoDatabaseFactory databaseFactory = null) + public IScopeProvider GetScopeProvider(ILogger logger, ITypeFinder typeFinder = null, FileSystems fileSystems = null, IUmbracoDatabaseFactory databaseFactory = null) { if (databaseFactory == null) { @@ -241,8 +241,9 @@ namespace Umbraco.Tests.TestHelpers databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, logger, new Lazy(() => mappers)); } + typeFinder = typeFinder ?? new TypeFinder(logger); fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger); - var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger); + var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger, typeFinder); return scopeProvider; } } diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 4a911680ab..38147fe45d 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -99,6 +99,8 @@ namespace Umbraco.Tests.Testing protected ILogger Logger => Factory.GetInstance(); + protected ITypeFinder TypeFinder => Factory.GetInstance(); + protected IProfiler Profiler => Factory.GetInstance(); protected virtual IProfilingLogger ProfilingLogger => Factory.GetInstance(); @@ -137,6 +139,7 @@ namespace Umbraco.Tests.Testing Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + Composition.RegisterUnique(typeLoader.TypeFinder); Composition.RegisterUnique(typeLoader); Composition.RegisterUnique(logger); Composition.RegisterUnique(profiler); @@ -358,7 +361,7 @@ namespace Umbraco.Tests.Testing Composition.WithCollectionBuilder(); // empty Composition.RegisterUnique(factory - => TestObjects.GetScopeProvider(factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance())); + => TestObjects.GetScopeProvider(factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance(), factory.TryGetInstance())); Composition.RegisterUnique(factory => (IScopeAccessor) factory.GetInstance()); Composition.ComposeServices(); diff --git a/src/Umbraco.Web/Cache/WebCachingAppCache.cs b/src/Umbraco.Web/Cache/WebCachingAppCache.cs index 3d169699f0..1879e8b69b 100644 --- a/src/Umbraco.Web/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Web/Cache/WebCachingAppCache.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Web.Caching; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; namespace Umbraco.Web.Cache { @@ -25,7 +26,7 @@ namespace Umbraco.Web.Cache /// /// Initializes a new instance of the class. /// - public WebCachingAppCache(System.Web.Caching.Cache cache) + public WebCachingAppCache(System.Web.Caching.Cache cache, ITypeFinder typeFinder) : base(typeFinder) { _cache = cache; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index eae2f33979..554e76c665 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -8,6 +8,7 @@ using CSharpTest.Net.Collections; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -46,6 +47,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private readonly IPublishedModelFactory _publishedModelFactory; private readonly IDefaultCultureAccessor _defaultCultureAccessor; private readonly UrlSegmentProviderCollection _urlSegmentProviders; + private readonly ITypeFinder _typeFinder; // volatile because we read it with no lock private volatile bool _isReady; @@ -77,7 +79,8 @@ namespace Umbraco.Web.PublishedCache.NuCache IDataSource dataSource, IGlobalSettings globalSettings, IEntityXmlSerializer entitySerializer, IPublishedModelFactory publishedModelFactory, - UrlSegmentProviderCollection urlSegmentProviders) + UrlSegmentProviderCollection urlSegmentProviders, + ITypeFinder typeFinder) : base(publishedSnapshotAccessor, variationContextAccessor) { //if (Interlocked.Increment(ref _singletonCheck) > 1) @@ -94,6 +97,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _defaultCultureAccessor = defaultCultureAccessor; _globalSettings = globalSettings; _urlSegmentProviders = urlSegmentProviders; + _typeFinder = typeFinder; // we need an Xml serializer here so that the member cache can support XPath, // for members this is done by navigating the serialized-to-xml member @@ -1162,7 +1166,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _contentGen = contentSnap.Gen; _mediaGen = mediaSnap.Gen; _domainGen = domainSnap.Gen; - elementsCache = _elementsCache = new FastDictionaryAppCache(); + elementsCache = _elementsCache = new FastDictionaryAppCache(_typeFinder); } } diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 09446e1dab..49db6646de 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -62,13 +62,13 @@ namespace Umbraco.Web.Runtime 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) - new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache)), + new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache, TypeFinder)), // we need request based cache when running in web-based context - new HttpRequestAppCache(() => HttpContext.Current?.Items), + new HttpRequestAppCache(() => HttpContext.Current?.Items, TypeFinder), new IsolatedCaches(type => // we need to have the dep clone runtime cache provider to ensure // all entities are cached properly (cloned in and cloned out) - new DeepCloneAppCache(new ObjectCacheAppCache()))); + new DeepCloneAppCache(new ObjectCacheAppCache(TypeFinder)))); #endregion } diff --git a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs index ec536b9d75..b9dbc7d996 100644 --- a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs +++ b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs @@ -7,6 +7,7 @@ using Lucene.Net.Analysis.Standard; using Examine.LuceneEngine; using Examine; using Umbraco.Core; +using Umbraco.Core.Composing; namespace Umbraco.Web.Search { @@ -17,10 +18,12 @@ namespace Umbraco.Web.Search { // TODO: we should inject the different IValueSetValidator so devs can just register them instead of overriding this class? - public UmbracoIndexesCreator(IProfilingLogger profilingLogger, + public UmbracoIndexesCreator( + ITypeFinder typeFinder, + IProfilingLogger profilingLogger, ILocalizationService languageService, IPublicAccessService publicAccessService, - IMemberService memberService, IUmbracoIndexConfig umbracoIndexConfig) + IMemberService memberService, IUmbracoIndexConfig umbracoIndexConfig) : base(typeFinder) { ProfilingLogger = profilingLogger ?? throw new System.ArgumentNullException(nameof(profilingLogger)); LanguageService = languageService ?? throw new System.ArgumentNullException(nameof(languageService)); From a8face20239ada6c69891c0c22f33cf0d03bbf47 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 17:30:50 +1100 Subject: [PATCH 06/11] Moving TypeHelper ... but now need IOHelper... --- .../Composing/ITypeFinder.cs | 3 + .../Composing/TypeFinder.cs | 117 +++++++----------- .../Composing/TypeLoader.cs | 19 ++- .../UmbracoSettings/ITypeFinderConfig.cs | 11 ++ src/Umbraco.Core/Runtime/CoreRuntime.cs | 7 ++ src/Umbraco.Core/Umbraco.Core.csproj | 2 - .../Components/ComponentTests.cs | 2 +- .../Composing/ComposingTestBase.cs | 5 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 13 +- .../Composing/BuildManagerTypeFinder.cs | 105 ++++++++++++++++ src/Umbraco.Web/Runtime/WebRuntime.cs | 4 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 12 files changed, 189 insertions(+), 100 deletions(-) rename src/{Umbraco.Core => Umbraco.Abstractions}/Composing/TypeFinder.cs (82%) rename src/{Umbraco.Core => Umbraco.Abstractions}/Composing/TypeLoader.cs (98%) create mode 100644 src/Umbraco.Abstractions/Configuration/UmbracoSettings/ITypeFinderConfig.cs create mode 100644 src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs diff --git a/src/Umbraco.Abstractions/Composing/ITypeFinder.cs b/src/Umbraco.Abstractions/Composing/ITypeFinder.cs index d5908f9c54..f302976dd6 100644 --- a/src/Umbraco.Abstractions/Composing/ITypeFinder.cs +++ b/src/Umbraco.Abstractions/Composing/ITypeFinder.cs @@ -4,6 +4,9 @@ using System.Reflection; namespace Umbraco.Core.Composing { + /// + /// Used to find objects by implemented types, names and/or attributes + /// public interface ITypeFinder { Type GetTypeByName(string name); diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Abstractions/Composing/TypeFinder.cs similarity index 82% rename from src/Umbraco.Core/Composing/TypeFinder.cs rename to src/Umbraco.Abstractions/Composing/TypeFinder.cs index 03fe115bbc..9d88153b0a 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Abstractions/Composing/TypeFinder.cs @@ -7,70 +7,51 @@ using System.Linq; using System.Reflection; using System.Security; using System.Text; -using System.Web.Compilation; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Exceptions; using Umbraco.Core.IO; using Umbraco.Core.Logging; namespace Umbraco.Core.Composing { - /// - /// A utility class to find all classes of a certain type by reflection in the current bin folder - /// of the web application. - /// + /// public class TypeFinder : ITypeFinder { private readonly ILogger _logger; - public TypeFinder(ILogger logger) + public TypeFinder(ILogger logger, 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 { - var isHosted = IOHelper.IsHosted; - - 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) { - if (isHosted) + try { - assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + var assName = AssemblyName.GetAssemblyName(a); + var ass = Assembly.Load(assName); + assemblies.Add(ass); } - } - catch (InvalidOperationException e) - { - if (e.InnerException is SecurityException == false) - throw; - } - - if (assemblies == null) - { - //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 = IOHelper.GetRootDirectoryBinFolder(); - 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) + catch (Exception e) { - try + if (e is SecurityException || e is BadImageFormatException) { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); + //swallow these exceptions } - catch (Exception e) + else { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } + throw; } } } @@ -80,25 +61,6 @@ namespace Umbraco.Core.Composing { assemblies.Add(a); } - - //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"); - } - } } catch (InvalidOperationException e) { @@ -115,31 +77,40 @@ namespace Umbraco.Core.Composing private volatile HashSet _localFilteredAssemblyCache; private readonly object _localFilteredAssemblyCacheLocker = new object(); private readonly List _notifiedLoadExceptionAssemblies = new List(); - private string[] _assembliesAcceptingLoadExceptions; private static readonly ConcurrentDictionary TypeNamesCache= new ConcurrentDictionary(); + private string _rootDir = ""; + private readonly string[] _assembliesAcceptingLoadExceptions; - private string[] AssembliesAcceptingLoadExceptions + // FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here + private string GetRootDirectorySafe() { - get + if (string.IsNullOrEmpty(_rootDir) == false) { - if (_assembliesAcceptingLoadExceptions != null) - return _assembliesAcceptingLoadExceptions; - - var s = ConfigurationManager.AppSettings[Constants.AppSettings.AssembliesAcceptingLoadExceptions]; - return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) - ? Array.Empty() - : s.Split(',').Select(x => x.Trim()).ToArray(); + 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) + if (_assembliesAcceptingLoadExceptions.Length == 0) return false; - if (AssembliesAcceptingLoadExceptions.Length == 1 && AssembliesAcceptingLoadExceptions[0] == "*") + if (_assembliesAcceptingLoadExceptions.Length == 1 && _assembliesAcceptingLoadExceptions[0] == "*") return true; var name = a.GetName().Name; // simple name of the assembly - return AssembliesAcceptingLoadExceptions.Any(pattern => + return _assembliesAcceptingLoadExceptions.Any(pattern => { if (pattern.Length > name.Length) return false; // pattern longer than name if (pattern.Length == name.Length) return pattern.InvariantEquals(name); // same length, must be identical diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Abstractions/Composing/TypeLoader.cs similarity index 98% rename from src/Umbraco.Core/Composing/TypeLoader.cs rename to src/Umbraco.Abstractions/Composing/TypeLoader.cs index 1ee302aa6c..e2ef86a45c 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Abstractions/Composing/TypeLoader.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading; -using System.Web.Compilation; using Umbraco.Core.Cache; using Umbraco.Core.Collections; using Umbraco.Core.IO; @@ -50,8 +49,9 @@ namespace Umbraco.Core.Composing /// The application runtime cache. /// Files storage location. /// A profiling logger. - public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger) - : this(typeFinder, runtimeCache, localTempPath, logger, true) + /// + public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, IEnumerable assembliesToScan = null) + : this(typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan) { } /// @@ -62,12 +62,14 @@ namespace Umbraco.Core.Composing /// Files storage location. /// A profiling logger. /// Whether to detect changes using hashes. - internal TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges) + /// + public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable assembliesToScan = null) { TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); _localTempPath = localTempPath; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _assemblies = assembliesToScan; if (detectChanges) { @@ -99,13 +101,6 @@ namespace Umbraco.Core.Composing } } - /// - /// Initializes a new, test/blank, instance of the class. - /// - /// The initialized instance cannot get types. - internal TypeLoader() - { } - /// /// Returns the underlying /// @@ -122,7 +117,7 @@ namespace Umbraco.Core.Composing /// This is for unit tests. /// // internal for tests - internal IEnumerable AssembliesToScan + protected IEnumerable AssembliesToScan { get => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan); set => _assemblies = value; diff --git a/src/Umbraco.Abstractions/Configuration/UmbracoSettings/ITypeFinderConfig.cs b/src/Umbraco.Abstractions/Configuration/UmbracoSettings/ITypeFinderConfig.cs new file mode 100644 index 0000000000..fd5b18ed39 --- /dev/null +++ b/src/Umbraco.Abstractions/Configuration/UmbracoSettings/ITypeFinderConfig.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Umbraco.Core.Configuration.UmbracoSettings +{ + public interface ITypeFinderConfig + { + IEnumerable AssembliesAcceptingLoadExceptions { get; } + } +} diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 192d449d12..431627cfeb 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -58,11 +58,18 @@ namespace Umbraco.Core.Runtime // loggers var logger = Logger = GetLogger(); + if (logger == null) + throw new InvalidOperationException($"The object returned from {nameof(GetLogger)} cannot be null"); var profiler = Profiler = GetProfiler(); + if (profiler == null) + throw new InvalidOperationException($"The object returned from {nameof(GetProfiler)} cannot be null"); + var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); // type finder TypeFinder = GetTypeFinder(); + if (TypeFinder == null) + throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null"); // the boot loader boots using a container scope, so anything that is PerScope will // be disposed after the boot loader has booted, and anything else will remain. diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 6643164020..f3ab82d731 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -177,8 +177,6 @@ - - diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 4fa25c2121..ca481352a7 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -52,7 +52,7 @@ namespace Umbraco.Tests.Components private static TypeLoader MockTypeLoader() { - return new TypeLoader(); + return new TypeLoader(Mock.Of(), Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of()); } public static IRuntimeState MockRuntimeState(RuntimeLevel level) diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index 4b5be30cd9..743c71b2f3 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -23,10 +23,7 @@ namespace Umbraco.Tests.Composing ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); var typeFinder = new TypeFinder(Mock.Of()); - TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, detectChanges: false) - { - AssembliesToScan = AssembliesToScan - }; + TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, false, AssembliesToScan); } [TearDown] diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 38147fe45d..0e1cd808a7 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -292,15 +292,12 @@ namespace Umbraco.Tests.Testing private static TypeLoader CreateCommonTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { var typeFinder = new TypeFinder(Mock.Of()); - return new TypeLoader(typeFinder, runtimeCache, globalSettings.LocalTempPath, logger, false) + return new TypeLoader(typeFinder, runtimeCache, globalSettings.LocalTempPath, logger, false, new[] { - AssembliesToScan = new[] - { - Assembly.Load("Umbraco.Core"), - Assembly.Load("Umbraco.Web"), - Assembly.Load("Umbraco.Tests") - } - }; + Assembly.Load("Umbraco.Core"), + Assembly.Load("Umbraco.Web"), + Assembly.Load("Umbraco.Tests") + }); } protected virtual void ComposeDatabase(UmbracoTestOptions.Database option) diff --git a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs new file mode 100644 index 0000000000..68c8308a9e --- /dev/null +++ b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using System.Web.Compilation; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration.UmbracoSettings; +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 + /// + /// + /// This finder will also try to resolve dynamic assemblies created from App_Code + /// + internal class BuildManagerTypeFinder : TypeFinder, ITypeFinder + { + + public BuildManagerTypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig) + { + _allAssemblies = new Lazy>(() => + { + var isHosted = IOHelper.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"); + } + } + } + } + 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 + /// + /// + /// + 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 + /// + internal class TypeFinderConfig : ITypeFinderConfig + { + private IEnumerable _assembliesAcceptingLoadExceptions; + public IEnumerable AssembliesAcceptingLoadExceptions + { + get + { + if (_assembliesAcceptingLoadExceptions != null) + return _assembliesAcceptingLoadExceptions; + + var s = ConfigurationManager.AppSettings[Constants.AppSettings.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 49db6646de..b64968dba4 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -5,6 +5,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Runtime; using Umbraco.Web.Cache; +using Umbraco.Web.Composing; using Umbraco.Web.Logging; namespace Umbraco.Web.Runtime @@ -17,6 +18,7 @@ namespace Umbraco.Web.Runtime { private readonly UmbracoApplicationBase _umbracoApplication; private IProfiler _webProfiler; + private BuildManagerTypeFinder _typeFinder; /// /// Initializes a new instance of the class. @@ -57,6 +59,8 @@ namespace Umbraco.Web.Runtime #region Getters + protected override ITypeFinder GetTypeFinder() => _typeFinder ??= new BuildManagerTypeFinder(Logger, new BuildManagerTypeFinder.TypeFinderConfig()); + protected override IProfiler GetProfiler() => _webProfiler; protected override AppCaches GetAppCaches() => new AppCaches( diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7097b0e6f5..79b0fe8744 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -135,6 +135,7 @@ + From f154d9c333b4127b67d37979902526636993f5c6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 18:56:14 +1100 Subject: [PATCH 07/11] Moves TypeLoader to abstractions --- .../Composing/TypeLoader.cs | 52 ++++++++++-------- src/Umbraco.Core/IO/IOHelper.cs | 2 + src/Umbraco.Core/Runtime/CoreRuntime.cs | 20 ++++++- .../DistributedCache/DistributedCacheTests.cs | 3 +- .../Components/ComponentTests.cs | 7 ++- .../Composing/CollectionBuildersTests.cs | 3 +- .../Composing/ComposingTestBase.cs | 4 +- .../Composing/CompositionTests.cs | 4 +- .../Composing/LazyCollectionBuilderTests.cs | 11 ++-- .../Composing/PackageActionCollectionTests.cs | 3 +- .../Composing/TypeLoaderTests.cs | 53 ++++++++++--------- src/Umbraco.Tests/CoreThings/UdiTests.cs | 5 +- src/Umbraco.Tests/IO/FileSystemsTests.cs | 2 +- .../PropertyEditors/ImageCropperTest.cs | 2 +- .../PropertyEditorValueEditorTests.cs | 2 +- .../Published/ConvertersTests.cs | 2 +- .../PublishedContentSnapshotTestBase.cs | 18 ++++--- .../PublishedContent/PublishedContentTests.cs | 18 ++++--- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 6 ++- .../Scoping/ScopeEventDispatcherTests.cs | 2 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 6 ++- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 10 +++- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 25 +++++---- .../Web/TemplateUtilitiesTests.cs | 4 +- src/Umbraco.Tests/Web/UmbracoHelperTests.cs | 6 ++- .../Composing/BuildManagerTypeFinder.cs | 6 +-- src/Umbraco.Web/Runtime/WebRuntime.cs | 2 +- 27 files changed, 171 insertions(+), 107 deletions(-) diff --git a/src/Umbraco.Abstractions/Composing/TypeLoader.cs b/src/Umbraco.Abstractions/Composing/TypeLoader.cs index 2573e5c467..d2f203e112 100644 --- a/src/Umbraco.Abstractions/Composing/TypeLoader.cs +++ b/src/Umbraco.Abstractions/Composing/TypeLoader.cs @@ -13,19 +13,22 @@ using File = System.IO.File; namespace Umbraco.Core.Composing { + + /// /// Provides methods to find and instantiate types. /// /// - /// This class should be used to get all types, the class should never be used directly. + /// This class should be used to get all types, the class should never be used directly. /// In most cases this class is not used directly but through extension methods that retrieve specific types. /// This class caches the types it knows to avoid excessive assembly scanning and shorten startup times, relying /// on a hash of the DLLs in the ~/bin folder to check for cache expiration. /// - public class TypeLoader + public sealed class TypeLoader { private const string CacheKey = "umbraco-types.list"; + private readonly IIOHelper _ioHelper; private readonly IAppPolicyCache _runtimeCache; private readonly IProfilingLogger _logger; @@ -39,33 +42,36 @@ namespace Umbraco.Core.Composing private string _currentAssembliesHash; private IEnumerable _assemblies; private bool _reportedChange; - private readonly string _localTempPath; + private readonly DirectoryInfo _localTempPath; private string _fileBasePath; /// /// Initializes a new instance of the class. /// + /// /// /// The application runtime cache. /// Files storage location. /// A profiling logger. /// - public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, IEnumerable assembliesToScan = null) - : this(typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan) + public TypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, IEnumerable assembliesToScan = null) + : this(ioHelper, typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan) { } /// /// Initializes a new instance of the class. /// + /// /// /// The application runtime cache. /// Files storage location. /// A profiling logger. /// Whether to detect changes using hashes. /// - public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable assembliesToScan = null) + public TypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, DirectoryInfo localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable assembliesToScan = null) { TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder)); + _ioHelper = ioHelper; _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); _localTempPath = localTempPath; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -117,25 +123,21 @@ namespace Umbraco.Core.Composing /// This is for unit tests. /// // internal for tests - protected IEnumerable AssembliesToScan - { - get => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan); - set => _assemblies = value; - } + public IEnumerable AssembliesToScan => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan); /// /// Gets the type lists. /// /// For unit tests. // internal for tests - internal IEnumerable TypeLists => _types.Values; + public IEnumerable TypeLists => _types.Values; /// /// Sets a type list. /// /// For unit tests. // internal for tests - internal void AddTypeList(TypeList typeList) + public void AddTypeList(TypeList typeList) { var tobject = typeof(object); // CompositeTypeTypeKey does not support null values _types[new CompositeTypeTypeKey(typeList.BaseType ?? tobject, typeList.AttributeType ?? tobject)] = typeList; @@ -182,12 +184,16 @@ namespace Umbraco.Core.Composing _currentAssembliesHash = GetFileHash(new List> { + // TODO: Would be nicer to abstract this logic out into IAssemblyHash + + // TODO: Use constants from SystemDirectories when we can (once it's ported to netstandard lib) + // the bin folder and everything in it - new Tuple(new DirectoryInfo(Current.IOHelper.MapPath(SystemDirectories.Bin)), false), + new Tuple(new DirectoryInfo(_ioHelper.MapPath("~/bin")), false), // the app code folder and everything in it - new Tuple(new DirectoryInfo(Current.IOHelper.MapPath("~/App_Code")), false), + new Tuple(new DirectoryInfo(_ioHelper.MapPath("~/App_Code")), false), // global.asax (the app domain also monitors this, if it changes will do a full restart) - new Tuple(new FileInfo(Current.IOHelper.MapPath("~/global.asax")), false) + new Tuple(new FileInfo(_ioHelper.MapPath("~/global.asax")), false) }, _logger); return _currentAssembliesHash; @@ -269,7 +275,7 @@ namespace Umbraco.Core.Composing /// A profiling logger. /// The hash. // internal for tests - internal static string GetFileHash(IEnumerable filesAndFolders, IProfilingLogger logger) + public static string GetFileHash(IEnumerable filesAndFolders, IProfilingLogger logger) { using (logger.DebugDuration("Determining hash of code files on disk", "Hash determined")) { @@ -300,7 +306,7 @@ namespace Umbraco.Core.Composing private const int FileDeleteTimeout = 4000; // milliseconds // internal for tests - internal Attempt> TryGetCached(Type baseType, Type attributeType) + public Attempt> TryGetCached(Type baseType, Type attributeType) { var cache = _runtimeCache.GetCacheItem, IEnumerable>>(CacheKey, ReadCacheSafe, TimeSpan.FromSeconds(ListFileCacheDuration)); @@ -333,7 +339,7 @@ namespace Umbraco.Core.Composing } // internal for tests - internal Dictionary, IEnumerable> ReadCache() + public Dictionary, IEnumerable> ReadCache() { var cache = new Dictionary, IEnumerable>(); @@ -379,7 +385,7 @@ namespace Umbraco.Core.Composing } // internal for tests - internal string GetTypesListFilePath() => GetFileBasePath() + ".list"; + public string GetTypesListFilePath() => GetFileBasePath() + ".list"; private string GetTypesHashFilePath() => GetFileBasePath() + ".hash"; @@ -390,7 +396,7 @@ namespace Umbraco.Core.Composing if (_fileBasePath != null) return _fileBasePath; - _fileBasePath = Path.Combine(_localTempPath, "TypesCache", "umbraco-types." + NetworkHelper.FileSafeMachineName); + _fileBasePath = Path.Combine(_localTempPath.FullName, "TypesCache", "umbraco-types." + NetworkHelper.FileSafeMachineName); // ensure that the folder exists var directory = Path.GetDirectoryName(_fileBasePath); @@ -404,7 +410,7 @@ namespace Umbraco.Core.Composing } // internal for tests - internal void WriteCache() + public void WriteCache() { _logger.Debug("Writing cache file."); var typesListFilePath = GetTypesListFilePath(); @@ -780,7 +786,7 @@ namespace Umbraco.Core.Composing /// Represents a list of types obtained by looking for types inheriting/implementing a /// specified type, and/or marked with a specified attribute type. /// - internal class TypeList + public sealed class TypeList { private readonly HashSet _types = new HashSet(); diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index a81c0ab559..36215b267c 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -13,6 +13,8 @@ namespace Umbraco.Core.IO { public class IOHelper : IIOHelper { + internal static IIOHelper Default { get; } = new IOHelper(); + /// /// Gets or sets a value forcing Umbraco to consider it is non-hosted. /// diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index ad644cd56b..42c39bfabf 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Web; using System.Web.Hosting; using Umbraco.Core.Cache; @@ -47,6 +48,11 @@ namespace Umbraco.Core.Runtime /// protected ITypeFinder TypeFinder { get; private set; } + /// + /// Gets the + /// + protected IIOHelper IOHelper { get; private set; } + /// public IRuntimeState State => _state; @@ -66,11 +72,14 @@ namespace Umbraco.Core.Runtime var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); - // type finder TypeFinder = GetTypeFinder(); if (TypeFinder == null) throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null"); + IOHelper = GetIOHelper(); + if (IOHelper == null) + throw new InvalidOperationException($"The object returned from {nameof(GetIOHelper)} cannot be null"); + // the boot loader boots using a container scope, so anything that is PerScope will // be disposed after the boot loader has booted, and anything else will remain. // note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else @@ -127,7 +136,7 @@ namespace Umbraco.Core.Runtime var configs = GetConfigs(); // type finder/loader - var typeLoader = new TypeLoader(TypeFinder, appCaches.RuntimeCache, configs.Global().LocalTempPath, ProfilingLogger); + var typeLoader = new TypeLoader(IOHelper, TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(configs.Global().LocalTempPath), ProfilingLogger); // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' @@ -334,6 +343,13 @@ namespace Umbraco.Core.Runtime protected virtual ITypeFinder GetTypeFinder() => new TypeFinder(Logger); + /// + /// Gets a + /// + /// + protected virtual IIOHelper GetIOHelper() + => Umbraco.Core.IO.IOHelper.Default; + /// /// Gets the application caches. /// diff --git a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs index 650cc64720..4ef08a15f5 100644 --- a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Sync; using Umbraco.Tests.Components; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Cache.DistributedCache { @@ -25,7 +26,7 @@ namespace Umbraco.Tests.Cache.DistributedCache { var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.RegisterUnique(_ => new TestServerRegistrar()); composition.RegisterUnique(_ => new TestServerMessenger()); diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index ca481352a7..5a437e402f 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Moq; using NUnit.Framework; @@ -52,7 +53,8 @@ namespace Umbraco.Tests.Components private static TypeLoader MockTypeLoader() { - return new TypeLoader(Mock.Of(), Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of()); + var ioHelper = IOHelper.Default; + return new TypeLoader(ioHelper, Mock.Of(), Mock.Of(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of()); } public static IRuntimeState MockRuntimeState(RuntimeLevel level) @@ -362,8 +364,9 @@ namespace Umbraco.Tests.Components [Test] public void AllComposers() { + var ioHelper = IOHelper.Default; var typeFinder = new TypeFinder(Mock.Of()); - var typeLoader = new TypeLoader(typeFinder, AppCaches.Disabled.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of()); + var typeLoader = new TypeLoader(ioHelper, typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of()); var register = MockRegister(); var composition = new Composition(register, typeLoader, Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); diff --git a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs index ec757e09f0..339bc7a3aa 100644 --- a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Composing; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Tests.Components; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Composing { @@ -21,7 +22,7 @@ namespace Umbraco.Tests.Composing Current.Reset(); var register = RegisterFactory.Create(); - _composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + _composition = new Composition(register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); } [TearDown] diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index 743c71b2f3..8a0dc46e7f 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Reflection; using Moq; using NUnit.Framework; @@ -23,7 +24,8 @@ namespace Umbraco.Tests.Composing ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); var typeFinder = new TypeFinder(Mock.Of()); - TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, false, AssembliesToScan); + var ioHelper = IOHelper.Default; + TypeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), ProfilingLogger, false, AssembliesToScan); } [TearDown] diff --git a/src/Umbraco.Tests/Composing/CompositionTests.cs b/src/Umbraco.Tests/Composing/CompositionTests.cs index 9668d7619a..5516d6cab8 100644 --- a/src/Umbraco.Tests/Composing/CompositionTests.cs +++ b/src/Umbraco.Tests/Composing/CompositionTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -37,7 +38,8 @@ namespace Umbraco.Tests.Composing var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); var typeFinder = new TypeFinder(Mock.Of()); - var typeLoader = new TypeLoader(typeFinder, Mock.Of(), IOHelper.MapPath("~/App_Data/TEMP"), logger); + var ioHelper = IOHelper.Default; + 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()); // create the factory, ensure it is the mocked factory diff --git a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs index 3996eba89f..ed5a6e3ee2 100644 --- a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs +++ b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Tests.Components; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Composing { @@ -40,7 +41,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderHandlesTypes() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() @@ -66,7 +67,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderHandlesProducers() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2) }) @@ -91,7 +92,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderHandlesTypesAndProducers() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() @@ -117,7 +118,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderThrowsOnIllegalTypes() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() @@ -139,7 +140,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderCanExcludeTypes() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() diff --git a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs index 07db4be3d3..5586f6baa0 100644 --- a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs +++ b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.PackageActions; using Umbraco.Tests.Components; +using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Composing { @@ -20,7 +21,7 @@ namespace Umbraco.Tests.Composing { var container = RegisterFactory.Create(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add(() => TypeLoader.GetPackageActions()); diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index b135405615..3d726e3906 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -28,24 +28,29 @@ namespace Umbraco.Tests.Composing { // this ensures it's reset var typeFinder = new TypeFinder(Mock.Of()); - _typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()), false); + _typeLoader = new TypeLoader(IOHelper.Default, typeFinder, NoAppCache.Instance, + new DirectoryInfo(IOHelper.Default.MapPath("~/App_Data/TEMP")), + new ProfilingLogger(Mock.Of(), Mock.Of()), false, + + // for testing, we'll specify which assemblies are scanned for the PluginTypeResolver + // TODO: Should probably update this so it only searches this assembly and add custom types to be found + new[] + { + this.GetType().Assembly, + typeof(System.Guid).Assembly, + typeof(NUnit.Framework.Assert).Assembly, + typeof(Microsoft.CSharp.CSharpCodeProvider).Assembly, + typeof(System.Xml.NameTable).Assembly, + typeof(System.Configuration.GenericEnumConverter).Assembly, + typeof(System.Web.SiteMap).Assembly, + //typeof(TabPage).Assembly, + typeof(System.Web.Mvc.ActionResult).Assembly, + typeof(TypeFinder).Assembly, + typeof(UmbracoContext).Assembly + }); + + - // for testing, we'll specify which assemblies are scanned for the PluginTypeResolver - // TODO: Should probably update this so it only searches this assembly and add custom types to be found - _typeLoader.AssembliesToScan = new[] - { - this.GetType().Assembly, - typeof(System.Guid).Assembly, - typeof(NUnit.Framework.Assert).Assembly, - typeof(Microsoft.CSharp.CSharpCodeProvider).Assembly, - typeof(System.Xml.NameTable).Assembly, - typeof(System.Configuration.GenericEnumConverter).Assembly, - typeof(System.Web.SiteMap).Assembly, - //typeof(TabPage).Assembly, - typeof(System.Web.Mvc.ActionResult).Assembly, - typeof(TypeFinder).Assembly, - typeof(UmbracoContext).Assembly - }; } [TearDown] @@ -190,15 +195,15 @@ AnotherContentFinder [Test] public void Create_Cached_Plugin_File() { - var types = new[] { typeof (TypeLoader), typeof (TypeLoaderTests), typeof (UmbracoContext) }; + var types = new[] { typeof(TypeLoader), typeof(TypeLoaderTests), typeof(UmbracoContext) }; - var typeList1 = new TypeLoader.TypeList(typeof (object), null); + var typeList1 = new TypeLoader.TypeList(typeof(object), null); foreach (var type in types) typeList1.Add(type); _typeLoader.AddTypeList(typeList1); _typeLoader.WriteCache(); - var plugins = _typeLoader.TryGetCached(typeof (object), null); - var diffType = _typeLoader.TryGetCached(typeof (object), typeof (ObsoleteAttribute)); + var plugins = _typeLoader.TryGetCached(typeof(object), null); + var diffType = _typeLoader.TryGetCached(typeof(object), typeof(ObsoleteAttribute)); Assert.IsTrue(plugins.Success); //this will be false since there is no cache of that type resolution kind @@ -281,16 +286,16 @@ AnotherContentFinder { var types = new HashSet(); - var propEditors = new TypeLoader.TypeList(typeof (DataEditor), null); + var propEditors = new TypeLoader.TypeList(typeof(DataEditor), null); propEditors.Add(typeof(LabelPropertyEditor)); types.Add(propEditors); - var found = types.SingleOrDefault(x => x.BaseType == typeof (DataEditor) && x.AttributeType == null); + var found = types.SingleOrDefault(x => x.BaseType == typeof(DataEditor) && x.AttributeType == null); Assert.IsNotNull(found); //This should not find a type list of this type - var shouldNotFind = types.SingleOrDefault(x => x.BaseType == typeof (IDataEditor) && x.AttributeType == null); + var shouldNotFind = types.SingleOrDefault(x => x.BaseType == typeof(IDataEditor) && x.AttributeType == null); Assert.IsNull(shouldNotFind); } diff --git a/src/Umbraco.Tests/CoreThings/UdiTests.cs b/src/Umbraco.Tests/CoreThings/UdiTests.cs index a6b87ad57b..dc2265eac2 100644 --- a/src/Umbraco.Tests/CoreThings/UdiTests.cs +++ b/src/Umbraco.Tests/CoreThings/UdiTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using Moq; @@ -25,10 +26,10 @@ namespace Umbraco.Tests.CoreThings { // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); - var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); + var ioHelper = IOHelper.Default; var typeFinder = new TypeFinder(Mock.Of()); container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()))); Current.Factory = container.Object; Udi.ResetUdiTypes(); diff --git a/src/Umbraco.Tests/IO/FileSystemsTests.cs b/src/Umbraco.Tests/IO/FileSystemsTests.cs index 872d7c2526..254371ae32 100644 --- a/src/Umbraco.Tests/IO/FileSystemsTests.cs +++ b/src/Umbraco.Tests/IO/FileSystemsTests.cs @@ -28,7 +28,7 @@ namespace Umbraco.Tests.IO { _register = RegisterFactory.Create(); - var composition = new Composition(_register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(_register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.Register(_ => Mock.Of()); composition.Register(_ => Mock.Of()); diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index 8d2ab84d35..5bea491973 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -70,7 +70,7 @@ namespace Umbraco.Tests.PropertyEditors try { var container = RegisterFactory.Create(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(container, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder(); diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index 764f6ac4a4..eda6d4f5bb 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -24,7 +24,7 @@ namespace Umbraco.Tests.PropertyEditors Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); register.Register(_ => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefaultUmbracoSettings()))); diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index 671129848c..360df5daeb 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -178,7 +178,7 @@ namespace Umbraco.Tests.Published Current.Reset(); var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Append() diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs index 7e6ae75356..d0e780a92b 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentSnapshotTestBase.cs @@ -1,5 +1,7 @@ using System; +using System.IO; using System.Linq; +using System.Reflection; using System.Web.Routing; using Moq; using Umbraco.Core; @@ -12,6 +14,7 @@ using Umbraco.Core.Composing; using Current = Umbraco.Core.Composing.Current; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; @@ -41,16 +44,15 @@ namespace Umbraco.Tests.PublishedContent Composition.RegisterUnique(f => new PublishedModelFactory(f.GetInstance().GetTypes())); } - protected override TypeLoader CreateTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) + protected override TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - var pluginManager = base.CreateTypeLoader(runtimeCache, globalSettings, logger); + var baseLoader = base.CreateTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger); - // this is so the model factory looks into the test assembly - pluginManager.AssembliesToScan = pluginManager.AssembliesToScan - .Union(new[] { typeof (PublishedContentMoreTests).Assembly }) - .ToList(); - - return pluginManager; + return new TypeLoader(ioHelper, typeFinder, runtimeCache, new DirectoryInfo(globalSettings.LocalTempPath), logger, false, + // this is so the model factory looks into the test assembly + baseLoader.AssembliesToScan + .Union(new[] {typeof(PublishedContentMoreTests).Assembly}) + .ToList()); } private UmbracoContext GetUmbracoContext() diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 6ef632bf90..b42dc32b24 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Web; using NUnit.Framework; @@ -14,6 +15,7 @@ using Moq; using Newtonsoft.Json; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Services; @@ -83,16 +85,16 @@ namespace Umbraco.Tests.PublishedContent ContentTypesCache.GetPublishedContentTypeByAlias = alias => alias.InvariantEquals("home") ? homeType : anythingType; } - protected override TypeLoader CreateTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) + + protected override TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - var pluginManager = base.CreateTypeLoader(runtimeCache, globalSettings, logger); + var baseLoader = base.CreateTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger); - // this is so the model factory looks into the test assembly - pluginManager.AssembliesToScan = pluginManager.AssembliesToScan - .Union(new[] { typeof(PublishedContentTests).Assembly }) - .ToList(); - - return pluginManager; + return new TypeLoader(ioHelper, typeFinder, runtimeCache, new DirectoryInfo(globalSettings.LocalTempPath), logger, false, + // this is so the model factory looks into the test assembly + baseLoader.AssembliesToScan + .Union(new[] { typeof(PublishedContentTests).Assembly }) + .ToList()); } private readonly Guid _node1173Guid = Guid.NewGuid(); diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs index c9b34bf3de..bf1c83be9f 100644 --- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -62,7 +62,8 @@ namespace Umbraco.Tests.Runtimes var appCaches = AppCaches.Disabled; var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy(() => factory.GetInstance())); var typeFinder = new TypeFinder(logger); - var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); + var ioHelper = IOHelper.Default; + var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger); var mainDom = new SimpleMainDom(); var runtimeState = new RuntimeState(logger, null, null, new Lazy(() => mainDom), new Lazy(() => factory.GetInstance())); @@ -251,7 +252,8 @@ namespace Umbraco.Tests.Runtimes var appCaches = AppCaches.Disabled; var databaseFactory = Mock.Of(); var typeFinder = new TypeFinder(Mock.Of()); - var typeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, IOHelper.MapPath("~/App_Data/TEMP"), profilingLogger); + var ioHelper = IOHelper.Default; + var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger); var runtimeState = Mock.Of(); Mock.Get(runtimeState).Setup(x => x.Level).Returns(RuntimeLevel.Run); var mainDom = Mock.Of(); diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 001553a8ae..74e79c4ce3 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -32,7 +32,7 @@ namespace Umbraco.Tests.Scoping var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + var composition = new Composition(register, TestHelper.GetMockedTypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); _testObjects = new TestObjects(register); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index a2f08604b5..e1e987c4bf 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Moq; using NPoco; using NUnit.Framework; @@ -36,10 +37,11 @@ namespace Umbraco.Tests.TestHelpers var container = RegisterFactory.Create(); + var ioHelper = IOHelper.Default; var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); var typeFinder = new TypeFinder(Mock.Of()); - var typeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, - IOHelper.MapPath("~/App_Data/TEMP"), + var typeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, + new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger, false); diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 647797a500..134eb8272b 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -7,10 +7,13 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading; +using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.PropertyEditors; @@ -24,13 +27,16 @@ namespace Umbraco.Tests.TestHelpers public static class TestHelper { - + public static TypeLoader GetMockedTypeLoader() + { + return new TypeLoader(Mock.Of(), Mock.Of(), Mock.Of(), new DirectoryInfo(IOHelper.Default.MapPath("~/App_Data/TEMP")), Mock.Of()); + } /// /// Gets the current assembly directory. /// /// The assembly directory. - static public string CurrentAssemblyDirectory + public static string CurrentAssemblyDirectory { get { diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 0e1cd808a7..273202ae7d 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -99,6 +99,8 @@ namespace Umbraco.Tests.Testing protected ILogger Logger => Factory.GetInstance(); + protected IIOHelper IOHelper => Factory.GetInstance(); + protected ITypeFinder TypeFinder => Factory.GetInstance(); protected IProfiler Profiler => Factory.GetInstance(); @@ -132,13 +134,17 @@ namespace Umbraco.Tests.Testing var (logger, profiler) = GetLoggers(Options.Logger); var proflogger = new ProfilingLogger(logger, profiler); var appCaches = GetAppCaches(); + var ioHelper = Umbraco.Core.IO.IOHelper.Default; + var typeFinder = new TypeFinder(logger); var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); - var typeLoader = GetTypeLoader(appCaches.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); + var typeLoader = GetTypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); var register = RegisterFactory.Create(); Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run)); + + Composition.RegisterUnique(ioHelper); Composition.RegisterUnique(typeLoader.TypeFinder); Composition.RegisterUnique(typeLoader); Composition.RegisterUnique(logger); @@ -268,31 +274,30 @@ namespace Umbraco.Tests.Testing .ComposeWebMappingProfiles(); } - protected virtual TypeLoader GetTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger, UmbracoTestOptions.TypeLoader option) + protected virtual TypeLoader GetTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger, UmbracoTestOptions.TypeLoader option) { switch (option) { case UmbracoTestOptions.TypeLoader.Default: - return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(runtimeCache, globalSettings, logger)); + return _commonTypeLoader ?? (_commonTypeLoader = CreateCommonTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger)); case UmbracoTestOptions.TypeLoader.PerFixture: - return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(runtimeCache, globalSettings, logger)); + return _featureTypeLoader ?? (_featureTypeLoader = CreateTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger)); case UmbracoTestOptions.TypeLoader.PerTest: - return CreateTypeLoader(runtimeCache, globalSettings, logger); + return CreateTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger); default: throw new ArgumentOutOfRangeException(nameof(option)); } } - protected virtual TypeLoader CreateTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) + protected virtual TypeLoader CreateTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - return CreateCommonTypeLoader(runtimeCache, globalSettings, logger); + return CreateCommonTypeLoader(ioHelper, typeFinder, runtimeCache, globalSettings, logger); } // common to all tests = cannot be overriden - private static TypeLoader CreateCommonTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) + private static TypeLoader CreateCommonTypeLoader(IIOHelper ioHelper, ITypeFinder typeFinder, IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - var typeFinder = new TypeFinder(Mock.Of()); - return new TypeLoader(typeFinder, runtimeCache, globalSettings.LocalTempPath, logger, false, new[] + return new TypeLoader(ioHelper, typeFinder, runtimeCache, new DirectoryInfo(globalSettings.LocalTempPath), logger, false, new[] { Assembly.Load("Umbraco.Core"), Assembly.Load("Umbraco.Web"), diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 907c5efdd2..e3678a1db5 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using System.Web; using Moq; @@ -40,8 +41,9 @@ namespace Umbraco.Tests.Web // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var factory = new Mock(); var typeFinder = new TypeFinder(Mock.Of()); + var ioHelper = IOHelper.Default; factory.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()))); factory.Setup(x => x.GetInstance(typeof (ServiceContext))).Returns(serviceContext); var settings = SettingsForTests.GetDefaultUmbracoSettings(); diff --git a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs index 0b3a8c2c7b..cdf17e12e9 100644 --- a/src/Umbraco.Tests/Web/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/Web/UmbracoHelperTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Text; using Examine.LuceneEngine; using Lucene.Net.Analysis; @@ -259,14 +260,15 @@ namespace Umbraco.Tests.Web { // FIXME: bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); - var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); var typeFinder = new TypeFinder(Mock.Of()); + var ioHelper = IOHelper.Default; container .Setup(x => x.GetInstance(typeof(TypeLoader))) .Returns(new TypeLoader( + ioHelper, typeFinder, NoAppCache.Instance, - Current.IOHelper.MapPath("~/App_Data/TEMP"), + new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), new ProfilingLogger(Mock.Of(), Mock.Of()) ) ); diff --git a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs index 68c8308a9e..d0cdf76702 100644 --- a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs +++ b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs @@ -25,11 +25,11 @@ namespace Umbraco.Web.Composing internal class BuildManagerTypeFinder : TypeFinder, ITypeFinder { - public BuildManagerTypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig) + public BuildManagerTypeFinder(IIOHelper ioHelper, ILogger logger, ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig) { _allAssemblies = new Lazy>(() => { - var isHosted = IOHelper.IsHosted; + var isHosted = ioHelper.IsHosted; try { if (isHosted) @@ -38,7 +38,7 @@ namespace Umbraco.Web.Composing //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"))); + 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())) { diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index b64968dba4..2ebec69da0 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Runtime #region Getters - protected override ITypeFinder GetTypeFinder() => _typeFinder ??= new BuildManagerTypeFinder(Logger, new BuildManagerTypeFinder.TypeFinderConfig()); + protected override ITypeFinder GetTypeFinder() => _typeFinder ??= new BuildManagerTypeFinder(IOHelper, Logger, new BuildManagerTypeFinder.TypeFinderConfig()); protected override IProfiler GetProfiler() => _webProfiler; From dd24d512fd3941f41fcbe18c09c963641c26a473 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 19:01:10 +1100 Subject: [PATCH 08/11] fixes startup order --- src/Umbraco.Core/Runtime/CoreRuntime.cs | 8 ++++---- src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 42c39bfabf..b6a4f07dfd 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -72,14 +72,14 @@ namespace Umbraco.Core.Runtime var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler); - TypeFinder = GetTypeFinder(); - if (TypeFinder == null) - throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null"); - IOHelper = GetIOHelper(); if (IOHelper == null) throw new InvalidOperationException($"The object returned from {nameof(GetIOHelper)} cannot be null"); + TypeFinder = GetTypeFinder(); + if (TypeFinder == null) + throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null"); + // the boot loader boots using a container scope, so anything that is PerScope will // be disposed after the boot loader has booted, and anything else will remain. // note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else diff --git a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs index d0cdf76702..15c8d6d8d6 100644 --- a/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs +++ b/src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs @@ -27,6 +27,9 @@ namespace Umbraco.Web.Composing public BuildManagerTypeFinder(IIOHelper ioHelper, ILogger logger, ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig) { + if (ioHelper == null) throw new ArgumentNullException(nameof(ioHelper)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + _allAssemblies = new Lazy>(() => { var isHosted = ioHelper.IsHosted; From 46b91b799121d18d96c8b3eccbc199e6d1f5b10b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 19:11:40 +1100 Subject: [PATCH 09/11] no c#8 --- src/Umbraco.Web/Runtime/WebRuntime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 2ebec69da0..2f1ef43e4c 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -59,7 +59,7 @@ namespace Umbraco.Web.Runtime #region Getters - protected override ITypeFinder GetTypeFinder() => _typeFinder ??= new BuildManagerTypeFinder(IOHelper, Logger, new BuildManagerTypeFinder.TypeFinderConfig()); + protected override ITypeFinder GetTypeFinder() => _typeFinder ?? (_typeFinder = new BuildManagerTypeFinder(IOHelper, Logger, new BuildManagerTypeFinder.TypeFinderConfig())); protected override IProfiler GetProfiler() => _webProfiler; From d3fb1ae8424d5307b90fe7e4bb253121504adb03 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 19:46:11 +1100 Subject: [PATCH 10/11] fixing tests --- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 134eb8272b..e07dbe1e5a 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -29,7 +29,7 @@ namespace Umbraco.Tests.TestHelpers public static TypeLoader GetMockedTypeLoader() { - return new TypeLoader(Mock.Of(), Mock.Of(), Mock.Of(), new DirectoryInfo(IOHelper.Default.MapPath("~/App_Data/TEMP")), Mock.Of()); + return new TypeLoader(IOHelper.Default, Mock.Of(), Mock.Of(), new DirectoryInfo(IOHelper.Default.MapPath("~/App_Data/TEMP")), Mock.Of()); } /// From 3242f78aa619191aa38e8dae6cd9df679cebf7fd Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 11 Nov 2019 21:38:18 +1100 Subject: [PATCH 11/11] Fixes tests --- src/Umbraco.Tests/Composing/TypeLoaderTests.cs | 2 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 3d726e3906..5163e6a4e8 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -274,7 +274,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(38, types.Count()); + Assert.AreEqual(37, types.Count()); } /// diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index 273202ae7d..d1e2ab2f6d 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -99,9 +99,9 @@ namespace Umbraco.Tests.Testing protected ILogger Logger => Factory.GetInstance(); - protected IIOHelper IOHelper => Factory.GetInstance(); + protected IIOHelper IOHelper { get; private set; } - protected ITypeFinder TypeFinder => Factory.GetInstance(); + protected ITypeFinder TypeFinder { get; private set; } protected IProfiler Profiler => Factory.GetInstance(); @@ -133,19 +133,19 @@ namespace Umbraco.Tests.Testing var (logger, profiler) = GetLoggers(Options.Logger); var proflogger = new ProfilingLogger(logger, profiler); + IOHelper = Umbraco.Core.IO.IOHelper.Default; + TypeFinder = new TypeFinder(logger); var appCaches = GetAppCaches(); - var ioHelper = Umbraco.Core.IO.IOHelper.Default; - var typeFinder = new TypeFinder(logger); var globalSettings = SettingsForTests.GetDefaultGlobalSettings(); - var typeLoader = GetTypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); + var typeLoader = GetTypeLoader(IOHelper, TypeFinder, appCaches.RuntimeCache, globalSettings, proflogger, Options.TypeLoader); var register = RegisterFactory.Create(); Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run)); - Composition.RegisterUnique(ioHelper); - Composition.RegisterUnique(typeLoader.TypeFinder); + Composition.RegisterUnique(IOHelper); + Composition.RegisterUnique(TypeFinder); Composition.RegisterUnique(typeLoader); Composition.RegisterUnique(logger); Composition.RegisterUnique(profiler);