diff --git a/src/Umbraco.Core/Deploy/IServiceConnector.cs b/src/Umbraco.Core/Deploy/IServiceConnector.cs
index d1c0bf61de..65553b991d 100644
--- a/src/Umbraco.Core/Deploy/IServiceConnector.cs
+++ b/src/Umbraco.Core/Deploy/IServiceConnector.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using umbraco.interfaces;
using Umbraco.Core;
namespace Umbraco.Core.Deploy
@@ -8,7 +9,7 @@ namespace Umbraco.Core.Deploy
///
/// Connects to an Umbraco service.
///
- public interface IServiceConnector
+ public interface IServiceConnector : IDiscoverable
{
///
/// Gets an artifact.
diff --git a/src/Umbraco.Core/Media/IImageUrlProvider.cs b/src/Umbraco.Core/Media/IImageUrlProvider.cs
index 3854e1f1ec..29c0ae34ed 100644
--- a/src/Umbraco.Core/Media/IImageUrlProvider.cs
+++ b/src/Umbraco.Core/Media/IImageUrlProvider.cs
@@ -1,8 +1,14 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
namespace Umbraco.Core.Media
{
- public interface IImageUrlProvider
+ // note: because this interface is obsolete is is *not* IDiscoverable, and in case the
+ // PluginManager is asked to find types implementing this interface it will fall back
+ // to a complete scan.
+
+ [Obsolete("IImageUrlProvider is no longer used and will be removed in future versions")]
+ public interface IImageUrlProvider // IDiscoverable
{
string Name { get; }
string GetImageUrlFromMedia(int mediaId, IDictionary parameters);
diff --git a/src/Umbraco.Core/Media/IThumbnailProvider.cs b/src/Umbraco.Core/Media/IThumbnailProvider.cs
index a5be69b72e..18b8453324 100644
--- a/src/Umbraco.Core/Media/IThumbnailProvider.cs
+++ b/src/Umbraco.Core/Media/IThumbnailProvider.cs
@@ -1,6 +1,13 @@
-namespace Umbraco.Core.Media
+using System;
+
+namespace Umbraco.Core.Media
{
- public interface IThumbnailProvider
+ // note: because this interface is obsolete is is *not* IDiscoverable, and in case the
+ // PluginManager is asked to find types implementing this interface it will fall back
+ // to a complete scan.
+
+ [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")]
+ public interface IThumbnailProvider // : IDiscoverable
{
bool CanProvideThumbnail(string fileUrl);
string GetThumbnailUrl(string fileUrl);
diff --git a/src/Umbraco.Core/ObjectResolution/Resolution.cs b/src/Umbraco.Core/ObjectResolution/Resolution.cs
index 0b478d54cf..3cdaab365b 100644
--- a/src/Umbraco.Core/ObjectResolution/Resolution.cs
+++ b/src/Umbraco.Core/ObjectResolution/Resolution.cs
@@ -63,49 +63,6 @@ namespace Umbraco.Core.ObjectResolution
}
}
- // NOTE - the ugly code below exists only because of umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers
- // which wants to re-register actions and handlers instead of properly restarting the application. Don't even think
- // about using it for anything else. Also, while the backdoor is open, the resolution system is locked so nothing
- // can work properly => deadlocks. Therefore, open the backdoor, do resolution changes EXCLUSIVELY, and close the door!
-
- ///
- /// Returns a disposable object that reprents dirty access to temporarily unfrozen resolution configuration.
- ///
- ///
- /// Should not be used.
- /// Should be used in a using(Resolution.DirtyBackdoorToConfiguration) { ... } mode.
- /// Because we just lift the frozen state, and we don't actually re-freeze, the Frozen event does not trigger.
- ///
- internal static IDisposable DirtyBackdoorToConfiguration
- {
- get { return new DirtyBackdoor(); }
- }
-
- // keep the class here because it needs write-access to Resolution.IsFrozen
- private class DirtyBackdoor : IDisposable
- {
-
- private readonly IDisposable _lock;
- private readonly bool _frozen;
-
- public DirtyBackdoor()
- {
- LogHelper.Debug(typeof(DirtyBackdoor), "Creating back door for resolution");
-
- _lock = new WriteLock(ConfigurationLock);
- _frozen = _isFrozen;
- _isFrozen = false;
- }
-
- public void Dispose()
- {
- LogHelper.Debug(typeof(DirtyBackdoor), "Disposing back door for resolution");
-
- _isFrozen = _frozen;
- _lock.Dispose();
- }
- }
-
///
/// Freezes resolution.
///
diff --git a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs
index 40ec415b30..99f9cebf6b 100644
--- a/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs
+++ b/src/Umbraco.Core/Persistence/Mappers/BaseMapper.cs
@@ -2,11 +2,12 @@
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
+using umbraco.interfaces;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Mappers
{
- public abstract class BaseMapper
+ public abstract class BaseMapper : IDiscoverable
{
private readonly ISqlSyntaxProvider _sqlSyntax;
diff --git a/src/Umbraco.Core/Persistence/Migrations/IMigration.cs b/src/Umbraco.Core/Persistence/Migrations/IMigration.cs
index 2769400e44..8d0adcf4bc 100644
--- a/src/Umbraco.Core/Persistence/Migrations/IMigration.cs
+++ b/src/Umbraco.Core/Persistence/Migrations/IMigration.cs
@@ -1,9 +1,11 @@
-namespace Umbraco.Core.Persistence.Migrations
+using umbraco.interfaces;
+
+namespace Umbraco.Core.Persistence.Migrations
{
///
/// Marker interface for database migrations
///
- public interface IMigration
+ public interface IMigration : IDiscoverable
{
void Up();
void Down();
diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs
index af4a597ea3..3eb0f2a018 100644
--- a/src/Umbraco.Core/PluginManager.cs
+++ b/src/Umbraco.Core/PluginManager.cs
@@ -7,45 +7,55 @@ using System.Reflection;
using System.Text;
using System.Threading;
using System.Web.Compilation;
-using System.Xml.Linq;
-using Umbraco.Core.Configuration;
+using Umbraco.Core.Cache;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
-using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Mappers;
-using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Profiling;
using Umbraco.Core.PropertyEditors;
-using Umbraco.Core.Cache;
using umbraco.interfaces;
using File = System.IO.File;
namespace Umbraco.Core
{
///
- /// Used to resolve all plugin types and cache them and is also used to instantiate plugin types
+ /// Provides methods to find and instanciate types.
///
///
- ///
- /// This class should be used to resolve all plugin types, the TypeFinder should not be used directly!
- ///
- /// This class can expose extension methods to resolve custom plugins
- ///
- /// Before this class resolves any plugins it checks if the hash has changed for the DLLs in the /bin folder, if it hasn't
- /// it will use the cached resolved plugins that it has already found which means that no assembly scanning is necessary. This leads
- /// to much faster startup times.
+ /// This class should be used to resolve all types, the class should never be used directly.
+ /// In most cases this class is not used directly but through extension methods that retrieve specific types.
+ /// This class caches the types it knows to avoid excessive assembly scanning and shorten startup times, relying
+ /// on a hash of the DLLs in the ~/bin folder to check for cache expiration.
///
public class PluginManager
{
+ private const string CacheKey = "umbraco-plugins.list";
+
+ private static PluginManager _current;
+ private static bool _hasCurrent;
+ private static object _currentLock = new object();
+
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IRuntimeCacheProvider _runtimeCache;
+ private readonly ProfilingLogger _logger;
+ private readonly string _tempFolder;
+
+ private readonly object _typesLock = new object();
+ private readonly Dictionary _types = new Dictionary();
+
+ private long _cachedAssembliesHash = -1;
+ private long _currentAssembliesHash = -1;
+ private IEnumerable _assemblies;
+ private bool _reportedChange;
+
///
- /// Creates a new PluginManager with an ApplicationContext instance which ensures that the plugin xml
- /// file is cached temporarily until app startup completes.
+ /// Initializes a new instance of the class.
///
- ///
- ///
- ///
- ///
+ /// A mechanism for retrieving service objects.
+ /// The application runtime cache.
+ /// A profiling logger.
+ /// Whether to detect changes using hashes.
internal PluginManager(IServiceProvider serviceProvider, IRuntimeCacheProvider runtimeCache, ProfilingLogger logger, bool detectChanges = true)
{
if (serviceProvider == null) throw new ArgumentNullException("serviceProvider");
@@ -56,22 +66,13 @@ namespace Umbraco.Core
_runtimeCache = runtimeCache;
_logger = logger;
+ // the temp folder where the cache file lives
_tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
- //create the folder if it doesn't exist
if (Directory.Exists(_tempFolder) == false)
- {
Directory.CreateDirectory(_tempFolder);
- }
var pluginListFile = GetPluginListFilePath();
- //this is a check for legacy changes, before we didn't store the TypeResolutionKind in the file which was a mistake,
- //so we need to detect if the old file is there without this attribute, if it is then we delete it
- if (DetectLegacyPluginListFile())
- {
- File.Delete(pluginListFile);
- }
-
if (detectChanges)
{
//first check if the cached hash is 0, if it is then we ne
@@ -80,7 +81,7 @@ namespace Umbraco.Core
//if they have changed, we need to write the new file
if (RequiresRescanning)
{
- //if the hash has changed, clear out the persisted list no matter what, this will force
+ // if the hash has changed, clear out the persisted list no matter what, this will force
// rescanning of all plugin types including lazy ones.
// http://issues.umbraco.org/issue/U4-4789
File.Delete(pluginListFile);
@@ -90,75 +91,96 @@ namespace Umbraco.Core
}
else
{
-
- //if the hash has changed, clear out the persisted list no matter what, this will force
+ // if the hash has changed, clear out the persisted list no matter what, this will force
// rescanning of all plugin types including lazy ones.
// http://issues.umbraco.org/issue/U4-4789
File.Delete(pluginListFile);
- //always set to true if we're not detecting (generally only for testing)
+ // always set to true if we're not detecting (generally only for testing)
RequiresRescanning = true;
}
}
- private readonly IServiceProvider _serviceProvider;
- private readonly IRuntimeCacheProvider _runtimeCache;
- private readonly ProfilingLogger _logger;
- private const string CacheKey = "umbraco-plugins.list";
- static PluginManager _resolver;
- private readonly string _tempFolder;
- private long _cachedAssembliesHash = -1;
- private long _currentAssembliesHash = -1;
- private static bool _initialized = false;
- private static object _singletonLock = new object();
-
///
- /// We will ensure that no matter what, only one of these is created, this is to ensure that caching always takes place
+ /// Gets or sets the set of assemblies to scan.
///
///
- /// The setter is generally only used for unit tests
+ /// If not explicitely set, defaults to all assemblies except those that are know to not have any of the
+ /// types we might scan. Because we only scan for application types, this means we can safely exclude GAC assemblies
+ /// for example.
+ /// This is for unit tests.
///
+ internal IEnumerable AssembliesToScan
+ {
+ get { return _assemblies ?? (_assemblies = TypeFinder.GetAssembliesWithKnownExclusions()); }
+ set { _assemblies = value; }
+ }
+
+ ///
+ /// Gets the type lists.
+ ///
+ /// For unit tests.
+ internal IEnumerable TypeLists
+ {
+ get { return _types.Values; }
+ }
+
+ ///
+ /// Sets a type list.
+ ///
+ /// For unit tests.
+ internal void AddTypeList(TypeList typeList)
+ {
+ _types[new TypeListKey(typeList.BaseType, typeList.AttributeType)] = typeList;
+ }
+
+ ///
+ /// Gets or sets the singleton instance.
+ ///
+ /// The setter exists for unit tests.
public static PluginManager Current
{
get
{
- return LazyInitializer.EnsureInitialized(ref _resolver, ref _initialized, ref _singletonLock, () =>
+ return LazyInitializer.EnsureInitialized(ref _current, ref _hasCurrent, ref _currentLock, () =>
{
+ IRuntimeCacheProvider runtimeCache;
+ ProfilingLogger profilingLogger;
+
if (ApplicationContext.Current == null)
{
+ runtimeCache = new NullCacheProvider();
var logger = LoggerResolver.HasCurrent ? LoggerResolver.Current.Logger : new DebugDiagnosticsLogger();
var profiler = ProfilerResolver.HasCurrent ? ProfilerResolver.Current.Profiler : new LogProfiler(logger);
- return new PluginManager(
- new ActivatorServiceProvider(),
- new NullCacheProvider(),
- new ProfilingLogger(logger, profiler));
+ profilingLogger = new ProfilingLogger(logger, profiler);
}
- return new PluginManager(
- new ActivatorServiceProvider(),
- ApplicationContext.Current.ApplicationCache.RuntimeCache,
- ApplicationContext.Current.ProfilingLogger);
+ else
+ {
+ runtimeCache = ApplicationContext.Current.ApplicationCache.RuntimeCache;
+ profilingLogger = ApplicationContext.Current.ProfilingLogger;
+ }
+
+ return new PluginManager(new ActivatorServiceProvider(), runtimeCache, profilingLogger);
});
}
set
{
- _initialized = true;
- _resolver = value;
+ _hasCurrent = true;
+ _current = value;
}
}
- #region Hash checking methods
-
+ #region Hashing
///
- /// Returns a bool if the assemblies in the /bin, app_code, global.asax, etc... have changed since they were last hashed.
+ /// Gets a value indicating whether the assemblies in bin, app_code, global.asax, etc... have changed since they were last hashed.
///
internal bool RequiresRescanning { get; private set; }
///
- /// Returns the currently cached hash value of the scanned assemblies in the /bin folder. Returns 0
- /// if no cache is found.
+ /// Gets the currently cached hash value of the scanned assemblies.
///
- ///
+ /// The cached hash value, or 0 if no cache is found.
internal long CachedAssembliesHash
{
get
@@ -167,24 +189,22 @@ namespace Umbraco.Core
return _cachedAssembliesHash;
var filePath = GetPluginHashFilePath();
- if (!File.Exists(filePath))
- return 0;
+ if (File.Exists(filePath) == false) return 0;
+
var hash = File.ReadAllText(filePath, Encoding.UTF8);
- Int64 val;
- if (Int64.TryParse(hash, out val))
- {
- _cachedAssembliesHash = val;
- return _cachedAssembliesHash;
- }
- //it could not parse for some reason so we'll return 0.
- return 0;
+
+ long val;
+ if (long.TryParse(hash, out val) == false) return 0;
+
+ _cachedAssembliesHash = val;
+ return _cachedAssembliesHash;
}
}
///
- /// Returns the current assemblies hash based on creating a hash from the assemblies in the /bin
+ /// Gets the current assemblies hash based on creating a hash from the assemblies in various places.
///
- ///
+ /// The current hash.
internal long CurrentAssembliesHash
{
get
@@ -192,26 +212,24 @@ namespace Umbraco.Core
if (_currentAssembliesHash != -1)
return _currentAssembliesHash;
- _currentAssembliesHash = GetFileHash(
- new List>
- {
- //add the bin folder and everything in it
- new Tuple(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)), false),
- //add the app code folder and everything in it
- new Tuple(new DirectoryInfo(IOHelper.MapPath("~/App_Code")), false),
- //add the global.asax (the app domain also monitors this, if it changes will do a full restart)
- new Tuple(new FileInfo(IOHelper.MapPath("~/global.asax")), false),
+ _currentAssembliesHash = GetFileHash(new List>
+ {
+ // the bin folder and everything in it
+ new Tuple(new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Bin)), false),
+ // the app code folder and everything in it
+ new Tuple(new DirectoryInfo(IOHelper.MapPath("~/App_Code")), false),
+ // global.asax (the app domain also monitors this, if it changes will do a full restart)
+ new Tuple(new FileInfo(IOHelper.MapPath("~/global.asax")), false),
+ // trees.config - use the contents to create the hash since this gets resaved on every app startup!
+ new Tuple(new FileInfo(IOHelper.MapPath(SystemDirectories.Config + "/trees.config")), true)
+ }, _logger);
- //add the trees.config - use the contents to create the has since this gets resaved on every app startup!
- new Tuple(new FileInfo(IOHelper.MapPath(SystemDirectories.Config + "/trees.config")), true)
- }, _logger
- );
return _currentAssembliesHash;
}
}
///
- /// Writes the assembly hash file
+ /// Writes the assembly hash file.
///
private void WriteCachePluginsHash()
{
@@ -220,668 +238,725 @@ namespace Umbraco.Core
}
///
- /// Returns a unique hash for the combination of FileInfo objects passed in
+ /// Returns a unique hash for a combination of FileInfo objects.
///
- ///
- /// A collection of files and whether or not to use their file contents to determine the hash or the file's properties
- /// (true will make a hash based on it's contents)
- ///
- ///
+ /// A collection of files.
+ /// A profiling logger.
+ /// The hash.
+ /// Each file is a tuple containing the FileInfo object and a boolean which indicates whether to hash the
+ /// file properties (false) or the file contents (true).
internal static long GetFileHash(IEnumerable> filesAndFolders, ProfilingLogger logger)
{
using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined"))
{
var hashCombiner = new HashCodeCombiner();
- //get the file info's to check
- var fileInfos = filesAndFolders.Where(x => x.Item2 == false).ToArray();
- var fileContents = filesAndFolders.Except(fileInfos);
-
- //add each unique folder/file to the hash
- foreach (var i in fileInfos.Select(x => x.Item1).DistinctBy(x => x.FullName))
- {
- hashCombiner.AddFileSystemItem(i);
- }
+ // get the distinct file infos to hash
+ var uniqInfos = new HashSet();
+ var uniqContent = new HashSet();
- //add each unique file's contents to the hash
- foreach (var i in fileContents.Select(x => x.Item1).DistinctBy(x => x.FullName))
+ foreach (var fileOrFolder in filesAndFolders)
{
- if (File.Exists(i.FullName))
+ var info = fileOrFolder.Item1;
+ if (fileOrFolder.Item2)
{
- var content = File.ReadAllText(i.FullName).Replace("\r\n", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty);
- hashCombiner.AddCaseInsensitiveString(content);
- }
-
- }
+ // add each unique file's contents to the hash
+ // normalize the content for cr/lf and case-sensitivity
- return ConvertPluginsHashFromHex(hashCombiner.GetCombinedHashCode());
+ if (uniqContent.Contains(info.FullName)) continue;
+ uniqContent.Add(info.FullName);
+ if (File.Exists(info.FullName) == false) continue;
+ var content = RemoveCrLf(File.ReadAllText(info.FullName));
+ hashCombiner.AddCaseInsensitiveString(content);
+ }
+ else
+ {
+ // add each unique folder/file to the hash
+
+ if (uniqInfos.Contains(info.FullName)) continue;
+ uniqInfos.Add(info.FullName);
+ hashCombiner.AddFileSystemItem(info);
+ }
+ }
+
+ return ConvertHashToInt64(hashCombiner.GetCombinedHashCode());
}
}
+ // fast! (yes, according to benchmarks)
+ private static string RemoveCrLf(string s)
+ {
+ var buffer = new char[s.Length];
+ var count = 0;
+ // ReSharper disable once ForCanBeConvertedToForeach - no!
+ for (var i = 0; i < s.Length; i++)
+ {
+ if (s[i] != '\r' && s[i] != '\n')
+ buffer[count++] = s[i];
+ }
+ return new string(buffer, 0, count);
+ }
+
+ ///
+ /// Returns a unique hash for a combination of FileInfo objects.
+ ///
+ /// A collection of files.
+ /// A profiling logger.
+ /// The hash.
internal static long GetFileHash(IEnumerable filesAndFolders, ProfilingLogger logger)
{
using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined"))
{
var hashCombiner = new HashCodeCombiner();
- //add each unique folder/file to the hash
- foreach (var i in filesAndFolders.DistinctBy(x => x.FullName))
+ // get the distinct file infos to hash
+ var uniqInfos = new HashSet();
+
+ foreach (var fileOrFolder in filesAndFolders)
{
- hashCombiner.AddFileSystemItem(i);
+ if (uniqInfos.Contains(fileOrFolder.FullName)) continue;
+ uniqInfos.Add(fileOrFolder.FullName);
+ hashCombiner.AddFileSystemItem(fileOrFolder);
}
- return ConvertPluginsHashFromHex(hashCombiner.GetCombinedHashCode());
+
+ return ConvertHashToInt64(hashCombiner.GetCombinedHashCode());
}
}
///
- /// Converts the hash value of current plugins to long from string
+ /// Converts a string hash value into an Int64.
///
- ///
- ///
- internal static long ConvertPluginsHashFromHex(string val)
+ internal static long ConvertHashToInt64(string val)
{
long outVal;
- if (Int64.TryParse(val, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out outVal))
- {
- return outVal;
- }
- return 0;
+ return long.TryParse(val, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out outVal) ? outVal : 0;
}
- ///
- /// Attempts to resolve the list of plugin + assemblies found in the runtime for the base type 'T' passed in.
- /// If the cache file doesn't exist, fails to load, is corrupt or the type 'T' element is not found then
- /// a false attempt is returned.
- ///
- ///
- ///
- internal Attempt> TryGetCachedPluginsFromFile(TypeResolutionKind resolutionType)
- {
- var filePath = GetPluginListFilePath();
- if (!File.Exists(filePath))
- return Attempt>.Fail();
+ #endregion
+ #region Cache
+
+ ///
+ /// Attemps to retrieve the list of types from the cache.
+ ///
+ /// Fails if the cache is missing or corrupt in any way.
+ internal Attempt> TryGetCached(Type baseType, Type attributeType)
+ {
+ var cache = _runtimeCache.GetCacheItem, IEnumerable>>(CacheKey, ReadCacheSafe, TimeSpan.FromMinutes(4));
+
+ IEnumerable types;
+ cache.TryGetValue(Tuple.Create(baseType == null ? string.Empty : baseType.FullName, attributeType == null ? string.Empty : attributeType.FullName), out types);
+ return types == null
+ ? Attempt>.Fail()
+ : Attempt.Succeed(types);
+ }
+
+ internal Dictionary, IEnumerable> ReadCacheSafe()
+ {
try
{
- //we will load the xml document, if the app context exist, we will load it from the cache (which is only around for 5 minutes)
- //while the app boots up, this should save some IO time on app startup when the app context is there (which is always unless in unit tests)
- var xml = _runtimeCache.GetCacheItem(CacheKey,
- () => XDocument.Load(filePath),
- new TimeSpan(0, 0, 5, 0));
-
- if (xml.Root == null)
- return Attempt>.Fail();
-
- var typeElement = xml.Root.Elements()
- .SingleOrDefault(x =>
- x.Name.LocalName == "baseType"
- && ((string)x.Attribute("type")) == typeof(T).FullName
- && ((string)x.Attribute("resolutionType")) == resolutionType.ToString());
-
- //return false but specify this exception type so we can detect it
- if (typeElement == null)
- return Attempt>.Fail(new CachedPluginNotFoundInFileException());
-
- //return success
- return Attempt.Succeed(typeElement.Elements("add")
- .Select(x => (string)x.Attribute("type")));
+ return ReadCache();
}
- catch (Exception ex)
+ catch
{
- //if the file is corrupted, etc... return false
- return Attempt>.Fail(ex);
+ try
+ {
+ var filePath = GetPluginListFilePath();
+ File.Delete(filePath);
+ }
+ catch
+ {
+ // on-purpose, does not matter
+ }
}
+
+ return new Dictionary, IEnumerable>();
+ }
+
+ internal Dictionary, IEnumerable> ReadCache()
+ {
+ var cache = new Dictionary, IEnumerable>();
+
+ var filePath = GetPluginListFilePath();
+ if (File.Exists(filePath) == false)
+ return cache;
+
+ using (var stream = File.OpenRead(filePath))
+ using (var reader = new StreamReader(stream))
+ {
+ while (true)
+ {
+ var baseType = reader.ReadLine();
+ if (baseType == null) return cache; // exit
+ if (baseType.StartsWith("<")) break; // old xml
+
+ var attributeType = reader.ReadLine();
+ if (attributeType == null) break;
+
+ var types = new List();
+ while (true)
+ {
+ var type = reader.ReadLine();
+ if (type == null)
+ {
+ types = null; // break 2 levels
+ break;
+ }
+ if (type == string.Empty)
+ {
+ cache[Tuple.Create(baseType, attributeType)] = types;
+ break;
+ }
+ types.Add(type);
+ }
+
+ if (types == null) break;
+ }
+ }
+
+ cache.Clear();
+ return cache;
}
///
- /// Removes cache files and internal cache as well
+ /// Removes cache files and internal cache.
///
- ///
- /// Generally only used for resetting cache, for example during the install process
- ///
+ /// Generally only used for resetting cache, for example during the install process.
public void ClearPluginCache()
{
var path = GetPluginListFilePath();
if (File.Exists(path))
File.Delete(path);
+
path = GetPluginHashFilePath();
if (File.Exists(path))
File.Delete(path);
_runtimeCache.ClearCacheItem(CacheKey);
}
-
+
private string GetPluginListFilePath()
{
- return Path.Combine(_tempFolder, string.Format("umbraco-plugins.{0}.list", NetworkHelper.FileSafeMachineName));
+ var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".list";
+ return Path.Combine(_tempFolder, filename);
}
private string GetPluginHashFilePath()
{
- return Path.Combine(_tempFolder, string.Format("umbraco-plugins.{0}.hash", NetworkHelper.FileSafeMachineName));
+ var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".hash";
+ return Path.Combine(_tempFolder, filename);
}
- ///
- /// This will return true if the plugin list file is a legacy one
- ///
- ///
- ///
- /// This method exists purely due to an error in 4.11. We were writing the plugin list file without the
- /// type resolution kind which will have caused some problems. Now we detect this legacy file and if it is detected
- /// we remove it so it can be recreated properly.
- ///
- internal bool DetectLegacyPluginListFile()
+ internal void WriteCache()
{
var filePath = GetPluginListFilePath();
- if (!File.Exists(filePath))
- return false;
- try
+ using (var stream = File.Open(filePath, FileMode.Create, FileAccess.ReadWrite))
+ using (var writer = new StreamWriter(stream))
{
- var xml = XDocument.Load(filePath);
- if (xml.Root == null)
- return false;
-
- var typeElement = xml.Root.Elements()
- .FirstOrDefault(x => x.Name.LocalName == "baseType");
-
- if (typeElement == null)
- return false;
-
- //now check if the typeElement is missing the resolutionType attribute
- return typeElement.Attributes().All(x => x.Name.LocalName != "resolutionType");
- }
- catch (Exception)
- {
- //if the file is corrupted, etc... return true so it is removed
- return true;
+ foreach (var typeList in _types.Values)
+ {
+ writer.WriteLine(typeList.BaseType == null ? string.Empty : typeList.BaseType.FullName);
+ writer.WriteLine(typeList.AttributeType == null ? string.Empty : typeList.AttributeType.FullName);
+ foreach (var type in typeList.Types)
+ writer.WriteLine(type.AssemblyQualifiedName);
+ writer.WriteLine();
+ }
}
}
- ///
- /// Adds/Updates the type list for the base type 'T' in the cached file
- ///
- ///
- ///
- ///
- ///
- /// THIS METHOD IS NOT THREAD SAFE
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- /// ]]>
- ///
- internal void UpdateCachedPluginsFile(IEnumerable typesFound, TypeResolutionKind resolutionType)
+ internal void UpdateCache()
{
- var filePath = GetPluginListFilePath();
- XDocument xml = null;
-
- if (File.Exists(filePath))
- {
- try
- {
- xml = XDocument.Load(filePath);
- }
- catch
- {
- try
- {
- File.Delete(filePath); // file is corrupt somehow
- }
- catch
- {
- // on-purpose, does not matter
- }
- xml = null;
- }
- }
-
- // else replace the xml, create the document and the root
- if (xml == null) xml = new XDocument(new XElement("plugins"));
-
- if (xml.Root == null)
- {
- //if for some reason there is no root, create it
- xml.Add(new XElement("plugins"));
- }
- //find the type 'T' element to add or update
- var typeElement = xml.Root.Elements()
- .SingleOrDefault(x =>
- x.Name.LocalName == "baseType"
- && ((string)x.Attribute("type")) == typeof(T).FullName
- && ((string)x.Attribute("resolutionType")) == resolutionType.ToString());
-
- if (typeElement == null)
- {
- //create the type element
- typeElement = new XElement("baseType",
- new XAttribute("type", typeof(T).FullName),
- new XAttribute("resolutionType", resolutionType.ToString()));
- //then add it to the root
- xml.Root.Add(typeElement);
- }
-
-
- //now we have the type element, we need to clear any previous types as children and add/update it with new ones
- typeElement.ReplaceNodes(typesFound.Select(x => new XElement("add", new XAttribute("type", x.AssemblyQualifiedName))));
- //save the xml file
- var dir = Path.GetDirectoryName(filePath);
- if (Directory.Exists(dir) == false)
- Directory.CreateDirectory(dir);
- xml.Save(filePath);
+ // note
+ // at the moment we write the cache to disk every time we update it. ideally we defer the writing
+ // since all the updates are going to happen in a row when Umbraco starts. that being said, the
+ // file is small enough, so it is not a priority.
+ WriteCache();
}
#endregion
- private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim();
- private readonly HashSet _types = new HashSet();
- private IEnumerable _assemblies;
+ #region Create Instances
///
- /// Returns all found property editors (based on the resolved Iparameter editors - this saves a scan)
+ /// Resolves and creates instances.
///
- internal IEnumerable ResolvePropertyEditors()
+ /// The type to use for resolution.
+ /// Indicates whether to throw if an instance cannot be created.
+ /// Indicates whether to use cache for type resolution.
+ /// A set of assemblies for type resolution.
+ /// The created instances.
+ ///
+ /// By default is false and instances that cannot be created are just skipped.
+ /// By default is true and cache is used for type resolution.
+ /// By default is null and is used.
+ /// Caching is disabled when using specific assemblies.
+ ///
+ internal IEnumerable FindAndCreateInstances(bool throwException = false, bool cache = true, IEnumerable specificAssemblies = null)
{
- //return all proeprty editor types found except for the base property editor type
- return ResolveTypes()
- .Where(x => x.IsType())
- .Except(new[] { typeof(PropertyEditor) });
- }
-
- ///
- /// Returns all found parameter editors (which includes property editors)
- ///
- internal IEnumerable ResolveParameterEditors()
- {
- //return all paramter editor types found except for the base property editor type
- return ResolveTypes()
- .Except(new[] { typeof(ParameterEditor), typeof(PropertyEditor) });
- }
-
- ///
- /// Returns all available IApplicationStartupHandler objects
- ///
- ///
- internal IEnumerable ResolveApplicationStartupHandlers()
- {
- return ResolveTypes();
- }
-
- ///
- /// Returns all classes of type ICacheRefresher
- ///
- ///
- internal IEnumerable ResolveCacheRefreshers()
- {
- return ResolveTypes();
- }
-
- ///
- /// Returns all available IPropertyEditorValueConverter
- ///
- ///
- internal IEnumerable ResolvePropertyEditorValueConverters()
- {
- return ResolveTypes();
- }
-
- ///
- /// Returns all available IDataType in application
- ///
- ///
- internal IEnumerable ResolveDataTypes()
- {
- return ResolveTypes();
- }
-
- ///
- /// Returns all available IMacroGuiRendering in application
- ///
- ///
- internal IEnumerable ResolveMacroRenderings()
- {
- return ResolveTypes();
- }
-
- ///
- /// Returns all available IPackageAction in application
- ///
- ///
- internal IEnumerable ResolvePackageActions()
- {
- return ResolveTypes();
- }
-
- ///
- /// Returns all available IAction in application
- ///
- ///
- internal IEnumerable ResolveActions()
- {
- return ResolveTypes();
- }
-
- ///
- /// Returns all mapper types that have a MapperFor attribute defined
- ///
- ///
- internal IEnumerable ResolveAssignedMapperTypes()
- {
- return ResolveTypesWithAttribute();
- }
-
- ///
- /// Returns all SqlSyntaxProviders with the SqlSyntaxProviderAttribute
- ///
- ///
- internal IEnumerable ResolveSqlSyntaxProviders()
- {
- return ResolveTypesWithAttribute();
- }
-
- ///
- /// Gets/sets which assemblies to scan when type finding, generally used for unit testing, if not explicitly set
- /// this will search all assemblies known to have plugins and exclude ones known to not have them.
- ///
- internal IEnumerable AssembliesToScan
- {
- get { return _assemblies ?? (_assemblies = TypeFinder.GetAssembliesWithKnownExclusions()); }
- set { _assemblies = value; }
- }
-
- ///
- /// Used to resolve and create instances of the specified type based on the resolved/cached plugin types
- ///
- ///
- /// set to true if an exception is to be thrown if there is an error during instantiation
- ///
- ///
- ///
- internal IEnumerable FindAndCreateInstances(bool throwException = false, bool cacheResult = true, IEnumerable specificAssemblies = null)
- {
- var types = ResolveTypes(cacheResult, specificAssemblies);
+ var types = ResolveTypes(cache, specificAssemblies);
return CreateInstances(types, throwException);
}
///
- /// Used to create instances of the specified type based on the resolved/cached plugin types
+ /// Creates instances of the specified types.
///
- ///
- ///
- /// set to true if an exception is to be thrown if there is an error during instantiation
- ///
+ /// The base type for all instances.
+ /// The instance types.
+ /// Indicates whether to throw if an instance cannot be created.
+ /// The created instances.
+ /// By default is false and instances that cannot be created are just skipped.
internal IEnumerable CreateInstances(IEnumerable types, bool throwException = false)
{
return _serviceProvider.CreateInstances(types, _logger.Logger, throwException);
}
///
- /// Used to create an instance of the specified type based on the resolved/cached plugin types
+ /// Creates an instance of the specified type.
///
- ///
- ///
+ /// The base type of the instance.
+ /// The type of the instance.
///
- ///
+ /// The created instance.
internal T CreateInstance(Type type, bool throwException = false)
{
var instances = CreateInstances(new[] { type }, throwException);
return instances.FirstOrDefault();
}
- private IEnumerable ResolveTypes(
- Func> finder,
- TypeResolutionKind resolutionType,
- bool cacheResult)
- {
- using (var readLock = new UpgradeableReadLock(Locker))
- {
- var typesFound = new List();
-
- using (_logger.TraceDuration(
- String.Format("Starting resolution types of {0}", typeof(T).FullName),
- String.Format("Completed resolution of types of {0}, found {1}", typeof(T).FullName, typesFound.Count)))
- {
- //check if the TypeList already exists, if so return it, if not we'll create it
- var typeList = _types.SingleOrDefault(x => x.IsTypeList(resolutionType));
-
- //need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505
- if (cacheResult && typeList != null)
- {
- _logger.Logger.Debug("Existing typeList found for {0} with resolution type {1}", () => typeof(T), () => resolutionType);
- }
-
- //if we're not caching the result then proceed, or if the type list doesn't exist then proceed
- if (cacheResult == false || typeList == null)
- {
- //upgrade to a write lock since we're adding to the collection
- readLock.UpgradeToWriteLock();
-
- typeList = new TypeList(resolutionType);
-
- //we first need to look into our cache file (this has nothing to do with the 'cacheResult' parameter which caches in memory).
- //if assemblies have not changed and the cache file actually exists, then proceed to try to lookup by the cache file.
- if (RequiresRescanning == false && File.Exists(GetPluginListFilePath()))
- {
- var fileCacheResult = TryGetCachedPluginsFromFile(resolutionType);
-
- //here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan
- //in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed
- //so in this instance there will never be a result.
- if (fileCacheResult.Exception != null && fileCacheResult.Exception is CachedPluginNotFoundInFileException)
- {
- _logger.Logger.Debug("Tried to find typelist for type {0} and resolution {1} in file cache but the type was not found so loading types by assembly scan ", () => typeof(T), () => resolutionType);
-
- //we don't have a cache for this so proceed to look them up by scanning
- LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder);
- }
- else
- {
- if (fileCacheResult.Success)
- {
- var successfullyLoadedFromCache = true;
- //we have a previous cache for this so we don't need to scan we just load what has been found in the file
- foreach (var t in fileCacheResult.Result)
- {
- try
- {
- //we use the build manager to ensure we get all types loaded, this is slightly slower than
- //Type.GetType but if the types in the assembly aren't loaded yet then we have problems with that.
- var type = BuildManager.GetType(t, true);
- typeList.AddType(type);
- }
- catch (Exception ex)
- {
- //if there are any exceptions loading types, we have to exist, this should never happen so
- //we will need to revert to scanning for types.
- successfullyLoadedFromCache = false;
- _logger.Logger.Error("Could not load a cached plugin type: " + t + " now reverting to re-scanning assemblies for the base type: " + typeof(T).FullName, ex);
- break;
- }
- }
- if (successfullyLoadedFromCache == false)
- {
- //we need to manually load by scanning if loading from the file was not successful.
- LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder);
- }
- else
- {
- _logger.Logger.Debug("Loaded plugin types {0} with resolution {1} from persisted cache", () => typeof(T), () => resolutionType);
- }
- }
- }
- }
- else
- {
- _logger.Logger.Debug("Assembly changes detected, loading types {0} for resolution {1} by assembly scan", () => typeof(T), () => resolutionType);
-
- //we don't have a cache for this so proceed to look them up by scanning
- LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder);
- }
-
- //only add the cache if we are to cache the results
- if (cacheResult)
- {
- //add the type list to the collection
- var added = _types.Add(typeList);
-
- _logger.Logger.Debug("Caching of typelist for type {0} and resolution {1} was successful = {2}", () => typeof(T), () => resolutionType, () => added);
-
- }
- }
- typesFound = typeList.GetTypes().ToList();
- }
-
- return typesFound;
- }
- }
-
- ///
- /// This method invokes the finder which scans the assemblies for the types and then loads the result into the type finder.
- /// Once the results are loaded, we update the cached type xml file
- ///
- ///
- ///
- ///
- ///
- /// THIS METHODS IS NOT THREAD SAFE
- ///
- private void LoadViaScanningAndUpdateCacheFile(TypeList typeList, TypeResolutionKind resolutionKind, Func> finder)
- {
- //we don't have a cache for this so proceed to look them up by scanning
- foreach (var t in finder())
- {
- typeList.AddType(t);
- }
- UpdateCachedPluginsFile(typeList.GetTypes(), resolutionKind);
- }
-
- #region Public Methods
- ///
- /// Generic method to find the specified type and cache the result
- ///
- ///
- ///
- public IEnumerable ResolveTypes(bool cacheResult = true, IEnumerable specificAssemblies = null)
- {
- return ResolveTypes(
- () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan),
- TypeResolutionKind.FindAllTypes,
- cacheResult);
- }
-
- ///
- /// Generic method to find the specified type that has an attribute and cache the result
- ///
- ///
- ///
- ///
- public IEnumerable ResolveTypesWithAttribute(bool cacheResult = true, IEnumerable specificAssemblies = null)
- where TAttribute : Attribute
- {
- return ResolveTypes(
- () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan),
- TypeResolutionKind.FindTypesWithAttribute,
- cacheResult);
- }
-
- ///
- /// Generic method to find any type that has the specified attribute
- ///
- ///
- ///
- public IEnumerable ResolveAttributedTypes(bool cacheResult = true, IEnumerable specificAssemblies = null)
- where TAttribute : Attribute
- {
- return ResolveTypes(
- () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan),
- TypeResolutionKind.FindAttributedTypes,
- cacheResult);
- }
#endregion
- ///
- /// Used for unit tests
- ///
- ///
- internal HashSet GetTypeLists()
- {
- return _types;
- }
-
-
-
- #region Private classes/Enums
+ #region Resolve Types
///
- /// The type of resolution being invoked
+ /// Resolves class types inheriting from or implementing the specified type
///
- internal enum TypeResolutionKind
+ /// The type to inherit from or implement.
+ /// Indicates whether to use cache for type resolution.
+ /// A set of assemblies for type resolution.
+ /// All class types inheriting from or implementing the specified type.
+ /// Caching is disabled when using specific assemblies.
+ public IEnumerable ResolveTypes(bool cache = true, IEnumerable specificAssemblies = null)
{
- FindAllTypes,
- FindAttributedTypes,
- FindTypesWithAttribute
- }
+ // do not cache anything from specific assemblies
+ cache &= specificAssemblies == null;
- internal abstract class TypeList
- {
- public abstract void AddType(Type t);
- public abstract bool IsTypeList(TypeResolutionKind resolutionType);
- public abstract IEnumerable GetTypes();
- }
-
- internal class TypeList : TypeList
- {
- private readonly TypeResolutionKind _resolutionType;
-
- public TypeList(TypeResolutionKind resolutionType)
+ // if not caching, or not IDiscoverable, directly resolve types
+ if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false)
{
- _resolutionType = resolutionType;
+ return ResolveTypesInternal(
+ typeof (T), null,
+ () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan),
+ cache);
}
- private readonly List _types = new List();
+ // if caching and IDiscoverable
+ // filter the cached discovered types (and cache the result)
- public override void AddType(Type t)
+ var discovered = ResolveTypesInternal(
+ typeof (IDiscoverable), null,
+ () => TypeFinder.FindClassesOfType(AssembliesToScan),
+ true);
+
+ return ResolveTypesInternal(
+ typeof (T), null,
+ () => discovered
+ .Where(x => typeof (T).IsAssignableFrom(x)),
+ true);
+ }
+
+ ///
+ /// Resolves class types inheriting from or implementing the specified type and marked with the specified attribute.
+ ///
+ /// The type to inherit from or implement.
+ /// The type of the attribute.
+ /// Indicates whether to use cache for type resolution.
+ /// A set of assemblies for type resolution.
+ /// All class types inheriting from or implementing the specified type and marked with the specified attribute.
+ /// Caching is disabled when using specific assemblies.
+ public IEnumerable ResolveTypesWithAttribute(bool cache = true, IEnumerable specificAssemblies = null)
+ where TAttribute : Attribute
+ {
+ // do not cache anything from specific assemblies
+ cache &= specificAssemblies == null;
+
+ // if not caching, or not IDiscoverable, directly resolve types
+ if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false)
{
- //if the type is an attribute type we won't do the type check because typeof is going to be the
- //attribute type whereas the 't' type is the object type found with the attribute.
- if (_resolutionType == TypeResolutionKind.FindAttributedTypes || t.IsType())
+ return ResolveTypesInternal(
+ typeof (T), typeof (TAttribute),
+ () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan),
+ cache);
+ }
+
+ // if caching and IDiscoverable
+ // filter the cached discovered types (and cache the result)
+
+ var discovered = ResolveTypesInternal(
+ typeof (IDiscoverable), null,
+ () => TypeFinder.FindClassesOfType(AssembliesToScan),
+ true);
+
+ return ResolveTypesInternal(
+ typeof (T), typeof (TAttribute),
+ () => discovered
+ .Where(x => typeof(T).IsAssignableFrom(x))
+ .Where(x => x.GetCustomAttributes(false).Any()),
+ true);
+ }
+
+ ///
+ /// Resolves class types marked with the specified attribute.
+ ///
+ /// The type of the attribute.
+ /// Indicates whether to use cache for type resolution.
+ /// A set of assemblies for type resolution.
+ /// All class types marked with the specified attribute.
+ /// Caching is disabled when using specific assemblies.
+ public IEnumerable ResolveAttributedTypes(bool cache = true, IEnumerable specificAssemblies = null)
+ where TAttribute : Attribute
+ {
+ // do not cache anything from specific assemblies
+ cache &= specificAssemblies == null;
+
+ return ResolveTypesInternal(
+ typeof (object), typeof (TAttribute),
+ () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan),
+ cache);
+ }
+
+ private IEnumerable ResolveTypesInternal(
+ Type baseType, Type attributeType,
+ Func> finder,
+ bool cache)
+ {
+ // using an upgradeable lock makes little sense here as only one thread can enter the upgradeable
+ // lock at a time, and we don't have non-upgradeable readers, and quite probably the plugin
+ // manager is mostly not going to be used in any kind of massively multi-threaded scenario - so,
+ // a plain lock is enough
+
+ var name = ResolvedName(baseType, attributeType);
+
+ lock (_typesLock)
+ using (_logger.TraceDuration(
+ "Resolving " + name,
+ "Resolved " + name)) // cannot contain typesFound.Count as it's evaluated before the find
+ {
+ // resolve within a lock & timer
+ return ResolveTypesInternalLocked(baseType, attributeType, finder, cache);
+ }
+ }
+
+ private static string ResolvedName(Type baseType, Type attributeType)
+ {
+ var s = attributeType == null ? string.Empty : ("[" + attributeType + "]");
+ s += baseType;
+ return s;
+ }
+
+ private IEnumerable ResolveTypesInternalLocked(
+ Type baseType, Type attributeType,
+ Func> finder,
+ bool cache)
+ {
+ // check if the TypeList already exists, if so return it, if not we'll create it
+ var listKey = new TypeListKey(baseType, attributeType);
+ TypeList typeList = null;
+ if (cache)
+ _types.TryGetValue(listKey, out typeList); // else null
+
+ // if caching and found, return
+ if (typeList != null)
+ {
+ // need to put some logging here to try to figure out why this is happening: http://issues.umbraco.org/issue/U4-3505
+ _logger.Logger.Debug("Resolving {0}: found a cached type list.", () => ResolvedName(baseType, attributeType));
+ return typeList.Types;
+ }
+
+ // else proceed,
+ typeList = new TypeList(baseType, attributeType);
+
+ var scan = RequiresRescanning || File.Exists(GetPluginListFilePath()) == false;
+
+ if (scan)
+ {
+ // either we have to rescan, or we could not find the cache file:
+ // report (only once) and scan and update the cache file - this variable is purely used to check if we need to log
+ if (_reportedChange == false)
{
- _types.Add(t);
+ _logger.Logger.Debug("Assembly changes detected, need to rescan everything.");
+ _reportedChange = true;
}
}
+ if (scan == false)
+ {
+ // if we don't have to scan, try the cache
+ var cacheResult = TryGetCached(baseType, attributeType);
+
+ // here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan
+ // in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed
+ // so in this instance there will never be a result.
+ if (cacheResult.Exception is CachedPluginNotFoundInFileException || cacheResult.Success == false)
+ {
+ _logger.Logger.Debug("Resolving {0}: failed to load from cache file, must scan assemblies.", () => ResolvedName(baseType, attributeType));
+ scan = true;
+ }
+ else
+ {
+ // successfully retrieved types from the file cache: load
+ foreach (var type in cacheResult.Result)
+ {
+ try
+ {
+ // we use the build manager to ensure we get all types loaded, this is slightly slower than
+ // Type.GetType but if the types in the assembly aren't loaded yet it would fail whereas
+ // BuildManager will load them - this is how eg MVC loads types, etc - no need to make it
+ // more complicated
+ typeList.Add(BuildManager.GetType(type, true));
+ }
+ catch (Exception ex)
+ {
+ // in case of any exception, we have to exit, and revert to scanning
+ _logger.Logger.Error("Resolving " + ResolvedName(baseType, attributeType) + ": failed to load cache file type " + type + ", reverting to scanning assemblies.", ex);
+ scan = true;
+ break;
+ }
+ }
+
+ if (scan == false)
+ {
+ _logger.Logger.Debug("Resolving {0}: loaded types from cache file.", () => ResolvedName(baseType, attributeType));
+ }
+ }
+ }
+
+ if (scan)
+ {
+ // either we had to scan, or we could not resolve the types from the cache file - scan now
+ _logger.Logger.Debug("Resolving {0}: scanning assemblies.", () => ResolvedName(baseType, attributeType));
+
+ foreach (var t in finder())
+ typeList.Add(t);
+ }
+
+ // if we are to cache the results, do so
+ if (cache)
+ {
+ var added = _types.ContainsKey(listKey) == false;
+ if (added)
+ {
+ _types[listKey] = typeList;
+ //if we are scanning then update the cache file
+ if (scan)
+ {
+ UpdateCache();
+ }
+ }
+
+ _logger.Logger.Debug("Resolved {0}, caching ({1}).", () => ResolvedName(baseType, attributeType), () => added.ToString().ToLowerInvariant());
+ }
+ else
+ {
+ _logger.Logger.Debug("Resolved {0}.", () => ResolvedName(baseType, attributeType));
+ }
+
+ return typeList.Types;
+ }
+
+ #endregion
+
+ #region Nested classes and stuff
+
+ ///
+ /// Groups a type and a resolution kind into a key.
+ ///
+ private struct TypeListKey
+ {
+ // ReSharper disable MemberCanBePrivate.Local
+ public readonly Type BaseType;
+ public readonly Type AttributeType;
+ // ReSharper restore MemberCanBePrivate.Local
+
+ public TypeListKey(Type baseType, Type attributeType)
+ {
+ BaseType = baseType ?? typeof (object);
+ AttributeType = attributeType;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == null || obj is TypeListKey == false) return false;
+ var o = (TypeListKey)obj;
+ return BaseType == o.BaseType && AttributeType == o.AttributeType;
+ }
+
+ public override int GetHashCode()
+ {
+ // in case AttributeType is null we need something else, using typeof (TypeListKey)
+ // which does not really "mean" anything, it's just a value...
+
+ var hash = 5381;
+ hash = ((hash << 5) + hash) ^ BaseType.GetHashCode();
+ hash = ((hash << 5) + hash) ^ (AttributeType ?? typeof (TypeListKey)).GetHashCode();
+ return hash;
+ }
+ }
+
+ ///
+ /// Represents a list of types obtained by looking for types inheriting/implementing a
+ /// specified type, and/or marked with a specified attribute type.
+ ///
+ internal class TypeList
+ {
+ private readonly HashSet _types = new HashSet();
+
+ public TypeList(Type baseType, Type attributeType)
+ {
+ BaseType = baseType;
+ AttributeType = attributeType;
+ }
+
+ public Type BaseType { get; private set; }
+ public Type AttributeType { get; private set; }
+
+ ///
+ /// Adds a type.
+ ///
+ public void Add(Type type)
+ {
+ if (BaseType.IsAssignableFrom(type) == false)
+ throw new ArgumentException("Base type " + BaseType + " is not assignable from type " + type + ".", "type");
+ _types.Add(type);
+ }
+
///
- /// Returns true if the current TypeList is of the same lookup type
+ /// Gets the types.
///
- ///
- ///
- ///
- public override bool IsTypeList(TypeResolutionKind resolutionType)
+ public IEnumerable Types
{
- return _resolutionType == resolutionType && (typeof(T)) == typeof(TLookup);
- }
-
- public override IEnumerable GetTypes()
- {
- return _types;
+ get { return _types; }
}
}
///
- /// This class is used simply to determine that a plugin was not found in the cache plugin list with the specified
- /// TypeResolutionKind.
+ /// Represents the error that occurs when a plugin was not found in the cache plugin
+ /// list with the specified TypeResolutionKind.
///
internal class CachedPluginNotFoundInFileException : Exception
- {
-
- }
+ { }
#endregion
}
+
+ internal static class PluginManagerExtensions
+ {
+ ///
+ /// Gets all classes inheriting from PropertyEditor.
+ ///
+ ///
+ /// Excludes the actual PropertyEditor base type.
+ ///
+ public static IEnumerable ResolvePropertyEditors(this PluginManager mgr)
+ {
+ // look for IParameterEditor (fast, IDiscoverable) then filter
+
+ var propertyEditor = typeof (PropertyEditor);
+
+ return mgr.ResolveTypes()
+ .Where(x => propertyEditor.IsAssignableFrom(x) && x != propertyEditor);
+ }
+
+ ///
+ /// Gets all classes implementing IParameterEditor.
+ ///
+ ///
+ /// Includes property editors.
+ /// Excludes the actual ParameterEditor and PropertyEditor base types.
+ ///
+ public static IEnumerable ResolveParameterEditors(this PluginManager mgr)
+ {
+ var propertyEditor = typeof (PropertyEditor);
+ var parameterEditor = typeof (ParameterEditor);
+
+ return mgr.ResolveTypes()
+ .Where(x => x != propertyEditor && x != parameterEditor);
+ }
+
+ ///
+ /// Gets all classes implementing IApplicationStartupHandler.
+ ///
+ [Obsolete("IApplicationStartupHandler is obsolete.")]
+ public static IEnumerable ResolveApplicationStartupHandlers(this PluginManager mgr)
+ {
+ return mgr.ResolveTypes();
+ }
+
+ ///
+ /// Gets all classes implementing ICacheRefresher.
+ ///
+ public static IEnumerable ResolveCacheRefreshers(this PluginManager mgr)
+ {
+ return mgr.ResolveTypes();
+ }
+
+ ///
+ /// Gets all classes implementing IPropertyEditorValueConverter.
+ ///
+ [Obsolete("IPropertyEditorValueConverter is obsolete.")]
+ public static IEnumerable ResolvePropertyEditorValueConverters(this PluginManager mgr)
+ {
+ return mgr.ResolveTypes();
+ }
+
+ ///
+ /// Gets all classes implementing IDataType.
+ ///
+ [Obsolete("IDataType is obsolete.")]
+ public static IEnumerable ResolveDataTypes(this PluginManager mgr)
+ {
+ return mgr.ResolveTypes();
+ }
+
+ ///
+ /// Gets all classes implementing IMacroGuiRendering.
+ ///
+ [Obsolete("IMacroGuiRendering is obsolete.")]
+ public static IEnumerable ResolveMacroRenderings(this PluginManager mgr)
+ {
+ return mgr.ResolveTypes();
+ }
+
+ ///
+ /// Gets all classes implementing IPackageAction.
+ ///
+ public static IEnumerable ResolvePackageActions(this PluginManager mgr)
+ {
+ return mgr.ResolveTypes();
+ }
+
+ ///
+ /// Gets all classes implementing IAction.
+ ///
+ public static IEnumerable ResolveActions(this PluginManager mgr)
+ {
+ return mgr.ResolveTypes();
+ }
+
+ ///
+ /// Gets all classes inheriting from BaseMapper and marked with the MapperForAttribute.
+ ///
+ public static IEnumerable ResolveAssignedMapperTypes(this PluginManager mgr)
+ {
+ return mgr.ResolveTypesWithAttribute();
+ }
+
+ ///
+ /// Gets all classes implementing ISqlSyntaxProvider and marked with the SqlSyntaxProviderAttribute.
+ ///
+ public static IEnumerable ResolveSqlSyntaxProviders(this PluginManager mgr)
+ {
+ return mgr.ResolveTypesWithAttribute();
+ }
+ }
}
diff --git a/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs b/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs
index 44320c9464..cbff5e45ab 100644
--- a/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs
+++ b/src/Umbraco.Core/PropertyEditors/IParameterEditor.cs
@@ -1,8 +1,9 @@
using System.Collections.Generic;
+using umbraco.interfaces;
namespace Umbraco.Core.PropertyEditors
{
- public interface IParameterEditor
+ public interface IParameterEditor : IDiscoverable
{
///
/// The id of the property editor
diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs
index c7cbc0f473..b2e41ad628 100644
--- a/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/IPropertyEditorValueConverter.cs
@@ -1,4 +1,5 @@
using System;
+using umbraco.interfaces;
namespace Umbraco.Core.PropertyEditors
{
@@ -7,8 +8,8 @@ namespace Umbraco.Core.PropertyEditors
///
// todo: drop IPropertyEditorValueConverter support (when?).
[Obsolete("Use IPropertyValueConverter.")]
- public interface IPropertyEditorValueConverter
- {
+ public interface IPropertyEditorValueConverter : IDiscoverable
+ {
///
/// Returns a value indicating whether this provider applies to the specified property.
///
diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs
index 79887843a0..0666aca087 100644
--- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs
+++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs
@@ -1,11 +1,12 @@
-using Umbraco.Core.Models.PublishedContent;
+using umbraco.interfaces;
+using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.PropertyEditors
{
///
/// Provides published content properties conversion service.
///
- public interface IPropertyValueConverter
+ public interface IPropertyValueConverter : IDiscoverable
{
///
/// Gets a value indicating whether the converter supports a property type.
diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs
index f970cf225b..2af6e99cc7 100644
--- a/src/Umbraco.Core/TypeFinder.cs
+++ b/src/Umbraco.Core/TypeFinder.cs
@@ -1,32 +1,24 @@
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Linq.Expressions;
using System.Reflection;
using System.Security;
using System.Text;
-using System.Threading;
using System.Web;
using System.Web.Compilation;
-using System.Web.Hosting;
-using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Core
{
-
///
/// 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
{
- private static volatile HashSet _localFilteredAssemblyCache = null;
+ private static volatile HashSet _localFilteredAssemblyCache;
private static readonly object LocalFilteredAssemblyCacheLocker = new object();
///
@@ -63,7 +55,7 @@ namespace Umbraco.Core
}
catch (InvalidOperationException e)
{
- if (!(e.InnerException is SecurityException))
+ if ((e.InnerException is SecurityException) == false)
throw;
}
@@ -99,7 +91,7 @@ namespace Umbraco.Core
}
//if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies.
- if (!assemblies.Any())
+ if (assemblies.Any() == false)
{
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
@@ -111,12 +103,12 @@ namespace Umbraco.Core
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())))
+ if (appCodeFolder.Exists && fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))
{
try
{
var appCodeAssembly = Assembly.Load("App_Code");
- if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already
+ if (assemblies.Contains(appCodeAssembly) == false) // BuildManager will find App_Code already
assemblies.Add(appCodeAssembly);
}
catch (FileNotFoundException ex)
@@ -128,7 +120,7 @@ namespace Umbraco.Core
}
catch (InvalidOperationException e)
{
- if (!(e.InnerException is SecurityException))
+ if (e.InnerException is SecurityException == false)
throw;
}
@@ -145,23 +137,15 @@ namespace Umbraco.Core
internal static HashSet GetAssembliesWithKnownExclusions(
IEnumerable excludeFromResults = null)
{
- if (_localFilteredAssemblyCache == null)
+ lock (LocalFilteredAssemblyCacheLocker)
{
- lock (LocalFilteredAssemblyCacheLocker)
- {
- //double check
- if (_localFilteredAssemblyCache == null)
- {
- _localFilteredAssemblyCache = new HashSet();
- var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter);
- foreach (var a in assemblies)
- {
- _localFilteredAssemblyCache.Add(a);
- }
- }
- }
+ if (_localFilteredAssemblyCache != null)
+ return _localFilteredAssemblyCache;
+
+ var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter);
+ _localFilteredAssemblyCache = new HashSet(assemblies);
+ return _localFilteredAssemblyCache;
}
- return _localFilteredAssemblyCache;
}
///
@@ -180,9 +164,9 @@ namespace Umbraco.Core
exclusionFilter = new string[] { };
return GetAllAssemblies()
- .Where(x => !excludeFromResults.Contains(x)
- && !x.GlobalAssemblyCache
- && !exclusionFilter.Any(f => x.FullName.StartsWith(f)));
+ .Where(x => excludeFromResults.Contains(x) == false
+ && x.GlobalAssemblyCache == false
+ && exclusionFilter.Any(f => x.FullName.StartsWith(f)) == false);
}
///
@@ -298,7 +282,7 @@ namespace Umbraco.Core
{
if (assemblies == null) throw new ArgumentNullException("assemblies");
- return GetClasses(assignTypeFrom, assemblies, onlyConcreteClasses,
+ return GetClassesWithBaseType(assignTypeFrom, assemblies, onlyConcreteClasses,
//the additional filter will ensure that any found types also have the attribute applied.
t => t.GetCustomAttributes(false).Any());
}
@@ -324,7 +308,7 @@ namespace Umbraco.Core
{
if (assemblies == null) throw new ArgumentNullException("assemblies");
- return GetClasses(typeof(T), assemblies, onlyConcreteClasses);
+ return GetClassesWithBaseType(typeof(T), assemblies, onlyConcreteClasses);
}
///
@@ -363,105 +347,9 @@ namespace Umbraco.Core
IEnumerable assemblies,
bool onlyConcreteClasses)
{
- if (assemblies == null) throw new ArgumentNullException("assemblies");
-
- if (TypeHelper.IsTypeAssignableFrom(attributeType) == false)
- throw new ArgumentException("The type specified: " + attributeType + " is not an Attribute type");
-
- var foundAttributedTypes = new HashSet();
-
- var assemblyList = assemblies.ToArray();
-
- //find all assembly references that are referencing the attribute type's assembly since we
- //should only be scanning those assemblies because any other assembly will definitely not
- //contain a class that has this attribute.
- var referencedAssemblies = TypeHelper.GetReferencedAssemblies(attributeType, assemblyList);
-
- //get a list of non-referenced assemblies (we'll use this when we recurse below)
- var otherAssemblies = assemblyList.Where(x => referencedAssemblies.Contains(x) == false).ToArray();
-
- //loop through the referenced assemblies
- foreach (var a in referencedAssemblies)
- {
- //get all types in this assembly
- var allTypes = GetTypesWithFormattedException(a)
- .ToArray();
-
- var attributedTypes = new Type[] { };
- try
- {
- //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes but have
- //the specified attribute
- attributedTypes = allTypes
- .Where(t => (TypeHelper.IsNonStaticClass(t)
- && (onlyConcreteClasses == false || t.IsAbstract == false))
- //the type must have this attribute
- && t.GetCustomAttributes(attributeType, false).Any())
- .ToArray();
- }
- catch (TypeLoadException ex)
- {
- LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex);
- continue;
- }
-
- //add the types to our list to return
- foreach (var t in attributedTypes)
- {
- foundAttributedTypes.Add(t);
- }
-
- //get all attributes of the type being searched for
- var allAttributeTypes = allTypes.Where(attributeType.IsAssignableFrom);
-
- //now we need to include types that may be inheriting from sub classes of the attribute type being searched for
- //so we will search in assemblies that reference those types too.
- foreach (var subTypesInAssembly in allAttributeTypes.GroupBy(x => x.Assembly))
- {
-
- //So that we are not scanning too much, we need to group the sub types:
- // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type.
- // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class
- // * We should not search for sub types if the type is static since you cannot inherit from them.
- var subTypeList = subTypesInAssembly
- .Where(t => t.IsSealed == false && TypeHelper.IsStaticClass(t) == false)
- .ToArray();
-
- var baseClassAttempt = TypeHelper.GetLowestBaseType(subTypeList);
-
- //if there's a base class amongst the types then we'll only search for that type.
- //otherwise we'll have to search for all of them.
- var subTypesToSearch = new HashSet();
- if (baseClassAttempt.Success)
- {
- subTypesToSearch.Add(baseClassAttempt.Result);
- }
- else
- {
- foreach (var t in subTypeList)
- {
- subTypesToSearch.Add(t);
- }
- }
-
- foreach (var typeToSearch in subTypesToSearch)
- {
- //recursively find the types inheriting from this sub type in the other non-scanned assemblies.
- var foundTypes = FindClassesWithAttribute(typeToSearch, otherAssemblies, onlyConcreteClasses);
-
- foreach (var f in foundTypes)
- {
- foundAttributedTypes.Add(f);
- }
- }
-
- }
- }
-
- return foundAttributedTypes;
+ return GetClassesWithAttribute(attributeType, assemblies, onlyConcreteClasses);
}
-
///
/// Finds the classes with attribute.
///
@@ -485,122 +373,129 @@ namespace Umbraco.Core
return FindClassesWithAttribute(GetAssembliesWithKnownExclusions());
}
-
#region Private methods
+ private static IEnumerable GetClassesWithAttribute(
+ Type attributeType,
+ IEnumerable assemblies,
+ bool onlyConcreteClasses)
+ {
+ if (typeof(Attribute).IsAssignableFrom(attributeType) == false)
+ throw new ArgumentException("Type " + attributeType + " is not an Attribute type.");
+
+ var candidateAssemblies = new HashSet(assemblies);
+ var attributeAssemblyIsCandidate = candidateAssemblies.Contains(attributeType.Assembly);
+ candidateAssemblies.Remove(attributeType.Assembly);
+ var types = new List();
+
+ var stack = new Stack();
+ stack.Push(attributeType.Assembly);
+
+ while (stack.Count > 0)
+ {
+ var assembly = stack.Pop();
+
+ 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
+ }
+ catch (TypeLoadException ex)
+ {
+ LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly), ex);
+ continue;
+ }
+
+ types.AddRange(assemblyTypes.Where(x =>
+ x.IsClass // only classes
+ && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed
+ && x.IsNestedPrivate == false // exclude nested private
+ && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract
+ && x.GetCustomAttribute() == null // exclude hidden
+ && x.GetCustomAttributes(attributeType, false).Any())); // marked with the attribute
+ }
+
+ if (assembly != attributeType.Assembly && assemblyTypes.Where(attributeType.IsAssignableFrom).Any() == false)
+ continue;
+
+ foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies))
+ {
+ candidateAssemblies.Remove(referencing);
+ stack.Push(referencing);
+ }
+ }
+
+ return types;
+ }
+
///
/// Finds types that are assignable from the assignTypeFrom parameter and will scan for these types in the assembly
/// list passed in, however we will only scan assemblies that have a reference to the assignTypeFrom Type or any type
/// deriving from the base type.
///
- ///
+ ///
///
///
/// An additional filter to apply for what types will actually be included in the return value
///
- private static IEnumerable GetClasses(
- Type assignTypeFrom,
+ private static IEnumerable GetClassesWithBaseType(
+ Type baseType,
IEnumerable assemblies,
bool onlyConcreteClasses,
Func additionalFilter = null)
{
- //the default filter will always return true.
- if (additionalFilter == null)
+ var candidateAssemblies = new HashSet(assemblies);
+ var baseTypeAssemblyIsCandidate = candidateAssemblies.Contains(baseType.Assembly);
+ candidateAssemblies.Remove(baseType.Assembly);
+ var types = new List();
+
+ var stack = new Stack();
+ stack.Push(baseType.Assembly);
+
+ while (stack.Count > 0)
{
- additionalFilter = type => true;
- }
+ var assembly = stack.Pop();
- var foundAssignableTypes = new HashSet();
-
- var assemblyList = assemblies.ToArray();
-
- //find all assembly references that are referencing the current type's assembly since we
- //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 referencedAssemblies = TypeHelper.GetReferencedAssemblies(assignTypeFrom, assemblyList);
-
- //get a list of non-referenced assemblies (we'll use this when we recurse below)
- var otherAssemblies = assemblyList.Where(x => referencedAssemblies.Contains(x) == false).ToArray();
-
- //loop through the referenced assemblies
- foreach (var a in referencedAssemblies)
- {
- //get all types in the assembly that are sub types of the current type
- var allSubTypes = GetTypesWithFormattedException(a)
- .Where(assignTypeFrom.IsAssignableFrom)
- .ToArray();
-
- var filteredTypes = new Type[] { };
- try
+ // get all assembly types that can be assigned to baseType
+ Type[] assemblyTypes = null;
+ if (assembly != baseType.Assembly || baseTypeAssemblyIsCandidate)
{
- //now filter the types based on the onlyConcreteClasses flag, not interfaces, not static classes
- filteredTypes = allSubTypes
- .Where(t => (TypeHelper.IsNonStaticClass(t)
- //Do not include nested private classes - since we are in full trust now this will find those too!
- && t.IsNestedPrivate == false
- && (onlyConcreteClasses == false || t.IsAbstract == false)
- //Do not include classes that are flagged to hide from the type finder
- && t.GetCustomAttribute() == null
- && additionalFilter(t)))
- .ToArray();
+ try
+ {
+ assemblyTypes = GetTypesWithFormattedException(assembly)
+ .Where(baseType.IsAssignableFrom)
+ .ToArray(); // in try block
+ }
+ catch (TypeLoadException ex)
+ {
+ LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", assembly), ex);
+ continue;
+ }
+
+ types.AddRange(assemblyTypes.Where(x =>
+ x.IsClass // only classes
+ && (x.IsAbstract == false || x.IsSealed == false) // ie non-static, static is abstract and sealed
+ && x.IsNestedPrivate == false // exclude nested private
+ && (onlyConcreteClasses == false || x.IsAbstract == false) // exclude abstract
+ && x.GetCustomAttribute() == null // exclude hidden
+ && (additionalFilter == null || additionalFilter(x)))); // filter
}
- catch (TypeLoadException ex)
- {
- LogHelper.Error(typeof(TypeFinder), string.Format("Could not query types on {0} assembly, this is most likely due to this assembly not being compatible with the current Umbraco version", a), ex);
+
+ if (assembly != baseType.Assembly && assemblyTypes.All(x => x.IsSealed))
continue;
- }
- //add the types to our list to return
- foreach (var t in filteredTypes)
+ foreach (var referencing in TypeHelper.GetReferencingAssemblies(assembly, candidateAssemblies))
{
- foundAssignableTypes.Add(t);
+ candidateAssemblies.Remove(referencing);
+ stack.Push(referencing);
}
-
- //now we need to include types that may be inheriting from sub classes of the type being searched for
- //so we will search in assemblies that reference those types too.
- foreach (var subTypesInAssembly in allSubTypes.GroupBy(x => x.Assembly))
- {
-
- //So that we are not scanning too much, we need to group the sub types:
- // * if there is more than 1 sub type in the same assembly then we should only search on the 'lowest base' type.
- // * We should also not search for sub types if the type is sealed since you cannot inherit from a sealed class
- // * We should not search for sub types if the type is static since you cannot inherit from them.
- var subTypeList = subTypesInAssembly
- .Where(t => t.IsSealed == false && TypeHelper.IsStaticClass(t) == false)
- .ToArray();
-
- var baseClassAttempt = TypeHelper.GetLowestBaseType(subTypeList);
-
- //if there's a base class amongst the types then we'll only search for that type.
- //otherwise we'll have to search for all of them.
- var subTypesToSearch = new HashSet();
- if (baseClassAttempt.Success)
- {
- subTypesToSearch.Add(baseClassAttempt.Result);
- }
- else
- {
- foreach (var t in subTypeList)
- {
- subTypesToSearch.Add(t);
- }
- }
-
- foreach (var typeToSearch in subTypesToSearch)
- {
- //recursively find the types inheriting from this sub type in the other non-scanned assemblies.
- var foundTypes = GetClasses(typeToSearch, otherAssemblies, onlyConcreteClasses, additionalFilter);
-
- foreach (var f in foundTypes)
- {
- foundAssignableTypes.Add(f);
- }
- }
-
- }
-
}
- return foundAssignableTypes;
+
+ return types;
}
internal static IEnumerable GetTypesWithFormattedException(Assembly a)
@@ -666,7 +561,6 @@ namespace Umbraco.Core
#endregion
-
public static Type GetTypeByName(string typeName)
{
var type = BuildManager.GetType(typeName, false);
@@ -684,6 +578,5 @@ namespace Umbraco.Core
.Select(x => x.GetType(typeName))
.FirstOrDefault(x => x != null);
}
-
}
}
diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs
index f0b3a82961..14c441dcd5 100644
--- a/src/Umbraco.Core/TypeHelper.cs
+++ b/src/Umbraco.Core/TypeHelper.cs
@@ -13,9 +13,12 @@ namespace Umbraco.Core
///
internal static class TypeHelper
{
-
- private static readonly ConcurrentDictionary GetFieldsCache = new ConcurrentDictionary();
- private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache = new ConcurrentDictionary, PropertyInfo[]>();
+ private static readonly ConcurrentDictionary, PropertyInfo[]> GetPropertiesCache
+ = new ConcurrentDictionary, PropertyInfo[]>();
+ private static readonly ConcurrentDictionary GetFieldsCache
+ = new ConcurrentDictionary();
+
+ private static readonly Assembly[] EmptyAssemblies = new Assembly[0];
///
/// Checks if the method is actually overriding a base method
@@ -30,44 +33,40 @@ namespace Umbraco.Core
///
/// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList
///
- ///
- ///
+ /// The referenced assembly.
+ /// A list of assemblies.
///
///
/// 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.
///
- public static Assembly[] GetReferencedAssemblies(Type assignTypeFrom, IEnumerable assemblies)
+ public static Assembly[] GetReferencingAssemblies(Assembly assembly, IEnumerable assemblies)
{
- //check if it is the app_code assembly.
- //check if it is App_global.asax assembly
- if (assignTypeFrom.Assembly.IsAppCodeAssembly() || assignTypeFrom.Assembly.IsGlobalAsaxAssembly())
- {
- return Enumerable.Empty().ToArray();
- }
+ if (assembly.IsAppCodeAssembly() || assembly.IsGlobalAsaxAssembly())
+ return EmptyAssemblies;
+
- //find all assembly references that are referencing the current type's assembly since we
- //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
- return assemblies
- .Where(assembly =>
- assembly == assignTypeFrom.Assembly
- || HasReferenceToAssemblyWithName(assembly, assignTypeFrom.Assembly.GetName().Name))
- .ToArray();
+ // find all assembly references that are referencing the current type's assembly since we
+ // 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();
}
///
- /// checks if the assembly has a reference with the same name as the expected assembly name.
+ /// Determines if an assembly references another assembly.
///
///
- ///
+ ///
///
- private static bool HasReferenceToAssemblyWithName(Assembly assembly, string expectedAssemblyName)
- {
- return assembly
- .GetReferencedAssemblies()
- .Select(a => a.Name)
- .Contains(expectedAssemblyName, StringComparer.Ordinal);
+ public static bool HasReference(Assembly assembly, string name)
+ {
+ // ReSharper disable once LoopCanBeConvertedToQuery - no!
+ foreach (var a in assembly.GetReferencedAssemblies())
+ {
+ if (string.Equals(a.Name, name, StringComparison.Ordinal)) return true;
+ }
+ return false;
}
///
@@ -106,13 +105,10 @@ namespace Umbraco.Core
public static Attempt GetLowestBaseType(params Type[] types)
{
if (types.Length == 0)
- {
return Attempt.Fail();
- }
- if (types.Length == 1)
- {
+
+ if (types.Length == 1)
return Attempt.Succeed(types[0]);
- }
foreach (var curr in types)
{
@@ -196,20 +192,15 @@ namespace Umbraco.Core
///
///
///
- public static PropertyInfo GetProperty(Type type, string name,
- bool mustRead = true,
- bool mustWrite = true,
+ public static PropertyInfo GetProperty(Type type, string name,
+ bool mustRead = true,
+ bool mustWrite = true,
bool includeIndexed = false,
bool caseSensitive = true)
{
- return CachedDiscoverableProperties(type, mustRead, mustWrite, includeIndexed)
- .FirstOrDefault(x =>
- {
- if (caseSensitive)
- return x.Name == name;
- return x.Name.InvariantEquals(name);
- });
- }
+ return CachedDiscoverableProperties(type, mustRead, mustWrite, includeIndexed)
+ .FirstOrDefault(x => caseSensitive ? (x.Name == name) : x.Name.InvariantEquals(name));
+ }
///
/// Gets (and caches) discoverable in the current for a given .
@@ -222,7 +213,7 @@ namespace Umbraco.Core
type,
x => type
.GetFields(BindingFlags.Public | BindingFlags.Instance)
- .Where(y => !y.IsInitOnly)
+ .Where(y => y.IsInitOnly == false)
.ToArray());
}
@@ -240,13 +231,12 @@ namespace Umbraco.Core
new Tuple(type, mustRead, mustWrite, includeIndexed),
x => type
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
- .Where(y => (!mustRead || y.CanRead)
- && (!mustWrite || y.CanWrite)
- && (includeIndexed || !y.GetIndexParameters().Any()))
+ .Where(y => (mustRead == false || y.CanRead)
+ && (mustWrite == false || y.CanWrite)
+ && (includeIndexed || y.GetIndexParameters().Any() == false))
.ToArray());
}
-
#region Match Type
//TODO: Need to determine if these methods should replace/combine/merge etc with IsTypeAssignableFrom, IsAssignableFromGeneric
@@ -337,9 +327,9 @@ namespace Umbraco.Core
// not a generic type, not a generic parameter
// so normal class or interface
- // fixme structs? enums? array types?
// about primitive types, value types, etc:
// http://stackoverflow.com/questions/1827425/how-to-check-programatically-if-a-type-is-a-struct-or-a-class
+ // if it's a primitive type... it needs to be ==
if (implementation == contract) return true;
if (contract.IsClass && implementation.IsClass && implementation.IsSubclassOf(contract)) return true;
diff --git a/src/Umbraco.Tests/CodeFirst/TypeInheritanceTest.cs b/src/Umbraco.Tests/CodeFirst/TypeInheritanceTest.cs
index e48e13a385..7d2942f1c9 100644
--- a/src/Umbraco.Tests/CodeFirst/TypeInheritanceTest.cs
+++ b/src/Umbraco.Tests/CodeFirst/TypeInheritanceTest.cs
@@ -35,7 +35,7 @@ namespace Umbraco.Tests.CodeFirst
}
};
-
+
}
[Test]
@@ -84,10 +84,7 @@ namespace Umbraco.Tests.CodeFirst
var foundTypes = _pluginManager.ResolveContentTypeBaseTypes();
Assert.That(foundTypes.Count(), Is.EqualTo(15));
- Assert.AreEqual(1,
- _pluginManager.GetTypeLists()
- .Count(x => x.IsTypeList(PluginManager.TypeResolutionKind.FindAllTypes)));
+ Assert.AreEqual(1, _pluginManager.TypeLists.Count(x => x.BaseType == typeof (ContentTypeBase) && x.AttributeType == null));
}
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs
index 95e8fb16e2..333970bf67 100644
--- a/src/Umbraco.Tests/Plugins/PluginManagerTests.cs
+++ b/src/Umbraco.Tests/Plugins/PluginManagerTests.cs
@@ -18,6 +18,7 @@ using Umbraco.Core.Profiling;
using Umbraco.Core.PropertyEditors;
using umbraco.DataLayer;
using umbraco.editorControls;
+using umbraco.interfaces;
using umbraco.MacroEngines;
using umbraco.uicontrols;
using Umbraco.Web;
@@ -34,14 +35,14 @@ namespace Umbraco.Tests.Plugins
public void Initialize()
{
//this ensures its reset
- _manager = new PluginManager(new ActivatorServiceProvider(), new NullCacheProvider(),
+ _manager = new PluginManager(new ActivatorServiceProvider(), new NullCacheProvider(),
new ProfilingLogger(Mock.Of(), Mock.Of()));
//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
_manager.AssembliesToScan = new[]
{
- this.GetType().Assembly,
+ this.GetType().Assembly,
typeof(ApplicationStartupHandler).Assembly,
typeof(SqlCEHelper).Assembly,
typeof(CMSNode).Assembly,
@@ -160,7 +161,7 @@ namespace Umbraco.Tests.Plugins
public void Detect_Legacy_Plugin_File_List()
{
var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
-
+
var filePath= Path.Combine(tempFolder, string.Format("umbraco-plugins.{0}.list", NetworkHelper.FileSafeMachineName));
File.WriteAllText(filePath, @"
@@ -169,12 +170,11 @@ namespace Umbraco.Tests.Plugins
");
-
- Assert.IsTrue(_manager.DetectLegacyPluginListFile());
+
+ Assert.IsEmpty(_manager.ReadCache()); // uber-legacy cannot be read
File.Delete(filePath);
- //now create a valid one
File.WriteAllText(filePath, @"
@@ -182,19 +182,32 @@ namespace Umbraco.Tests.Plugins
");
- Assert.IsFalse(_manager.DetectLegacyPluginListFile());
+ Assert.IsEmpty(_manager.ReadCache()); // legacy cannot be read
+
+ File.Delete(filePath);
+
+ File.WriteAllText(filePath, @"IContentFinder
+
+MyContentFinder
+AnotherContentFinder
+
+");
+
+ Assert.IsNotNull(_manager.ReadCache()); // works
}
[Test]
public void Create_Cached_Plugin_File()
{
- var types = new[] { typeof(PluginManager), typeof(PluginManagerTests), typeof(UmbracoContext) };
+ var types = new[] { typeof (PluginManager), typeof (PluginManagerTests), typeof (UmbracoContext) };
- //yes this is silly, none of these types inherit from string, but this is just to test the xml file format
- _manager.UpdateCachedPluginsFile(types, PluginManager.TypeResolutionKind.FindAllTypes);
+ var typeList1 = new PluginManager.TypeList(typeof (object), null);
+ foreach (var type in types) typeList1.Add(type);
+ _manager.AddTypeList(typeList1);
+ _manager.WriteCache();
- var plugins = _manager.TryGetCachedPluginsFromFile(PluginManager.TypeResolutionKind.FindAllTypes);
- var diffType = _manager.TryGetCachedPluginsFromFile(PluginManager.TypeResolutionKind.FindAttributedTypes);
+ var plugins = _manager.TryGetCached(typeof (object), null);
+ var diffType = _manager.TryGetCached(typeof (object), typeof (ObsoleteAttribute));
Assert.IsTrue(plugins.Success);
//this will be false since there is no cache of that type resolution kind
@@ -210,7 +223,7 @@ namespace Umbraco.Tests.Plugins
public void PluginHash_From_String()
{
var s = "hello my name is someone".GetHashCode().ToString("x", CultureInfo.InvariantCulture);
- var output = PluginManager.ConvertPluginsHashFromHex(s);
+ var output = PluginManager.ConvertHashToInt64(s);
Assert.AreNotEqual(0, output);
}
@@ -240,7 +253,7 @@ namespace Umbraco.Tests.Plugins
var list1 = new[] { f1, f2, f3, f4, f5, f6 };
var list2 = new[] { f1, f3, f5 };
var list3 = new[] { f1, f3, f5, f7 };
-
+
//Act
var hash1 = PluginManager.GetFileHash(list1, new ProfilingLogger(Mock.Of(), Mock.Of()));
var hash2 = PluginManager.GetFileHash(list2, new ProfilingLogger(Mock.Of(), Mock.Of()));
@@ -259,9 +272,7 @@ namespace Umbraco.Tests.Plugins
{
var foundTypes1 = _manager.ResolveFindMeTypes();
var foundTypes2 = _manager.ResolveFindMeTypes();
- Assert.AreEqual(1,
- _manager.GetTypeLists()
- .Count(x => x.IsTypeList(PluginManager.TypeResolutionKind.FindAllTypes)));
+ Assert.AreEqual(1, _manager.TypeLists.Count(x => x.BaseType == typeof(IFindMe) && x.AttributeType == null));
}
[Test]
@@ -344,20 +355,20 @@ namespace Umbraco.Tests.Plugins
{
var types = new HashSet();
- var propEditors = new PluginManager.TypeList(PluginManager.TypeResolutionKind.FindAllTypes);
- propEditors.AddType(typeof(LabelPropertyEditor));
+ var propEditors = new PluginManager.TypeList(typeof (PropertyEditor), null);
+ propEditors.Add(typeof(LabelPropertyEditor));
types.Add(propEditors);
- var found = types.SingleOrDefault(x => x.IsTypeList(PluginManager.TypeResolutionKind.FindAllTypes));
+ var found = types.SingleOrDefault(x => x.BaseType == typeof (PropertyEditor) && x.AttributeType == null);
Assert.IsNotNull(found);
//This should not find a type list of this type
- var shouldNotFind = types.SingleOrDefault(x => x.IsTypeList(PluginManager.TypeResolutionKind.FindAllTypes));
+ var shouldNotFind = types.SingleOrDefault(x => x.BaseType == typeof (IParameterEditor) && x.AttributeType == null);
Assert.IsNull(shouldNotFind);
}
-
+
[XsltExtension("Blah.Blah")]
public class MyXsltExtension
{
@@ -371,7 +382,7 @@ namespace Umbraco.Tests.Plugins
}
- public interface IFindMe
+ public interface IFindMe : IDiscoverable
{
}
diff --git a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs
index 50ab053668..35c14860d7 100644
--- a/src/Umbraco.Tests/Plugins/TypeFinderTests.cs
+++ b/src/Umbraco.Tests/Plugins/TypeFinderTests.cs
@@ -26,11 +26,11 @@ using Umbraco.Web.BaseRest;
namespace Umbraco.Tests.Plugins
{
-
+
///
/// Tests for typefinder
///
- [TestFixture]
+ [TestFixture]
public class TypeFinderTests
{
///
@@ -43,7 +43,7 @@ namespace Umbraco.Tests.Plugins
{
_assemblies = new[]
{
- this.GetType().Assembly,
+ this.GetType().Assembly,
typeof(ApplicationStartupHandler).Assembly,
typeof(SqlCEHelper).Assembly,
typeof(CMSNode).Assembly,
@@ -75,7 +75,7 @@ namespace Umbraco.Tests.Plugins
[Test]
public void Find_Classes_Of_Type()
{
- var typesFound = TypeFinder.FindClassesOfType(_assemblies);
+ var typesFound = TypeFinder.FindClassesOfType(_assemblies);
var originalTypesFound = TypeFinderOriginal.FindClassesOfType(_assemblies);
Assert.AreEqual(originalTypesFound.Count(), typesFound.Count());
@@ -118,7 +118,7 @@ namespace Umbraco.Tests.Plugins
}
}
}
-
+
}
[Ignore]
@@ -149,7 +149,7 @@ namespace Umbraco.Tests.Plugins
}
}
}
-
+
}
public class MyTag : ITag
@@ -161,7 +161,7 @@ namespace Umbraco.Tests.Plugins
public class MySuperTag : MyTag
{
-
+
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
@@ -203,7 +203,7 @@ namespace Umbraco.Tests.Plugins
/// 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
+ /// 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
@@ -329,8 +329,8 @@ namespace Umbraco.Tests.Plugins
}
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
+ //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.
}
}
@@ -347,9 +347,9 @@ namespace Umbraco.Tests.Plugins
}
catch (SecurityException)
{
- //we will just ignore this because if we are trying to do a call to:
+ //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
+ //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.
}
}
@@ -361,7 +361,7 @@ namespace Umbraco.Tests.Plugins
}
///
- /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan
+ /// 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.
///
@@ -429,7 +429,7 @@ namespace Umbraco.Tests.Plugins
"RouteDebugger,",
"SqlCE4Umbraco,",
"umbraco.datalayer,",
- "umbraco.interfaces,",
+ "umbraco.interfaces,",
"umbraco.providers,",
"Umbraco.Web.UI,",
"umbraco.webservices",
@@ -631,5 +631,5 @@ namespace Umbraco.Tests.Plugins
}
}
-
+
}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
index f96f176036..87af8f3234 100644
--- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
+++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
@@ -62,7 +62,7 @@ namespace Umbraco.Tests.TestHelpers
public override void TearDown()
{
base.TearDown();
-
+
// reset settings
SettingsForTests.Reset();
UmbracoContext.Current = null;
@@ -119,7 +119,7 @@ namespace Umbraco.Tests.TestHelpers
}
///
- /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan
+ /// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan
/// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs
/// to be set to true if the SetupPluginManager has been overridden.
///
diff --git a/src/Umbraco.Web/Editors/IEditorValidator.cs b/src/Umbraco.Web/Editors/IEditorValidator.cs
index e1d4e68ed2..8cbd4e2949 100644
--- a/src/Umbraco.Web/Editors/IEditorValidator.cs
+++ b/src/Umbraco.Web/Editors/IEditorValidator.cs
@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using umbraco.interfaces;
namespace Umbraco.Web.Editors
{
- internal interface IEditorValidator
+ internal interface IEditorValidator : IDiscoverable
{
Type ModelType { get; }
IEnumerable Validate(object model);
diff --git a/src/Umbraco.Web/HealthCheck/HealthCheck.cs b/src/Umbraco.Web/HealthCheck/HealthCheck.cs
index ec1358947a..34158a66eb 100644
--- a/src/Umbraco.Web/HealthCheck/HealthCheck.cs
+++ b/src/Umbraco.Web/HealthCheck/HealthCheck.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
+using umbraco.interfaces;
using Umbraco.Core;
namespace Umbraco.Web.HealthCheck
@@ -9,7 +10,7 @@ namespace Umbraco.Web.HealthCheck
/// The abstract health check class
///
[DataContract(Name = "healtCheck", Namespace = "")]
- public abstract class HealthCheck
+ public abstract class HealthCheck : IDiscoverable
{
protected HealthCheck(HealthCheckContext healthCheckContext)
{
diff --git a/src/Umbraco.Web/Media/ImageUrl.cs b/src/Umbraco.Web/Media/ImageUrl.cs
index dff9358a38..2af7fa8b9b 100644
--- a/src/Umbraco.Web/Media/ImageUrl.cs
+++ b/src/Umbraco.Web/Media/ImageUrl.cs
@@ -11,6 +11,7 @@ using umbraco;
namespace Umbraco.Web.Media
{
+ [Obsolete("This is no longer used and will be removed in future versions")]
public class ImageUrl
{
[Obsolete("Use TryGetImageUrl() instead")]
diff --git a/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs b/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs
index c52d937d8f..dfed61e69d 100644
--- a/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs
+++ b/src/Umbraco.Web/Media/ImageUrlProviderResolver.cs
@@ -8,9 +8,10 @@ using Umbraco.Web.Media.ImageUrlProviders;
namespace Umbraco.Web.Media
{
- internal sealed class ImageUrlProviderResolver : ManyObjectsResolverBase
+ [Obsolete("IImageUrlProvider is no longer used and will be removed in future versions")]
+ internal sealed class ImageUrlProviderResolver : LazyManyObjectsResolverBase
{
- internal ImageUrlProviderResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable value)
+ internal ImageUrlProviderResolver(IServiceProvider serviceProvider, ILogger logger, Func> value)
: base(serviceProvider, logger, value)
{
}
diff --git a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs
index 2513e8c1f4..f172dc3b7e 100644
--- a/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs
+++ b/src/Umbraco.Web/Media/ThumbnailProviders/ThumbnailProvidersResolver.cs
@@ -12,7 +12,8 @@ using umbraco.BusinessLogic.Utils;
namespace Umbraco.Web.Media.ThumbnailProviders
{
- public sealed class ThumbnailProvidersResolver : ManyObjectsResolverBase
+ [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")]
+ public sealed class ThumbnailProvidersResolver : LazyManyObjectsResolverBase
{
///
/// Constructor
@@ -20,7 +21,7 @@ namespace Umbraco.Web.Media.ThumbnailProviders
///
///
///
- internal ThumbnailProvidersResolver(IServiceProvider serviceProvider, ILogger logger, IEnumerable providers)
+ internal ThumbnailProvidersResolver(IServiceProvider serviceProvider, ILogger logger, Func> providers)
: base(serviceProvider, logger, providers)
{
diff --git a/src/Umbraco.Web/Mvc/PluginController.cs b/src/Umbraco.Web/Mvc/PluginController.cs
index 48608fc9a1..4ff4b961b8 100644
--- a/src/Umbraco.Web/Mvc/PluginController.cs
+++ b/src/Umbraco.Web/Mvc/PluginController.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Web.Mvc;
+using umbraco.interfaces;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Services;
@@ -12,7 +13,7 @@ namespace Umbraco.Web.Mvc
///
/// A base class for all plugin controllers to inherit from
///
- public abstract class PluginController : Controller
+ public abstract class PluginController : Controller, IDiscoverable
{
///
/// stores the metadata about plugin controllers
diff --git a/src/Umbraco.Web/WebApi/UmbracoApiController.cs b/src/Umbraco.Web/WebApi/UmbracoApiController.cs
index b850532011..0356432c66 100644
--- a/src/Umbraco.Web/WebApi/UmbracoApiController.cs
+++ b/src/Umbraco.Web/WebApi/UmbracoApiController.cs
@@ -2,6 +2,7 @@
using System.Linq;
using System.Web.Http;
using System.Web.Http.ModelBinding;
+using umbraco.interfaces;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Validation;
using Umbraco.Web.Models.ContentEditing;
@@ -11,7 +12,7 @@ namespace Umbraco.Web.WebApi
///
/// The base class for auto-routed API controllers for Umbraco
///
- public abstract class UmbracoApiController : UmbracoApiControllerBase
+ public abstract class UmbracoApiController : UmbracoApiControllerBase, IDiscoverable
{
protected UmbracoApiController()
{
diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs
index 3f970b06ab..29565065e2 100644
--- a/src/Umbraco.Web/WebBootManager.cs
+++ b/src/Umbraco.Web/WebBootManager.cs
@@ -533,11 +533,11 @@ namespace Umbraco.Web
ThumbnailProvidersResolver.Current = new ThumbnailProvidersResolver(
ServiceProvider, LoggerResolver.Current.Logger,
- PluginManager.ResolveThumbnailProviders());
+ () => PluginManager.ResolveThumbnailProviders());
ImageUrlProviderResolver.Current = new ImageUrlProviderResolver(
ServiceProvider, LoggerResolver.Current.Logger,
- PluginManager.ResolveImageUrlProviders());
+ () => PluginManager.ResolveImageUrlProviders());
CultureDictionaryFactoryResolver.Current = new CultureDictionaryFactoryResolver(
new DefaultCultureDictionaryFactory());
diff --git a/src/Umbraco.Web/WebServices/FolderBrowserService.cs b/src/Umbraco.Web/WebServices/FolderBrowserService.cs
index bf5a3f1bd5..15a6c10880 100644
--- a/src/Umbraco.Web/WebServices/FolderBrowserService.cs
+++ b/src/Umbraco.Web/WebServices/FolderBrowserService.cs
@@ -15,7 +15,7 @@ using Tag = umbraco.cms.businesslogic.Tags.Tag;
namespace Umbraco.Web.WebServices
{
//TODO: Can we convert this to MVC please instead of /base?
-
+ [Obsolete("Thumbnails are generated by ImageProcessor, use that instead")]
[RestExtension("FolderBrowserService")]
public class FolderBrowserService
{
diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Image.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Image.cs
index c920d43c7a..dd0c0e0554 100644
--- a/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Image.cs
+++ b/src/Umbraco.Web/umbraco.presentation/umbraco/templateControls/Image.cs
@@ -1,10 +1,12 @@
-using System.Web.UI;
+using System;
+using System.Web.UI;
using System.Web.UI.HtmlControls;
using Umbraco.Core.Media;
using Umbraco.Web.Media;
namespace umbraco.presentation.templateControls
{
+ [Obsolete("This is no longer used and will be removed in future versions")]
public class Image : HtmlImage
{
public string NodeId { get; set; }
diff --git a/src/umbraco.businesslogic/PluginManagerExtensions.cs b/src/umbraco.businesslogic/PluginManagerExtensions.cs
index 7a61414315..0df66b3b0c 100644
--- a/src/umbraco.businesslogic/PluginManagerExtensions.cs
+++ b/src/umbraco.businesslogic/PluginManagerExtensions.cs
@@ -16,9 +16,8 @@ namespace umbraco.businesslogic
///
///
internal static IEnumerable ResolveApplications(this PluginManager resolver)
- {
- //don't cache the result of this because it is only used once during app startup, caching will just add a bit more mem overhead for no reason
- return resolver.ResolveTypesWithAttribute(cacheResult:false);
+ {
+ return resolver.ResolveTypesWithAttribute();
}
///
@@ -28,8 +27,7 @@ namespace umbraco.businesslogic
///
internal static IEnumerable ResolveAttributedTrees(this PluginManager resolver)
{
- //don't cache the result of this because it is only used once during app startup, caching will just add a bit more mem overhead for no reason
- return resolver.ResolveTypesWithAttribute(cacheResult:false);
+ return resolver.ResolveTypesWithAttribute();
}
}
diff --git a/src/umbraco.cms/Actions/Action.cs b/src/umbraco.cms/Actions/Action.cs
index df6c257396..3a5503d714 100644
--- a/src/umbraco.cms/Actions/Action.cs
+++ b/src/umbraco.cms/Actions/Action.cs
@@ -35,40 +35,10 @@ namespace umbraco.BusinessLogic.Actions
public class Action
{
private static readonly Dictionary ActionJs = new Dictionary();
-
- private static readonly object Lock = new object();
-
- static Action()
- {
- ReRegisterActionsAndHandlers();
- }
-
- ///
- /// This is used when an IAction or IActionHandler is installed into the system
- /// and needs to be loaded into memory.
- ///
- ///
- /// TODO: this shouldn't be needed... we should restart the app pool when a package is installed!
- ///
+
+ [Obsolete("This no longer performs any action there is never a reason to rescan because the app domain will be restarted if new IActions are added because they are included in assemblies")]
public static void ReRegisterActionsAndHandlers()
- {
- lock (Lock)
- {
- // NOTE use the DirtyBackdoor to change the resolution configuration EXCLUSIVELY
- // ie do NOT do ANYTHING else while holding the backdoor, because while it is open
- // the whole resolution system is locked => nothing can work properly => deadlocks
-
- var newResolver = new ActionsResolver(
- new ActivatorServiceProvider(), LoggerResolver.Current.Logger,
- () => TypeFinder.FindClassesOfType(PluginManager.Current.AssembliesToScan));
-
- using (Umbraco.Core.ObjectResolution.Resolution.DirtyBackdoorToConfiguration)
- {
- ActionsResolver.Reset(false); // and do NOT reset the whole resolution!
- ActionsResolver.Current = newResolver;
- }
-
- }
+ {
}
///
diff --git a/src/umbraco.cms/businesslogic/macro/IMacroEngine.cs b/src/umbraco.cms/businesslogic/macro/IMacroEngine.cs
index 952ee11158..9655a9b0c7 100644
--- a/src/umbraco.cms/businesslogic/macro/IMacroEngine.cs
+++ b/src/umbraco.cms/businesslogic/macro/IMacroEngine.cs
@@ -6,7 +6,8 @@ using umbraco.interfaces;
namespace umbraco.cms.businesslogic.macro
{
- public interface IMacroEngine {
+ public interface IMacroEngine : IDiscoverable
+ {
string Name { get; }
IEnumerable SupportedExtensions { get; }
IEnumerable SupportedUIExtensions { get; }
diff --git a/src/umbraco.cms/businesslogic/media/IMediaFactory.cs b/src/umbraco.cms/businesslogic/media/IMediaFactory.cs
index 8f8f87556e..0106f131b4 100644
--- a/src/umbraco.cms/businesslogic/media/IMediaFactory.cs
+++ b/src/umbraco.cms/businesslogic/media/IMediaFactory.cs
@@ -3,11 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using umbraco.BusinessLogic;
+using umbraco.interfaces;
namespace umbraco.cms.businesslogic.media
{
[Obsolete("This interface is no longer used and will be removed from the codebase in future versions")]
- public interface IMediaFactory
+ public interface IMediaFactory : IDiscoverable
{
List Extensions { get; }
int Priority { get; }
diff --git a/src/umbraco.interfaces/IAction.cs b/src/umbraco.interfaces/IAction.cs
index 6d62a0780f..e82c5935c4 100644
--- a/src/umbraco.interfaces/IAction.cs
+++ b/src/umbraco.interfaces/IAction.cs
@@ -5,8 +5,8 @@ namespace umbraco.interfaces
///
/// Summary description for ActionI.
///
- public interface IAction
- {
+ public interface IAction : IDiscoverable
+ {
char Letter {get;}
bool ShowInNotifier {get;}
bool CanBePermissionAssigned {get;}
diff --git a/src/umbraco.interfaces/IApplication.cs b/src/umbraco.interfaces/IApplication.cs
index 98dc043dcd..3c0528f961 100644
--- a/src/umbraco.interfaces/IApplication.cs
+++ b/src/umbraco.interfaces/IApplication.cs
@@ -7,6 +7,6 @@ namespace umbraco.interfaces
///
/// Interface for created applications in the umbraco backoffice
///
- public interface IApplication
- {}
+ public interface IApplication : IDiscoverable
+ { }
}
diff --git a/src/umbraco.interfaces/IApplicationStartupHandler.cs b/src/umbraco.interfaces/IApplicationStartupHandler.cs
index cff7d25cb9..4e66135db0 100644
--- a/src/umbraco.interfaces/IApplicationStartupHandler.cs
+++ b/src/umbraco.interfaces/IApplicationStartupHandler.cs
@@ -14,6 +14,6 @@ namespace umbraco.interfaces
/// and bind to any custom events in the OnApplicationInitialized method.
///
[Obsolete("This interface is obsolete, use IApplicationEventHandler or ApplicationEventHandler instead")]
- public interface IApplicationStartupHandler
+ public interface IApplicationStartupHandler : IDiscoverable
{ }
}
diff --git a/src/umbraco.interfaces/ICacheRefresher.cs b/src/umbraco.interfaces/ICacheRefresher.cs
index d239e81fe6..642be660d3 100644
--- a/src/umbraco.interfaces/ICacheRefresher.cs
+++ b/src/umbraco.interfaces/ICacheRefresher.cs
@@ -7,7 +7,7 @@ namespace umbraco.interfaces
/// The IcacheRefresher Interface is used for loadbalancing.
///
///
- public interface ICacheRefresher
+ public interface ICacheRefresher : IDiscoverable
{
Guid UniqueIdentifier { get; }
string Name { get; }
diff --git a/src/umbraco.interfaces/IDataType.cs b/src/umbraco.interfaces/IDataType.cs
index 72d611a843..591057d76b 100644
--- a/src/umbraco.interfaces/IDataType.cs
+++ b/src/umbraco.interfaces/IDataType.cs
@@ -8,8 +8,8 @@ namespace umbraco.interfaces
/// And finally it contains IData which manages the actual data in the Data Type
///
[Obsolete("IDataType is obsolete and is no longer used, it will be removed from the codebase in future versions")]
- public interface IDataType
- {
+ public interface IDataType : IDiscoverable
+ {
///
/// Gets the id.
///
diff --git a/src/umbraco.interfaces/IDiscoverable.cs b/src/umbraco.interfaces/IDiscoverable.cs
new file mode 100644
index 0000000000..2c65122104
--- /dev/null
+++ b/src/umbraco.interfaces/IDiscoverable.cs
@@ -0,0 +1,8 @@
+namespace umbraco.interfaces
+{
+ ///
+ /// Marks a class or an interface as discoverable by PluginManager.
+ ///
+ public interface IDiscoverable
+ { }
+}
\ No newline at end of file
diff --git a/src/umbraco.interfaces/IMacroGuiRendering.cs b/src/umbraco.interfaces/IMacroGuiRendering.cs
index a9b1bdebe2..717b441889 100644
--- a/src/umbraco.interfaces/IMacroGuiRendering.cs
+++ b/src/umbraco.interfaces/IMacroGuiRendering.cs
@@ -6,8 +6,8 @@ namespace umbraco.interfaces
/// Summary description for IMacroGuiRendering.
///
[Obsolete("This interface is no longer used and will be removed from the codebase in future versions")]
- public interface IMacroGuiRendering
- {
+ public interface IMacroGuiRendering : IDiscoverable
+ {
///
/// Gets or sets the value.
///
diff --git a/src/umbraco.interfaces/IPackageAction.cs b/src/umbraco.interfaces/IPackageAction.cs
index aea727221f..b52be3a245 100644
--- a/src/umbraco.interfaces/IPackageAction.cs
+++ b/src/umbraco.interfaces/IPackageAction.cs
@@ -4,7 +4,8 @@ using System.Text;
using System.Xml;
namespace umbraco.interfaces {
- public interface IPackageAction {
+ public interface IPackageAction : IDiscoverable
+ {
bool Execute(string packageName, XmlNode xmlData);
string Alias();
bool Undo(string packageName, XmlNode xmlData);
diff --git a/src/umbraco.interfaces/ITree.cs b/src/umbraco.interfaces/ITree.cs
index 43cfe5d322..857c0a49ed 100644
--- a/src/umbraco.interfaces/ITree.cs
+++ b/src/umbraco.interfaces/ITree.cs
@@ -6,8 +6,8 @@ namespace umbraco.interfaces
///
/// Interface for created application trees in the umbraco backoffice
///
- public interface ITree
- {
+ public interface ITree : IDiscoverable
+ {
///
/// Sets the tree id.
///
diff --git a/src/umbraco.interfaces/umbraco.interfaces.csproj b/src/umbraco.interfaces/umbraco.interfaces.csproj
index 43a96244b1..dc246fc9bf 100644
--- a/src/umbraco.interfaces/umbraco.interfaces.csproj
+++ b/src/umbraco.interfaces/umbraco.interfaces.csproj
@@ -127,6 +127,7 @@
+