Merge pull request #7778 from umbraco/netcore/feature/typefinder-netstandard

Creates a netstandard compliant TypeFinder
This commit is contained in:
Shannon Deminick
2020-03-13 11:11:50 +11:00
committed by GitHub
37 changed files with 499 additions and 289 deletions

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Returns a list of scannable assemblies based on an entry point assembly and it's references
/// </summary>
/// <remarks>
/// This will recursively search through the entry point's assemblies and Umbraco's core assemblies and their references
/// to create a list of scannable assemblies based on whether they themselves or their transitive dependencies reference Umbraco core assemblies.
/// </remarks>
public class DefaultUmbracoAssemblyProvider : IAssemblyProvider
{
private readonly Assembly _entryPointAssembly;
private static readonly string[] UmbracoCoreAssemblyNames = new[]
{
"Umbraco.Core",
"Umbraco.Web",
"Umbraco.Infrastructure",
"Umbraco.PublishedCache.NuCache",
"Umbraco.ModelsBuilder.Embedded",
"Umbraco.Examine.Lucene",
};
public DefaultUmbracoAssemblyProvider(Assembly entryPointAssembly)
{
_entryPointAssembly = entryPointAssembly ?? throw new ArgumentNullException(nameof(entryPointAssembly));
}
// TODO: It would be worth investigating a netcore3 version of this which would use
// var allAssemblies = System.Runtime.Loader.AssemblyLoadContext.All.SelectMany(x => x.Assemblies);
// that will still only resolve Assemblies that are already loaded but it would also make it possible to
// query dynamically generated assemblies once they are added. It would also provide the ability to probe
// assembly locations that are not in the same place as the entry point assemblies.
public IEnumerable<Assembly> Assemblies
{
get
{
var finder = new FindAssembliesWithReferencesTo(new[] { _entryPointAssembly }, UmbracoCoreAssemblyNames, true);
return finder.Find();
}
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Finds Assemblies from the entry point assemblies, it's dependencies and it's transitive dependencies that reference that targetAssemblyNames
/// </summary>
/// <remarkes>
/// borrowed and modified from here https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/FindAssembliesWithReferencesTo.cs
/// </remarkes>
internal class FindAssembliesWithReferencesTo
{
private readonly Assembly[] _referenceAssemblies;
private readonly string[] _targetAssemblies;
private readonly bool _includeTargets;
/// <summary>
/// Constructor
/// </summary>
/// <param name="referenceAssemblies">Entry point assemblies</param>
/// <param name="targetAssemblyNames">Used to check if the entry point or it's transitive assemblies reference these assembly names</param>
/// <param name="includeTargets">If true will also use the target assembly names as entry point assemblies</param>
public FindAssembliesWithReferencesTo(Assembly[] referenceAssemblies, string[] targetAssemblyNames, bool includeTargets)
{
_referenceAssemblies = referenceAssemblies;
_targetAssemblies = targetAssemblyNames;
_includeTargets = includeTargets;
}
public IEnumerable<Assembly> Find()
{
var referenceItems = new List<Assembly>();
foreach (var assembly in _referenceAssemblies)
{
referenceItems.Add(assembly);
}
if (_includeTargets)
{
foreach(var target in _targetAssemblies)
{
referenceItems.Add(Assembly.Load(target));
}
}
var provider = new ReferenceResolver(_targetAssemblies, referenceItems);
var assemblyNames = provider.ResolveAssemblies();
return assemblyNames.ToList();
}
}
}

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Reflection;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Provides a list of assemblies that can be scanned
/// </summary>
public interface IAssemblyProvider
{
IEnumerable<Assembly> Assemblies { get; }
}
}

View File

