Moving TypeHelper ... but now need IOHelper...

This commit is contained in:
Shannon
2019-11-11 17:30:50 +11:00
parent d84963dac5
commit a8face2023
12 changed files with 189 additions and 100 deletions

View File

@@ -4,6 +4,9 @@ using System.Reflection;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Used to find objects by implemented types, names and/or attributes
/// </summary>
public interface ITypeFinder
{
Type GetTypeByName(string name);

View File

@@ -7,70 +7,51 @@ using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
using System.Web.Compilation;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Exceptions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Core.Composing
{
/// <summary>
/// A utility class to find all classes of a certain type by reflection in the current bin folder
/// of the web application.
/// </summary>
/// <inheritdoc cref="ITypeFinder"/>
public class TypeFinder : ITypeFinder
{
private readonly ILogger _logger;
public TypeFinder(ILogger logger)
public TypeFinder(ILogger logger, ITypeFinderConfig typeFinderConfig = null)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_assembliesAcceptingLoadExceptions = typeFinderConfig?.AssembliesAcceptingLoadExceptions.Where(x => !x.IsNullOrWhiteSpace()).ToArray() ?? Array.Empty<string>();
_allAssemblies = new Lazy<HashSet<Assembly>>(() =>
{
HashSet<Assembly> assemblies = null;
try
{
var isHosted = IOHelper.IsHosted;
try
//NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have
// already been loaded in to the app domain, instead we will look directly into the bin folder and load each one.
var binFolder = GetRootDirectorySafe();
var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList();
//var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory;
//var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList();
assemblies = new HashSet<Assembly>();
foreach (var a in binAssemblyFiles)
{
if (isHosted)
try
{
assemblies = new HashSet<Assembly>(BuildManager.GetReferencedAssemblies().Cast<Assembly>());
var assName = AssemblyName.GetAssemblyName(a);
var ass = Assembly.Load(assName);
assemblies.Add(ass);
}
}
catch (InvalidOperationException e)
{
if (e.InnerException is SecurityException == false)
throw;
}
if (assemblies == null)
{
//NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have
// already been loaded in to the app domain, instead we will look directly into the bin folder and load each one.
var binFolder = IOHelper.GetRootDirectoryBinFolder();
var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList();
//var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory;
//var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList();
assemblies = new HashSet<Assembly>();
foreach (var a in binAssemblyFiles)
catch (Exception e)
{
try
if (e is SecurityException || e is BadImageFormatException)
{
var assName = AssemblyName.GetAssemblyName(a);
var ass = Assembly.Load(assName);
assemblies.Add(ass);
//swallow these exceptions
}
catch (Exception e)
else
{
if (e is SecurityException || e is BadImageFormatException)
{
//swallow these exceptions
}
else
{
throw;
}
throw;
}
}
}
@@ -80,25 +61,6 @@ namespace Umbraco.Core.Composing
{
assemblies.Add(a);
}
//here we are trying to get the App_Code assembly
var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported
var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code")));
//check if the folder exists and if there are any files in it with the supported file extensions
if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))
{
try
{
var appCodeAssembly = Assembly.Load("App_Code");
if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already
assemblies.Add(appCodeAssembly);
}
catch (FileNotFoundException ex)
{
//this will occur if it cannot load the assembly
_logger.Error(typeof(TypeFinder), ex, "Could not load assembly App_Code");
}
}
}
catch (InvalidOperationException e)
{
@@ -115,31 +77,40 @@ namespace Umbraco.Core.Composing
private volatile HashSet<Assembly> _localFilteredAssemblyCache;
private readonly object _localFilteredAssemblyCacheLocker = new object();
private readonly List<string> _notifiedLoadExceptionAssemblies = new List<string>();
private string[] _assembliesAcceptingLoadExceptions;
private static readonly ConcurrentDictionary<string, Type> TypeNamesCache= new ConcurrentDictionary<string, Type>();
private string _rootDir = "";
private readonly string[] _assembliesAcceptingLoadExceptions;
private string[] AssembliesAcceptingLoadExceptions
// FIXME - this is only an interim change, once the IIOHelper stuff is merged we should use IIOHelper here
private string GetRootDirectorySafe()
{
get
if (string.IsNullOrEmpty(_rootDir) == false)
{
if (_assembliesAcceptingLoadExceptions != null)
return _assembliesAcceptingLoadExceptions;
var s = ConfigurationManager.AppSettings[Constants.AppSettings.AssembliesAcceptingLoadExceptions];
return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s)
? Array.Empty<string>()
: s.Split(',').Select(x => x.Trim()).ToArray();
return _rootDir;
}
var codeBase = Assembly.GetExecutingAssembly().CodeBase;
var uri = new Uri(codeBase);
var path = uri.LocalPath;
var baseDirectory = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(baseDirectory))
throw new PanicException("No root directory could be resolved.");
_rootDir = baseDirectory.Contains("bin")
? baseDirectory.Substring(0, baseDirectory.LastIndexOf("bin", StringComparison.OrdinalIgnoreCase) - 1)
: baseDirectory;
return _rootDir;
}
private bool AcceptsLoadExceptions(Assembly a)
{
if (AssembliesAcceptingLoadExceptions.Length == 0)
if (_assembliesAcceptingLoadExceptions.Length == 0)
return false;
if (AssembliesAcceptingLoadExceptions.Length == 1 && AssembliesAcceptingLoadExceptions[0] == "*")
if (_assembliesAcceptingLoadExceptions.Length == 1 && _assembliesAcceptingLoadExceptions[0] == "*")
return true;
var name = a.GetName().Name; // simple name of the assembly
return AssembliesAcceptingLoadExceptions.Any(pattern =>
return _assembliesAcceptingLoadExceptions.Any(pattern =>
{
if (pattern.Length > name.Length) return false; // pattern longer than name
if (pattern.Length == name.Length) return pattern.InvariantEquals(name); // same length, must be identical

View File

@@ -5,7 +5,6 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web.Compilation;
using Umbraco.Core.Cache;
using Umbraco.Core.Collections;
using Umbraco.Core.IO;
@@ -50,8 +49,9 @@ namespace Umbraco.Core.Composing
/// <param name="runtimeCache">The application runtime cache.</param>
/// <param name="localTempPath">Files storage location.</param>
/// <param name="logger">A profiling logger.</param>
public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger)
: this(typeFinder, runtimeCache, localTempPath, logger, true)
/// <param name="assembliesToScan"></param>
public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, IEnumerable<Assembly> assembliesToScan = null)
: this(typeFinder, runtimeCache, localTempPath, logger, true, assembliesToScan)
{ }
/// <summary>
@@ -62,12 +62,14 @@ namespace Umbraco.Core.Composing
/// <param name="localTempPath">Files storage location.</param>
/// <param name="logger">A profiling logger.</param>
/// <param name="detectChanges">Whether to detect changes using hashes.</param>
internal TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges)
/// <param name="assembliesToScan"></param>
public TypeLoader(ITypeFinder typeFinder, IAppPolicyCache runtimeCache, string localTempPath, IProfilingLogger logger, bool detectChanges, IEnumerable<Assembly> assembliesToScan = null)
{
TypeFinder = typeFinder ?? throw new ArgumentNullException(nameof(typeFinder));
_runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache));
_localTempPath = localTempPath;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_assemblies = assembliesToScan;
if (detectChanges)
{
@@ -99,13 +101,6 @@ namespace Umbraco.Core.Composing
}
}
/// <summary>
/// Initializes a new, test/blank, instance of the <see cref="TypeLoader"/> class.
/// </summary>
/// <remarks>The initialized instance cannot get types.</remarks>
internal TypeLoader()
{ }
/// <summary>
/// Returns the underlying <see cref="ITypeFinder"/>
/// </summary>
@@ -122,7 +117,7 @@ namespace Umbraco.Core.Composing
/// <para>This is for unit tests.</para>
/// </remarks>
// internal for tests
internal IEnumerable<Assembly> AssembliesToScan
protected IEnumerable<Assembly> AssembliesToScan
{
get => _assemblies ?? (_assemblies = TypeFinder.AssembliesToScan);
set => _assemblies = value;

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface ITypeFinderConfig
{
IEnumerable<string> AssembliesAcceptingLoadExceptions { get; }
}
}

View File

@@ -58,11 +58,18 @@ namespace Umbraco.Core.Runtime
// loggers
var logger = Logger = GetLogger();
if (logger == null)
throw new InvalidOperationException($"The object returned from {nameof(GetLogger)} cannot be null");
var profiler = Profiler = GetProfiler();
if (profiler == null)
throw new InvalidOperationException($"The object returned from {nameof(GetProfiler)} cannot be null");
var profilingLogger = ProfilingLogger = new ProfilingLogger(logger, profiler);
// type finder
TypeFinder = GetTypeFinder();
if (TypeFinder == null)
throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null");
// the boot loader boots using a container scope, so anything that is PerScope will
// be disposed after the boot loader has booted, and anything else will remain.

View File

@@ -177,8 +177,6 @@
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
<Compile Include="Composing\LightInject\MixedLightInjectScopeManagerProvider.cs" />
<Compile Include="Composing\TargetedServiceFactory.cs" />
<Compile Include="Composing\TypeFinder.cs" />
<Compile Include="Composing\TypeLoader.cs" />
<Compile Include="IO\IMediaFileSystem.cs" />
<Compile Include="IO\IMediaPathScheme.cs" />
<Compile Include="IO\IOHelper.cs" />

View File

@@ -52,7 +52,7 @@ namespace Umbraco.Tests.Components
private static TypeLoader MockTypeLoader()
{
return new TypeLoader();
return new TypeLoader(Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), IOHelper.MapPath("~/App_Data/TEMP"), Mock.Of<IProfilingLogger>());
}
public static IRuntimeState MockRuntimeState(RuntimeLevel level)

