using Pilz.Text; using System.Reflection; namespace Pilz.Plugins; public class PluginManager where TPluginInterface : class where TPluginRuntimeInfo : PluginRuntimeInfo { protected readonly List loadedPlugins = []; /// /// The default initialization parameters used when no parameters passed. Commonly used for dynamically loading assemblies. /// public virtual object?[] DefaultParameters { get; set; } = []; /// /// Returns a list of all currently loaded plugins. /// public virtual IEnumerable LoadedPlugins => loadedPlugins.AsReadOnly(); protected void OnCurrentAppDomainAssemblyLoad(object? sender, AssemblyLoadEventArgs args) { LoadPlugins(args.LoadedAssembly, null); } /// /// Loads assembly from the given file paths and then loads the plugins from the assemblies. /// /// /// /// public virtual IEnumerable> LoadPlugins(string[] paths, object?[]? parameters) { var results = new List>(); foreach (var path in paths) results.Add(LoadPlugin(path, parameters)); return results; } /// /// Loads plugins from the given assemblies. /// /// /// /// public virtual IEnumerable> LoadPlugins(Assembly[] assemblies, object?[]? parameters) { var results = new List>(); foreach (var assembly in assemblies) results.Add(LoadPlugins(assembly, parameters)); return results; } /// /// Loads plugins from already loaded assemblies for the current . /// /// public virtual IEnumerable> LoadOwnPlugins(object?[]? parameters, string? whitelist) { return LoadOwnPlugins(false, parameters, whitelist); } /// /// Loads plugins from already loaded assemblies for the current . /// /// Do also load plugins from all yet not loaded assemblies by listening the event /// >. /// >. /// public virtual IEnumerable> LoadOwnPlugins(bool listenAssemblyLoadContext, object?[]? parameters, string? whitelist) { var results = new List>(); if (listenAssemblyLoadContext) AppDomain.CurrentDomain.AssemblyLoad += OnCurrentAppDomainAssemblyLoad; var assemblies = AppDomain.CurrentDomain.GetAssemblies().AsEnumerable(); if (!string.IsNullOrWhiteSpace(whitelist)) assemblies = assemblies.Where(n => LikeOperator.IsLike(n.GetName().Name, whitelist)); foreach (var assembly in assemblies) results.Add(LoadPlugins(assembly, parameters)); return results; } /// /// Loads an assembly from the given file path and then loads plugins from the assembly. /// /// /// /// public virtual PluginLoadInfo LoadPlugin(string path, object?[]? parameters) { var result = new PluginLoadInfo(); var loadContext = new PluginLoadContext(path); if (File.Exists(path)) { try { result.Assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(path))); } catch { result.Status = PluginLoadStatus.ErrorAtLoading; } } else result.Status = PluginLoadStatus.FileNotFound; if (result.Assembly != null) LoadPlugin(result, parameters); return result; } /// /// Load plugins from a given assembly. /// /// /// /// public virtual PluginLoadInfo LoadPlugins(Assembly assembly, object?[]? parameters) { var result = new PluginLoadInfo { Assembly = assembly }; LoadPlugin(result, parameters); return result; } /// /// Loads the plugin from a given type. ///
Commonly used to ensure loading core plugins. ///
/// /// /// public virtual PluginLoadInfo LoadPlugin(object?[]? parameters) where TPlugin : TPluginInterface { var result = new PluginLoadInfo { Assembly = typeof(TPlugin).Assembly }; LoadPlugin(result, typeof(TPlugin), parameters); return result; } protected virtual void LoadPlugin(PluginLoadInfo result, object?[]? parameters) { if (result.Assembly == null) result.Status = PluginLoadStatus.NoValidPlugin; //else if (loadedPlugins.Any(n => n.Assembly == result.Assembly)) // result.Status = PluginLoadStatus.AlreadyLoaded; else { foreach (var type in result.Assembly.GetTypes()) LoadPlugin(result, type, parameters); } if (result.PluginsInternal.Count == 0) result.Status = PluginLoadStatus.NoValidPlugin; } protected virtual void LoadPlugin(PluginLoadInfo result, Type type, object?[]? parameters) { if (loadedPlugins.Any(n => n.Plugin != null && n.Plugin.GetType() == type)) result.Status = PluginLoadStatus.AlreadyLoaded; else if (type.IsAssignableTo(typeof(TPluginInterface))) { var info = Activator.CreateInstance(); info.Assembly = result.Assembly; result.PluginsInternal.Add(info); try { if (Activator.CreateInstance(type, GetConstructorFlags(), null, GetParameters(type, parameters), null) is TPluginInterface plugin) { if (plugin is IPluginLateInitialization pluginLateInit) pluginLateInit.LateInit(); info.Plugin = plugin; info.Status = PluginStatus.Success; loadedPlugins.Add(info); } } catch (Exception ex) { info.Exception = ex; info.Status = PluginStatus.ErrorAtInitializing; } } else result.Status = PluginLoadStatus.NoValidPlugin; } protected virtual object?[]? GetParameters(Type type, object?[]? parameters) { if (parameters == null || parameters.Length == 0) return DefaultParameters; return parameters; } protected virtual BindingFlags GetConstructorFlags() { return BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance; } }