@@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Resolves assemblies that reference one of the specified "targetAssemblies" either directly or transitively.
/// </summary>
/// <remarks>
/// Borrowed and modified from https://github.com/dotnet/aspnetcore-tooling/blob/master/src/Razor/src/Microsoft.NET.Sdk.Razor/ReferenceResolver.cs
/// </remarks>
internal class ReferenceResolver
{
private readonly HashSet<string> _umbracoAssemblies;
private readonly IReadOnlyList<Assembly> _assemblies;
private readonly Dictionary<Assembly, Classification> _classifications;
private readonly List<Assembly> _lookup = new List<Assembly>();
public ReferenceResolver(IReadOnlyList<string> targetAssemblies, IReadOnlyList<Assembly> entryPointAssemblies)
{
_umbracoAssemblies = new HashSet<string>(targetAssemblies, StringComparer.Ordinal);
_assemblies = entryPointAssemblies;
_classifications = new Dictionary<Assembly, Classification>();
foreach (var item in entryPointAssemblies)
{
_lookup.Add(item);
}
}
/// <summary>
/// Returns a list of assemblies that directly reference or transitively reference the targetAssemblies
/// </summary>
/// <returns></returns>
/// <remarks>
/// This includes all assemblies in the same location as the entry point assemblies
/// </remarks>
public IEnumerable<Assembly> ResolveAssemblies()
{
var applicationParts = new List<Assembly>();
var assemblies = new HashSet<Assembly>(_assemblies);
// Get the unique directories of the assemblies
var assemblyLocations = GetAssemblyFolders(assemblies).ToList();
// Load in each assembly in the directory of the entry assembly to be included in the search
// for Umbraco dependencies/transitive dependencies
foreach(var dir in assemblyLocations)
{
foreach(var dll in Directory.EnumerateFiles(dir, "*.dll"))
{
var assemblyName = AssemblyName.GetAssemblyName(dll);
// don't include if this is excluded
if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => assemblyName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase)))
continue;
// don't include this item if it's Umbraco
// TODO: We should maybe pass an explicit list of these names in?
if (assemblyName.FullName.StartsWith("Umbraco."))
continue;
var assembly = Assembly.Load(assemblyName);
assemblies.Add(assembly);
}
}
foreach (var item in assemblies)
{
var classification = Resolve(item);
if (classification == Classification.ReferencesUmbraco || classification == Classification.IsUmbraco)
{
applicationParts.Add(item);
}
}
return applicationParts;
}
private IEnumerable<string> GetAssemblyFolders(IEnumerable<Assembly> assemblies)
{
return assemblies.Select(x => Path.GetDirectoryName(GetAssemblyLocation(x)).ToLowerInvariant()).Distinct();
}
// borrowed from https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Core/src/ApplicationParts/RelatedAssemblyAttribute.cs
private string GetAssemblyLocation(Assembly assembly)
{
if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) &&
result.IsFile && string.IsNullOrWhiteSpace(result.Fragment))
{
return result.LocalPath;
}
return assembly.Location;
}
private Classification Resolve(Assembly assembly)
{
if (_classifications.TryGetValue(assembly, out var classification))
{
return classification;
}
// Initialize the dictionary with a value to short-circuit recursive references.
classification = Classification.Unknown;
_classifications[assembly] = classification;
if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => assembly.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase)))
{
// if its part of the filter it doesn't reference umbraco
classification = Classification.DoesNotReferenceUmbraco;
}
else if (_umbracoAssemblies.Contains(assembly.GetName().Name))
{
classification = Classification.IsUmbraco;
}
else
{
classification = Classification.DoesNotReferenceUmbraco;
foreach (var reference in GetReferences(assembly))
{
// recurse
var referenceClassification = Resolve(reference);
if (referenceClassification == Classification.IsUmbraco || referenceClassification == Classification.ReferencesUmbraco)
{
classification = Classification.ReferencesUmbraco;
break;
}
}
}
Debug.Assert(classification != Classification.Unknown);
_classifications[assembly] = classification;
return classification;
}
protected virtual IEnumerable<Assembly> GetReferences(Assembly assembly)
{
foreach (var referenceName in assembly.GetReferencedAssemblies())
{
// don't include if this is excluded
if (TypeFinder.KnownAssemblyExclusionFilter.Any(f => referenceName.FullName.StartsWith(f, StringComparison.InvariantCultureIgnoreCase)))
continue;
var reference = Assembly.Load(referenceName);
if (!_lookup.Contains(reference))
{
// A dependency references an item that isn't referenced by this project.
// We'll add this reference so that we can calculate the classification.
_lookup.Add(reference);
}
yield return reference;
}
}
protected enum Classification
{
Unknown,
DoesNotReferenceUmbraco,
ReferencesUmbraco,
IsUmbraco,
}
}
}

View File