View File

@@ -23,10 +23,7 @@ namespace Umbraco.Tests.Composing
ProfilingLogger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, detectChanges: false)
{
AssembliesToScan = AssembliesToScan
};
TypeLoader = new TypeLoader(typeFinder, NoAppCache.Instance, IOHelper.MapPath("~/App_Data/TEMP"), ProfilingLogger, false, AssembliesToScan);
}
[TearDown]

View File

@@ -292,15 +292,12 @@ namespace Umbraco.Tests.Testing
private static TypeLoader CreateCommonTypeLoader(IAppPolicyCache runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger)
{
var typeFinder = new TypeFinder(Mock.Of<ILogger>());
return new TypeLoader(typeFinder, runtimeCache, globalSettings.LocalTempPath, logger, false)
return new TypeLoader(typeFinder, runtimeCache, globalSettings.LocalTempPath, logger, false, new[]
{
AssembliesToScan = new[]
{
Assembly.Load("Umbraco.Core"),
Assembly.Load("Umbraco.Web"),
Assembly.Load("Umbraco.Tests")
}
};
Assembly.Load("Umbraco.Core"),
Assembly.Load("Umbraco.Web"),
Assembly.Load("Umbraco.Tests")
});
}
protected virtual void ComposeDatabase(UmbracoTestOptions.Database option)

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using System.Web.Compilation;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Web.Composing
{
/// <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(ILogger logger, ITypeFinderConfig typeFinderConfig = null) : base(logger, typeFinderConfig)
{
_allAssemblies = new Lazy<HashSet<Assembly>>(() =>
{
var isHosted = IOHelper.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 IEnumerable<string> _assembliesAcceptingLoadExceptions;
public IEnumerable<string> AssembliesAcceptingLoadExceptions
{
get
{
if (_assembliesAcceptingLoadExceptions != null)
return _assembliesAcceptingLoadExceptions;
var s = ConfigurationManager.AppSettings[Constants.AppSettings.AssembliesAcceptingLoadExceptions];
return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s)
? Array.Empty<string>()
: s.Split(',').Select(x => x.Trim()).ToArray();
}
}
}
}
}

