using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net.Http.Headers; using System.Reflection; using System.Reflection.Metadata.Ecma335; using System.Text; using System.Threading.Tasks; 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); } /// /// Loads assembly from the given file paths and then loads the plugins from the assemblies. /// /// /// /// public virtual IEnumerable> LoadPlugins(string[] paths, params 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, params 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(params object?[]? parameters) { return LoadOwnPlugins(false, parameters); } /// /// 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, params object?[]? parameters) { var results = new List>(); if (listenAssemblyLoadContext) AppDomain.CurrentDomain.AssemblyLoad += OnCurrentAppDomainAssemblyLoad; foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 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, params 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, params object?[]? parameters) { var result = new PluginLoadInfo { Assembly = assembly }; LoadPlugin(result); return result; } /// /// Loads the plugin from a given type. ///
Commonly used to ensure loading core plugins. ///
/// /// /// public virtual PluginLoadInfo LoadPlugin(params 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, params 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, params object?[]? parameters) { if (parameters == null || parameters.Length == 0) parameters = DefaultParameters; 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, null, 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 BindingFlags GetConstructorFlags() { return BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance; } } }