@@ -1,106 +1,35 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Composing
{
/// <inheritdoc cref="ITypeFinder"/>
public class TypeFinder : ITypeFinder
{
private readonly 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<string>();
_allAssemblies = new Lazy<HashSet<Assembly>>(() =>
{
HashSet<Assembly> assemblies = null;
try
{
//NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have
// already been loaded in to the app domain, instead we will look directly into the bin folder and load each one.
var binFolder = GetRootDirectorySafe();
var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList();
//var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory;
//var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList();
assemblies = new HashSet<Assembly>();
foreach (var a in binAssemblyFiles)
{
try
{
var assName = AssemblyName.GetAssemblyName(a);
var ass = Assembly.Load(assName);
assemblies.Add(ass);
}
catch (Exception e)
{
if (e is SecurityException || e is BadImageFormatException)
{
//swallow these exceptions
}
else
{
throw;
}
}
}
//Since we are only loading in the /bin assemblies above, we will also load in anything that's already loaded (which will include gac items)
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
assemblies.Add(a);
}
}
catch (InvalidOperationException e)
{
if (e.InnerException is SecurityException == false)
throw;
}
return assemblies;
});
}
//Lazy access to the all assemblies list
private readonly Lazy<HashSet<Assembly>> _allAssemblies;
private readonly IAssemblyProvider _assemblyProvider;
private volatile HashSet<Assembly> _localFilteredAssemblyCache;
private readonly object _localFilteredAssemblyCacheLocker = new object();
private readonly List<string> _notifiedLoadExceptionAssemblies = new List<string>();
private static readonly ConcurrentDictionary<string, Type> TypeNamesCache= new ConcurrentDictionary<string, Type>();
private string _rootDir = "";
private static readonly ConcurrentDictionary<string, Type> TypeNamesCache = new ConcurrentDictionary<string, Type>();
private readonly string[] _assembliesAcceptingLoadExceptions;
// FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here
private string GetRootDirectorySafe()
// used for benchmark tests
internal bool QueryWithReferencingAssemblies = true;
public TypeFinder(ILogger logger, IAssemblyProvider assemblyProvider, ITypeFinderConfig typeFinderConfig = null)
{
if (string.IsNullOrEmpty(_rootDir) == false)
{
return _rootDir;
}
var codeBase = Assembly.GetExecutingAssembly().CodeBase;
var uri = new Uri(codeBase);
var path = uri.LocalPath;
var baseDirectory = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(baseDirectory))
throw new PanicException("No root directory could be resolved.");
_rootDir = baseDirectory.Contains("bin")
? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1)
: baseDirectory;
return _rootDir;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_assemblyProvider = assemblyProvider;
_assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty<string>();
}
private bool AcceptsLoadExceptions(Assembly a)
@@ -119,22 +48,8 @@ namespace Umbraco.Core.Composing
});
}
/// <summary>
/// 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
/// </summary>
/// <remarks>
/// 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
/// </remarks>
private IEnumerable<Assembly> GetAllAssemblies()
{
return _allAssemblies.Value;
}
private IEnumerable<Assembly> GetAllAssemblies() => _assemblyProvider.Assemblies;
/// <inheritdoc />
public IEnumerable<Assembly> AssembliesToScan
@@ -181,7 +96,10 @@ 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"
/// </remarks>
private static readonly string[] KnownAssemblyExclusionFilter = {
internal static readonly string[] KnownAssemblyExclusionFilter = {
"mscorlib,",
"netstandard,",
"System,",
"Antlr3.",
"AutoMapper,",
"AutoMapper.",
@@ -228,7 +146,14 @@ namespace Umbraco.Core.Composing
"WebDriver,",
"itextsharp,",
"mscorlib,",
"nunit.framework,",
"NUnit,",
"NUnit.",
"NUnit3.",
"Selenium.",
"ImageProcessor",
"MiniProfiler.",
"Owin,",
"SQLite",
};
/// <summary>
@@ -290,6 +215,11 @@ namespace Umbraco.Core.Composing
/// <returns></returns>
public virtual Type GetTypeByName(string name)
{
//NOTE: This will not find types in dynamic assemblies unless those assemblies are already loaded
//into the appdomain.
// This is exactly what the BuildManager does, if the type is an assembly qualified type
// name it will find it.
if (TypeNameContainsAssembly(name))
@@ -340,18 +270,24 @@ namespace Umbraco.Core.Composing
var stack = new Stack<Assembly>();
stack.Push(attributeType.Assembly);
if (!QueryWithReferencingAssemblies)
{
foreach (var a in candidateAssemblies)
stack.Push(a);
}
while (stack.Count > 0)
{
var assembly = stack.Pop();
Type[] assemblyTypes = null;
IReadOnlyList<Type> assemblyTypes = null;
if (assembly != attributeType.Assembly || attributeAssemblyIsCandidate)
{
// get all assembly types that can be assigned to baseType
try
{
assemblyTypes = GetTypesWithFormattedException(assembly)
.ToArray(); // in try block
.ToList(); // in try block
}
catch (TypeLoadException ex)
{
@@ -371,10 +307,13 @@ namespace Umbraco.Core.Composing
if (assembly != attributeType.Assembly && assemblyTypes.Where(attributeType.IsAssignableFrom).Any() == false)
continue;
foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies))
if (QueryWithReferencingAssemblies)
{
candidateAssemblies.Remove(referencing);
stack.Push(referencing);
foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies))
{
candidateAssemblies.Remove(referencing);
stack.Push(referencing);
}
}
}
@@ -405,19 +344,25 @@ namespace Umbraco.Core.Composing
var stack = new Stack<Assembly>();
stack.Push(baseType.Assembly);
if (!QueryWithReferencingAssemblies)
{
foreach (var a in candidateAssemblies)
stack.Push(a);
}
while (stack.Count > 0)
{
var assembly = stack.Pop();
// get all assembly types that can be assigned to baseType
Type[] assemblyTypes = null;
IReadOnlyList<Type> assemblyTypes = null;
if (assembly != baseType.Assembly || baseTypeAssemblyIsCandidate)
{
try
{
assemblyTypes = GetTypesWithFormattedException(assembly)
.Where(baseType.IsAssignableFrom)
.ToArray(); // in try block
.ToList(); // in try block
}
catch (TypeLoadException ex)
{
@@ -437,10 +382,13 @@ namespace Umbraco.Core.Composing
if (assembly != baseType.Assembly && assemblyTypes.All(x => x.IsSealed))
continue;
foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies))
if (QueryWithReferencingAssemblies)
{
candidateAssemblies.Remove(referencing);
stack.Push(referencing);
foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies))
{
candidateAssemblies.Remove(referencing);
stack.Push(referencing);
}
}
}
@@ -522,6 +470,5 @@ namespace Umbraco.Core.Composing
#endregion
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
namespace Umbraco.Core.Composing
{
/// <summary>
/// TypeFinder config via appSettings
/// </summary>
internal class TypeFinderConfig : ITypeFinderConfig
{
private readonly ITypeFinderSettings _settings;
private IEnumerable<string> _assembliesAcceptingLoadExceptions;
public TypeFinderConfig(ITypeFinderSettings settings)
{
_settings = settings;
}
public IEnumerable<string> AssembliesAcceptingLoadExceptions
{
get
{
if (_assembliesAcceptingLoadExceptions != null)
return _assembliesAcceptingLoadExceptions;
var s = _settings.AssembliesAcceptingLoadExceptions;
return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s)
? Array.Empty<string>()
: s.Split(',').Select(x => x.Trim()).ToArray();
}
}
}
}