View File

@@ -5,6 +5,7 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Runtime;
using Umbraco.Web.Cache;
using Umbraco.Web.Composing;
using Umbraco.Web.Logging;
namespace Umbraco.Web.Runtime
@@ -17,6 +18,7 @@ namespace Umbraco.Web.Runtime
{
private readonly UmbracoApplicationBase _umbracoApplication;
private IProfiler _webProfiler;
private BuildManagerTypeFinder _typeFinder;
/// <summary>
/// Initializes a new instance of the <see cref="WebRuntime"/> class.
@@ -57,6 +59,8 @@ namespace Umbraco.Web.Runtime
#region Getters
protected override ITypeFinder GetTypeFinder() => _typeFinder ??= new BuildManagerTypeFinder(Logger, new BuildManagerTypeFinder.TypeFinderConfig());
protected override IProfiler GetProfiler() => _webProfiler;
protected override AppCaches GetAppCaches() => new AppCaches(

View File

@@ -135,6 +135,7 @@
<Compile Include="Compose\BackOfficeUserAuditEventsComposer.cs" />
<Compile Include="Compose\NotificationsComposer.cs" />
<Compile Include="Compose\PublicAccessComposer.cs" />
<Compile Include="Composing\BuildManagerTypeFinder.cs" />
<Compile Include="Composing\CompositionExtensions\Installer.cs" />
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
<Compile Include="Compose\BackOfficeUserAuditEventsComponent.cs" />