using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web.Compilation;
using Umbraco.Core.Cache;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Profiling;
using Umbraco.Core.PropertyEditors;
using umbraco.interfaces;
using File = System.IO.File;
namespace Umbraco.Core
{
///
/// Provides methods to find and instanciate types.
///
///
/// 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 string _cachedAssembliesHash = null;
private string _currentAssembliesHash = null;
private IEnumerable _assemblies;
private bool _reportedChange;
///
/// 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");
if (runtimeCache == null) throw new ArgumentNullException("runtimeCache");
if (logger == null) throw new ArgumentNullException("logger");
_serviceProvider = serviceProvider;
_runtimeCache = runtimeCache;
_logger = logger;
// the temp folder where the cache file lives
_tempFolder = IOHelper.MapPath("~/App_Data/TEMP/PluginCache");
if (Directory.Exists(_tempFolder) == false)
Directory.CreateDirectory(_tempFolder);
var pluginListFile = GetPluginListFilePath();
if (detectChanges)
{
//first check if the cached hash is string.Empty, if it is then we need
//do the check if they've changed
RequiresRescanning = (CachedAssembliesHash != CurrentAssembliesHash) || CachedAssembliesHash == string.Empty;
//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
// rescanning of all plugin types including lazy ones.
// http://issues.umbraco.org/issue/U4-4789
File.Delete(pluginListFile);
WriteCachePluginsHash();
}
}
else
{
// 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)
RequiresRescanning = true;
}
}
///
/// Gets or sets the set of assemblies to scan.
///
///
/// 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 _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);
profilingLogger = new ProfilingLogger(logger, profiler);
}
else
{
runtimeCache = ApplicationContext.Current.ApplicationCache.RuntimeCache;
profilingLogger = ApplicationContext.Current.ProfilingLogger;
}
return new PluginManager(new ActivatorServiceProvider(), runtimeCache, profilingLogger);
});
}
set
{
_hasCurrent = true;
_current = value;
}
}
#region Hashing
///
/// 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; }
///
/// Gets the currently cached hash value of the scanned assemblies.
///
/// The cached hash value, or string.Empty if no cache is found.
internal string CachedAssembliesHash
{
get
{
if (_cachedAssembliesHash != null)
return _cachedAssembliesHash;
var filePath = GetPluginHashFilePath();
if (File.Exists(filePath) == false) return string.Empty;
var hash = File.ReadAllText(filePath, Encoding.UTF8);
_cachedAssembliesHash = hash;
return _cachedAssembliesHash;
}
}
///
/// Gets the current assemblies hash based on creating a hash from the assemblies in various places.
///
/// The current hash.
internal string CurrentAssembliesHash
{
get
{
if (_currentAssembliesHash != null)
return _currentAssembliesHash;
_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);
return _currentAssembliesHash;
}
}
///
/// Writes the assembly hash file.
///
private void WriteCachePluginsHash()
{
var filePath = GetPluginHashFilePath();
File.WriteAllText(filePath, CurrentAssembliesHash.ToString(), Encoding.UTF8);
}
///
/// Returns a unique hash for a combination of FileInfo objects.
///
/// 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 string GetFileHash(IEnumerable> filesAndFolders, ProfilingLogger logger)
{
using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined"))
{
// get the distinct file infos to hash
var uniqInfos = new HashSet();
var uniqContent = new HashSet();
using (var generator = new HashGenerator())
{
foreach (var fileOrFolder in filesAndFolders)
{
var info = fileOrFolder.Item1;
if (fileOrFolder.Item2)
{
// add each unique file's contents to the hash
// normalize the content for cr/lf and case-sensitivity
if (uniqContent.Add(info.FullName))
{
if (File.Exists(info.FullName) == false) continue;
var content = RemoveCrLf(File.ReadAllText(info.FullName));
generator.AddCaseInsensitiveString(content);
}
}
else
{
// add each unique folder/file to the hash
if (uniqInfos.Add(info.FullName))
{
generator.AddFileSystemItem(info);
}
}
}
return generator.GenerateHash();
}
}
}
// 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 string GetFileHash(IEnumerable filesAndFolders, ProfilingLogger logger)
{
using (logger.TraceDuration("Determining hash of code files on disk", "Hash determined"))
{
using (var generator = new HashGenerator())
{
// get the distinct file infos to hash
var uniqInfos = new HashSet();
foreach (var fileOrFolder in filesAndFolders)
{
if (uniqInfos.Contains(fileOrFolder.FullName)) continue;
uniqInfos.Add(fileOrFolder.FullName);
generator.AddFileSystemItem(fileOrFolder);
}
return generator.GenerateHash();
}
}
}
#endregion
#region Cache
private const int ListFileOpenReadTimeout = 4000; // milliseconds
private const int ListFileOpenWriteTimeout = 2000; // milliseconds
///
/// 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
{
return ReadCache();
}
catch
{
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 = GetFileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, ListFileOpenReadTimeout))
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.
///
/// 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()
{
var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".list";
return Path.Combine(_tempFolder, filename);
}
private string GetPluginHashFilePath()
{
var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".hash";
return Path.Combine(_tempFolder, filename);
}
internal void WriteCache()
{
var filePath = GetPluginListFilePath();
using (var stream = GetFileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout))
using (var writer = new StreamWriter(stream))
{
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();
}
}
}
internal void UpdateCache()
{
// 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();
}
private static Stream GetFileStream(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, int timeoutMilliseconds)
{
const int pauseMilliseconds = 250;
var attempts = timeoutMilliseconds / pauseMilliseconds;
while (true)
{
try
{
return new FileStream(path, fileMode, fileAccess, fileShare);
}
catch
{
if (--attempts == 0)
throw;
LogHelper.Debug(string.Format("Attempted to get filestream for file {0} failed, {1} attempts left, pausing for {2} milliseconds", path, attempts, pauseMilliseconds));
Thread.Sleep(pauseMilliseconds);
}
}
}
#endregion
#region Create Instances
///
/// Resolves and creates instances.
///
/// 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)
{
var types = ResolveTypes(cache, specificAssemblies);
return CreateInstances(types, throwException);
}
///
/// Creates instances of the specified types.
///
/// 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);
}
///
/// 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();
}
#endregion
#region Resolve Types
///
/// Resolves class types inheriting from or implementing the specified type
///
/// 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)
{
// 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)
{
return ResolveTypesInternal(
typeof (T), null,
() => TypeFinder.FindClassesOfType(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), 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)
{
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)
{
_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);
}
///
/// Gets the types.
///
public IEnumerable Types
{
get { return _types; }
}
}
///
/// 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();
}
}
}