View File

@@ -82,9 +82,9 @@ namespace Umbraco.Core.Composing
/// If the assembly of the assignTypeFrom Type is in the App_Code assembly, then we return nothing since things cannot
/// reference that assembly, same with the global.asax assembly.
/// </remarks>
public static Assembly[] GetReferencingAssemblies(Assembly assembly, IEnumerable<Assembly> assemblies)
public static IReadOnlyList<Assembly> GetReferencingAssemblies(Assembly assembly, IEnumerable<Assembly> assemblies)
{
if (assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly())
if (assembly.IsDynamic || assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly())
return EmptyAssemblies;
@@ -92,7 +92,7 @@ namespace Umbraco.Core.Composing
// should only be scanning those assemblies because any other assembly will definitely not
// contain sub type's of the one we're currently looking for
var name = assembly.GetName().Name;
return assemblies.Where(x => x == assembly || HasReference(x, name)).ToArray();
return assemblies.Where(x => x == assembly || HasReference(x, name)).ToList();
}
/// <summary>

View File

@@ -516,29 +516,29 @@ namespace Umbraco.Core.Composing
#region Get Assembly Attributes
/// <summary>
/// Gets the assembly attributes of the specified type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The attribute type.</typeparam>
/// <returns>
/// The assembly attributes of the specified type <typeparamref name="T" />.
/// </returns>
public IEnumerable<T> GetAssemblyAttributes<T>()
where T : Attribute
{
return AssembliesToScan.SelectMany(a => a.GetCustomAttributes<T>()).ToList();
}
///// <summary>
///// Gets the assembly attributes of the specified type <typeparamref name="T" />.
///// </summary>
///// <typeparam name="T">The attribute type.</typeparam>
///// <returns>
///// The assembly attributes of the specified type <typeparamref name="T" />.
///// </returns>
//public IEnumerable<T> GetAssemblyAttributes<T>()
// where T : Attribute
//{
// return AssembliesToScan.SelectMany(a => a.GetCustomAttributes<T>()).ToList();
//}
/// <summary>
/// Gets all the assembly attributes.
/// </summary>
/// <returns>
/// All assembly attributes.
/// </returns>
public IEnumerable<Attribute> GetAssemblyAttributes()
{
return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList();
}
///// <summary>
///// Gets all the assembly attributes.
///// </summary>
///// <returns>
///// All assembly attributes.
///// </returns>
//public IEnumerable<Attribute> GetAssemblyAttributes()
//{
// return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList();
//}
/// <summary>
/// Gets the assembly attributes of the specified <paramref name="attributeTypes" />.

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
@@ -369,7 +370,21 @@ namespace Umbraco.Core.Runtime
/// </summary>
/// <returns></returns>
protected virtual ITypeFinder GetTypeFinder()
=> new TypeFinder(Logger);
// TODO: Currently we are not passing in any TypeFinderConfig (with ITypeFinderSettings) which we should do, however
// this is not critical right now and would require loading in some config before boot time so just leaving this as-is for now.
=> new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(
// GetEntryAssembly was actually an exposed API by request of the aspnetcore team which works in aspnet core because a website
// in that case is essentially an exe. However in netframework there is no entry assembly, things don't really work that way since
// the process that is running the site is iisexpress, so this returns null. The best we can do is fallback to GetExecutingAssembly()
// which will just return Umbraco.Infrastructure (currently with netframework) and for our purposes that is OK.
// If you are curious... There is really no way to get the entry assembly in netframework without the hosting website having it's own
// code compiled for the global.asax which is the entry point. Because the default global.asax for umbraco websites is just a file inheriting
// from Umbraco.Web.UmbracoApplication, the global.asax file gets dynamically compiled into a DLL in the dynamic folder (we can get an instance
// of that, but this doesn't really help us) but the actually entry execution is still Umbraco.Web. So that is the 'highest' level entry point
// assembly we can get and we can only get that if we put this code into the WebRuntime since the executing assembly is the 'current' one.
// For this purpose, it doesn't matter if it's Umbraco.Web or Umbraco.Infrastructure since all assemblies are in that same path and we are
// getting rid of netframework.
Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()));
/// <summary>

