From 6398f1e2166fb42acdd8528580a403c16595eb30 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 14:26:06 +1100 Subject: [PATCH] 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,