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; 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 Umbraco.Core.Configuration; 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 if(File.Exists(pluginListFile)) 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 if (File.Exists(pluginListFile)) 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() { switch (GlobalSettings.LocalTempStorageLocation) { case LocalTempStorage.AspNetTemp: return Path.Combine(HttpRuntime.CodegenDir, "umbraco-plugins.list"); case LocalTempStorage.EnvironmentTemp: var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoPlugins", //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not // utilizing an old path appDomainHash); return Path.Combine(cachePath, "umbraco-plugins.list"); case LocalTempStorage.Default: default: return Path.Combine(_tempFolder, "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".list"); } } private string GetPluginHashFilePath() { var filename = "umbraco-plugins." + NetworkHelper.FileSafeMachineName + ".hash"; return Path.Combine(_tempFolder, filename); } internal void WriteCache() { // be absolutely sure if (Directory.Exists(_tempFolder) == false) Directory.CreateDirectory(_tempFolder); 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(); } } }