View File

@@ -0,0 +1,30 @@
using BenchmarkDotNet.Attributes;
using System;
using System.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Tests.Benchmarks.Config;
namespace Umbraco.Tests.Benchmarks
{
[MediumRunJob]
[MemoryDiagnoser]
public class TypeFinderBenchmarks
{
[Benchmark(Baseline = true)]
public void WithGetReferencingAssembliesCheck()
{
var typeFinder1 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var found = typeFinder1.FindClassesOfType<IDiscoverable>().Count();
}
[Benchmark]
public void WithoutGetReferencingAssembliesCheck()
{
var typeFinder2 = new TypeFinder(new NullLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly));
typeFinder2.QueryWithReferencingAssemblies = false;
var found = typeFinder2.FindClassesOfType<IDiscoverable>().Count();
}
}
}

View File

@@ -61,6 +61,7 @@
<Compile Include="SqlTemplatesBenchmark.cs" />
<Compile Include="StringReplaceManyBenchmarks.cs" />
<Compile Include="TryConvertToBenchmarks.cs" />
<Compile Include="TypeFinderBenchmarks.cs" />
<Compile Include="XmlBenchmarks.cs" />
</ItemGroup>
<ItemGroup>

View File

@@ -13,6 +13,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Tests.Collections;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web.Cache;
namespace Umbraco.Tests.Cache
@@ -28,7 +29,7 @@ namespace Umbraco.Tests.Cache
public override void Setup()
{
base.Setup();
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
_memberCache = new ObjectCacheAppCache(typeFinder);
_provider = new DeepCloneAppCache(_memberCache);

View File

@@ -16,7 +16,7 @@ namespace Umbraco.Tests.Cache
public override void Setup()
{
base.Setup();
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
_ctx = new FakeHttpContextFactory("http://localhost/test");
_appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items, typeFinder);
}

View File

