Moving TypeHelper ... but now need IOHelper...
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
105
src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs
Normal file
105
src/Umbraco.Web/Composing/BuildManagerTypeFinder.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in New Issue
Block a user