@@ -1,10 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Moq;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Cache
{
@@ -21,7 +18,7 @@ namespace Umbraco.Tests.Cache
public override void Setup()
{
base.Setup();
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
_provider = new ObjectCacheAppCache(typeFinder);
}

View File

@@ -32,7 +32,7 @@ namespace Umbraco.Tests.Components
var mock = new Mock<IFactory>();
var logger = Mock.Of<ILogger>();
var typeFinder = new TypeFinder(logger);
var typeFinder = TestHelper.GetTypeFinder();
var f = new UmbracoDatabaseFactory(logger, new Lazy<IMapperCollection>(() => new MapperCollection(Enumerable.Empty<BaseMapper>())), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator);
var fs = new FileSystems(mock.Object, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings());
var coreDebug = Mock.Of<ICoreDebug>();
@@ -371,14 +371,15 @@ namespace Umbraco.Tests.Components
public void AllComposers()
{
var ioHelper = TestHelper.IOHelper;
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var typeLoader = new TypeLoader(ioHelper, typeFinder, AppCaches.Disabled.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), Mock.Of<IProfilingLogger>());
var register = MockRegister();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(),
MockRuntimeState(RuntimeLevel.Run), Configs, TestHelper.IOHelper, AppCaches.NoCache);
var types = typeLoader.GetTypes<IComposer>().Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web"));
var allComposers = typeLoader.GetTypes<IComposer>().ToList();
var types = allComposers.Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web")).ToList();
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
var requirements = composers.GetRequirements();
var report = Composers.GetComposersReport(requirements);

View File

@@ -22,7 +22,7 @@ namespace Umbraco.Tests.Composing
{
ProfilingLogger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var ioHelper = TestHelper.IOHelper;
TypeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), ProfilingLogger, false, AssembliesToScan);
}

View File

@@ -38,7 +38,7 @@ namespace Umbraco.Tests.Composing
.Returns(() => factoryFactory?.Invoke(mockedFactory));
var logger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var ioHelper = TestHelper.IOHelper;
var typeLoader = new TypeLoader(ioHelper, typeFinder, Mock.Of<IAppPolicyCache>(), new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), logger);
var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of<IRuntimeState>(), TestHelper.GetConfigs(), TestHelper.IOHelper, AppCaches.NoCache);

View File

@@ -57,7 +57,7 @@ namespace Umbraco.Tests.Composing
[Test]
public void Find_Class_Of_Type_With_Attribute()
{
var typeFinder = new TypeFinder(GetTestProfilingLogger());
var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var typesFound = typeFinder.FindClassesOfTypeWithAttribute<TestEditor, MyTestAttribute>(_assemblies);
Assert.AreEqual(2, typesFound.Count());
}
@@ -65,12 +65,15 @@ namespace Umbraco.Tests.Composing
[Test]
public void Find_Classes_With_Attribute()
{
var typeFinder = new TypeFinder(GetTestProfilingLogger());
var typeFinder = new TypeFinder(GetTestProfilingLogger(), new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var typesFound = typeFinder.FindClassesWithAttribute<TreeAttribute>(_assemblies);
Assert.AreEqual(0, typesFound.Count()); // 0 classes in _assemblies are marked with [Tree]
typesFound = typeFinder.FindClassesWithAttribute<TreeAttribute>(new[] { typeof (UmbracoContext).Assembly });
Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree]
typesFound = typeFinder.FindClassesWithAttribute<TreeAttribute>();
Assert.AreEqual(22, typesFound.Count()); // + classes in Umbraco.Web are marked with [Tree]
}
private static IProfilingLogger GetTestProfilingLogger()

View File

@@ -27,7 +27,7 @@ namespace Umbraco.Tests.Composing
public void Initialize()
{
// this ensures it's reset
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
_typeLoader = new TypeLoader(TestHelper.IOHelper, typeFinder, NoAppCache.Instance,
new DirectoryInfo(TestHelper.IOHelper.MapPath("~/App_Data/TEMP")),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()), false,

View File

@@ -17,7 +17,7 @@ namespace Umbraco.Tests.Macros
[SetUp]
public void Setup()
{
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
//we DO want cache enabled for these tests
var cacheHelper = new AppCaches(
new ObjectCacheAppCache(typeFinder),

View File

@@ -22,6 +22,7 @@ using Umbraco.Tests.TestHelpers.Entities;
using Umbraco.Tests.TestHelpers.Stubs;
using Umbraco.Tests.Testing;
using Umbraco.Web.PropertyEditors;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Models
{
@@ -269,7 +270,8 @@ namespace Umbraco.Tests.Models
content.UpdateDate = DateTime.Now;
content.WriterId = 23;
var runtimeCache = new ObjectCacheAppCache(new TypeFinder(Mock.Of<ILogger>()));
var typeFinder = TestHelper.GetTypeFinder();
var runtimeCache = new ObjectCacheAppCache(typeFinder);
runtimeCache.Insert(content.Id.ToString(CultureInfo.InvariantCulture), () => content);
var proflog = GetTestProfilingLogger();

View File

@@ -127,7 +127,7 @@ namespace Umbraco.Tests.Published
var setType1 = publishedContentTypeFactory.CreateContentType(1000, "set1", CreatePropertyTypes);
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var elementsCache = new FastDictionaryAppCache(typeFinder);
var snapshotCache = new FastDictionaryAppCache(typeFinder);

View File

@@ -143,7 +143,7 @@ namespace Umbraco.Tests.PublishedContent
// create a data source for NuCache
_source = new TestDataSource(kits);
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var settings = Mock.Of<INuCacheSettings>();

View File

@@ -184,7 +184,7 @@ namespace Umbraco.Tests.PublishedContent
// create a variation accessor
_variationAccesor = new TestVariationContextAccessor();
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var settings = Mock.Of<INuCacheSettings>();
// at last, create the complete NuCache snapshot service!

View File

@@ -125,6 +125,10 @@ namespace Umbraco.Tests.Runtimes
}
// override because we cannot use Assembly.GetEntryAssembly in Nunit tests since that is always null
protected override ITypeFinder GetTypeFinder()
=> new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
// must override the database factory
// else BootFailedException because U cannot connect to the configured db
protected internal override IUmbracoDatabaseFactory GetDatabaseFactory()

View File

@@ -63,7 +63,7 @@ namespace Umbraco.Tests.Runtimes
var profilingLogger = new ProfilingLogger(logger, profiler);
var appCaches = AppCaches.Disabled;
var databaseFactory = new UmbracoDatabaseFactory(logger, new Lazy<IMapperCollection>(() => factory.GetInstance<IMapperCollection>()), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator);
var typeFinder = new TypeFinder(logger);
var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var ioHelper = TestHelper.IOHelper;
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger);
@@ -256,7 +256,7 @@ namespace Umbraco.Tests.Runtimes
var profilingLogger = new ProfilingLogger(logger, profiler);
var appCaches = AppCaches.Disabled;
var databaseFactory = Mock.Of<IUmbracoDatabaseFactory>();
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var ioHelper = TestHelper.IOHelper;
var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger);
var runtimeState = Mock.Of<IRuntimeState>();

View File

@@ -85,7 +85,7 @@ namespace Umbraco.Tests.Scoping
var memberRepository = Mock.Of<IMemberRepository>();
var hostingEnvironment = TestHelper.GetHostingEnvironment();
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var settings = Mock.Of<INuCacheSettings>();
return new PublishedSnapshotService(

View File

@@ -58,7 +58,7 @@ namespace Umbraco.Tests.Services
var memberRepository = Mock.Of<IMemberRepository>();
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var settings = Mock.Of<INuCacheSettings>();
return new PublishedSnapshotService(

View File

@@ -38,7 +38,7 @@ namespace Umbraco.Tests.TestHelpers
var ioHelper = TestHelper.IOHelper;
var logger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var typeLoader = new TypeLoader(ioHelper, typeFinder, NoAppCache.Instance,
new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")),
logger,

View File

@@ -40,7 +40,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs
{
if (_factory != null) return _factory(requestContext);
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var types = typeFinder.FindClassesOfType<ControllerBase>(new[] { Assembly.GetExecutingAssembly() });
var controllerTypes = types.Where(x => x.Name.Equals(controllerName + "Controller", StringComparison.InvariantCultureIgnoreCase));

View File

@@ -43,6 +43,14 @@ namespace Umbraco.Tests.TestHelpers
public static class TestHelper
{
public static ITypeFinder GetTypeFinder()
{
var typeFinder = new TypeFinder(Mock.Of<ILogger>(),
new DefaultUmbracoAssemblyProvider(typeof(TestHelper).Assembly));
return typeFinder;
}
public static TypeLoader GetMockedTypeLoader()
{
return new TypeLoader(IOHelper, Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), new DirectoryInfo(IOHelper.MapPath("~/App_Data/TEMP")), Mock.Of<IProfilingLogger>());

View File

@@ -247,7 +247,7 @@ namespace Umbraco.Tests.TestHelpers
databaseFactory = new UmbracoDatabaseFactory(Constants.System.UmbracoConnectionName, logger, new Lazy<IMapperCollection>(() => mappers), TestHelper.GetConfigs(), TestHelper.DbProviderFactoryCreator);
}
typeFinder = typeFinder ?? new TypeFinder(logger);
typeFinder = typeFinder ?? new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger, TestHelper.IOHelper, SettingsForTests.GenerateMockGlobalSettings());
var coreDebug = Current.Configs.CoreDebug();
var mediaFileSystem = Mock.Of<IMediaFileSystem>();

View File

@@ -171,7 +171,7 @@ namespace Umbraco.Tests.Testing
var proflogger = new ProfilingLogger(logger, profiler);
IOHelper = TestHelper.IOHelper;
TypeFinder = new TypeFinder(logger);
TypeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var appCaches = GetAppCaches();
var globalSettings = SettingsForTests.GetDefaultGlobalSettings();
var settings = SettingsForTests.GenerateMockWebRoutingSettings();

View File

@@ -28,7 +28,7 @@ namespace Umbraco.Tests.Web
{
// FIXME: bad in a unit test - but Udi has a static ctor that wants it?!
var container = new Mock<IFactory>();
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
var typeFinder = TestHelper.GetTypeFinder();
var ioHelper = TestHelper.IOHelper;
container
.Setup(x => x.GetInstance(typeof(TypeLoader)))

View File

@@ -1,118 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Web.Compilation;
using Umbraco.Core.Configuration;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Web.Composing
{
/// <summary>
/// An implementation of TypeFinder that uses the BuildManager to resolve references for aspnet framework hosted websites
/// </summary>
/// <remarks>
/// This finder will also try to resolve dynamic assemblies created from App_Code
/// </remarks>
internal class BuildManagerTypeFinder : TypeFinder, ITypeFinder
{
public BuildManagerTypeFinder(
IIOHelper ioHelper,
IHostingEnvironment hostingEnvironment,
ILogger logger,
ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig)
{
if (ioHelper == null) throw new ArgumentNullException(nameof(ioHelper));
if (hostingEnvironment == null) throw new ArgumentNullException(nameof(hostingEnvironment));
if (logger == null) throw new ArgumentNullException(nameof(logger));
_allAssemblies = new Lazy<HashSet<Assembly>>(() =>
{
var isHosted = hostingEnvironment.IsHosted;
try
{
if (isHosted)
{
var assemblies = new HashSet<Assembly>(BuildManager.GetReferencedAssemblies().Cast<Assembly>());
//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<Assembly>(base.AssembliesToScan);
});
}
private readonly Lazy<HashSet<Assembly>> _allAssemblies;
/// <summary>
/// Explicitly implement and return result from BuildManager
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
Type ITypeFinder.GetTypeByName (string name) => BuildManager.GetType(name, false);
/// <summary>
/// Explicitly implement and return result from BuildManager
/// </summary>
IEnumerable<Assembly> ITypeFinder.AssembliesToScan => _allAssemblies.Value;
/// <summary>
/// TypeFinder config via appSettings
/// </summary>
internal class TypeFinderConfig : ITypeFinderConfig
{
private readonly ITypeFinderSettings _settings;
private IEnumerable<string> _assembliesAcceptingLoadExceptions;
public TypeFinderConfig(ITypeFinderSettings settings)
{
_settings = settings;
}
public IEnumerable<string> AssembliesAcceptingLoadExceptions
{
get
{
if (_assembliesAcceptingLoadExceptions != null)
return _assembliesAcceptingLoadExceptions;
var s = _settings.AssembliesAcceptingLoadExceptions;
return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s)
? Array.Empty<string>()
: s.Split(',').Select(x => x.Trim()).ToArray();
}
}
}
}
}

View File

@@ -21,8 +21,6 @@ namespace Umbraco.Web.Runtime
/// <remarks>On top of CoreRuntime, handles all of the web-related runtime aspects of Umbraco.</remarks>
public class WebRuntime : CoreRuntime
{
private BuildManagerTypeFinder _typeFinder;
/// <summary>
/// Initializes a new instance of the <see cref="WebRuntime"/> class.
/// </summary>
@@ -92,8 +90,6 @@ namespace Umbraco.Web.Runtime
#region Getters
protected override ITypeFinder GetTypeFinder() => _typeFinder ?? (_typeFinder = new BuildManagerTypeFinder(IOHelper, HostingEnvironment, Logger, new BuildManagerTypeFinder.TypeFinderConfig(new TypeFinderSettings())));
protected override AppCaches GetAppCaches() => new AppCaches(
// we need to have the dep clone runtime cache provider to ensure
// all entities are cached properly (cloned in and cloned out)

View File

@@ -143,7 +143,6 @@
<Compile Include="Compose\AuditEventsComposer.cs" />
<Compile Include="Compose\BackOfficeUserAuditEventsComponent.cs" />
<Compile Include="Compose\BackOfficeUserAuditEventsComposer.cs" />
<Compile Include="Composing\BuildManagerTypeFinder.cs" />
<Compile Include="Composing\CompositionExtensions\Installer.cs" />
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
<Compile Include="Models\NoNodesViewModel.cs" />