Files
2026-04-17 14:55:32 -04:00

112 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
source_files:
- Common/DTS.Common.Core/PluginLib/PluginConfigData.cs
- Common/DTS.Common.Core/PluginLib/PluginConfig.cs
- Common/DTS.Common.Core/PluginLib/PluginConfigSectionHandler.cs
- Common/DTS.Common.Core/PluginLib/PluginManager.cs
generated_at: "2026-04-16T02:06:04.645753+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "698a84042cfeb418"
---
# PluginLib
## Documentation: Plugin Configuration and Management Module
---
### 1. **Purpose**
This module provides infrastructure for loading, configuring, and resolving plugins using the Managed Extensibility Framework (MEF). It reads plugin folder paths from a custom configuration section (`DTS.Common.Core.PluginLib.Config`) in `App.config`, initializes MEF catalogs and containers over those directories, and exposes static methods to retrieve plugin instances by exported contract type. It supports both singleton and multiple-plugin resolution patterns, and includes custom assembly resolution logic to handle dependencies across plugin directories.
---
### 2. **Public Interface**
#### `PluginConfigData`
- **`string[] PluginFolders`**
XML-serialized array of plugin folder paths, populated from `<Folder>` elements under the `<DatPro.Core.PluginLib.Config>` root in `App.config`.
#### `PluginConfig`
- **`const string DTSPlugins = "DTSPlugins"`**
Hardcoded configuration key name used to retrieve the base plugin namespace prefix from `AppSettings`.
- **`string GetDTSPluginsSetting(string setting)`**
Concatenates the value of the `"DTSPlugins"` app setting (retrieved via `DTSConfig.GetAppSetting`) with the provided `setting` using `"."` as separator.
*Example:* If `"DTSPlugins"` = `"DTS.Equipment"`, then `GetDTSPluginsSetting("Exporter")` returns `"DTS.Equipment.Exporter"`.
#### `PluginManager`
- **`AggregateCatalog PluginCatalog { get; set; }`**
MEF catalog aggregating plugin assemblies from configured directories.
- **`CompositionContainer PluginContainer { get; set; }`**
MEF composition container built from `PluginCatalog`.
- **`static T GetPlugin<T>() where T : class`**
Returns a single exported instance of type `T`. Throws if zero or more than one export of `T` exists.
- **`static T GetPlugin<T>(string configPluginSetting) where T : class`**
Returns a specific exported instance of type `T`, selected by matching `item.Value.ToString()` to `configPluginSetting`.
*Note:* This relies on the plugin instances `ToString()` returning the expected identifier.
- **`static IEnumerable<Lazy<T>> GetPlugins<T>() where T : class`**
Returns all exported instances of type `T`.
- **`List<Assembly> GetPluginList<T>() where T : class`**
Returns a deduplicated list of assemblies containing MEF parts (plugin types), *not* necessarily those that export `T`.
*Implementation detail:* Iterates over `DirectoryCatalog.Parts`, extracts the `Assembly` from each parts type, and returns distinct entries.
- **`static PluginManager GetPluginManager(string appPath)`**
Returns the singleton `PluginManager` instance. Initializes it once per process if not already created.
*Note:* `appPath` is validated (as `DirectoryInfo`) but not used beyond that in the current implementation.
#### `PluginConfigSectionHandler`
- **`FilterHashKeyCollection HashKeys { get; }`**
Configuration property mapping to the `PluginFolders` element in the custom config section. Contains `FilterHashElement` entries.
#### `FilterHashKeyCollection`
- **`FilterHashElement this[int idx] => (FilterHashElement)BaseGet(idx);`**
Indexer to access `FilterHashElement` items by zero-based index.
#### `FilterHashElement`
- **`string Key { get; set; }`**
Required, key-only configuration property (e.g., unused in current logic).
- **`string Value { get; set; }`**
Optional string value, used in `PluginManager` constructor as the plugin directory path.
---
### 3. **Invariants**
- **Singleton enforcement:** `PluginManager` is a singleton; only one instance exists per AppDomain, enforced via `lock(THREAD_LOCK)` in `GetPluginManager`.
- **Configuration dependency:** `PluginManager` constructor *requires* the `DTS.Common.Core.PluginLib.Config` section to be present in `App.config`; otherwise, it logs and throws.
- **Directory existence:** Each `FilterHashElement.Value` must point to an existing directory; otherwise, an `IOException` is thrown during initialization.
- **Assembly loading constraints:** Assemblies named with prefixes `"DTS.Common"`, `"C1"`, or `"Xceed"` are *excluded* from explicit `Assembly.LoadFrom` calls in the constructor (though they may still be loaded via MEF or `AssemblyResolve`).
- **MEF contract resolution:** `GetPlugin<T>()` expects exactly one export of `T`; otherwise, MEF throws (not explicitly handled in code).
- **Plugin selection via `ToString()`:** `GetPlugin<T>(string)` matches plugins by `item.Value.ToString() == configPluginSetting`, which is fragile and undocumented in plugin contracts.
---
### 4. **Dependencies**
#### **Imports / Uses**
- `DTS.Common.Core.Config.DTSConfig` — for reading config sections (`GetSection`) and app settings (`GetAppSetting`).
- `DTS.Common.Utilities.Logging.APILogger` — for logging errors and warnings.
- `System.ComponentModel.Composition.*` — MEF types (`AggregateCatalog`, `CompositionContainer`, `DirectoryCatalog`, `ReflectionModelServices`).
- `System.Configuration` — for `ConfigurationSection`, `ConfigurationElementCollection`, `ConfigurationElement`.
#### **Consumers (inferred)**
- Any module requiring plugin discovery/resolution (e.g., equipment exporters, data processors) likely calls `PluginManager.GetPlugin<T>()` or `GetPlugins<T>()`.
- Configuration infrastructure (`DTSConfig`) must register the `DTS.Common.Core.PluginLib.Config` section handler (not shown here).
---
### 5. **Gotchas**
- **`appPath` parameter in `GetPluginManager(string appPath)` is unused after validation.** The constructor uses `DTSConfig.DTSConfigInit(appPath)`, but subsequent calls to `GetPluginManager` ignore `appPath` entirely — only the first calls `appPath` matters.
- **`GetPlugin<T>(string)` uses `ToString()` for identification**, which is unreliable and not part of MEFs contract model. Plugins must override `ToString()` to return the expected identifier — a hidden coupling.
- **Assembly filtering is hardcoded and incomplete:** Exclusion of `"DTS.Common"`, `"C1"`, `"Xceed"` is done via string prefix on filename, not strong name or version — may break if filenames change or include versioned suffixes.
- **Redundant assembly loading:** The constructor calls `Assembly.LoadFrom` for each `.dll` *after* adding `DirectoryCatalog` (which loads assemblies lazily). This may cause duplicate loads or conflicts.
- **`GetPluginList<T>()` ignores the generic constraint `T`** — it returns *all* plugin assemblies, not just those exporting `T`.
- **`CurrentDomain_AssemblyResolve` is defined but never subscribed.** The event handler is unused — assembly resolution failures may occur if plugins depend on other plugins not in the GAC.
- **No cleanup/disposal logic:** `PluginCatalog` and `PluginContainer` are not disposed, risking resource leaks in long-running processes.
- **Thread-safety is partial:** While `GetPluginManager` is thread-safe, `PluginCatalog` and `PluginContainer` are mutable properties — external modification after initialization is possible (though discouraged by design).
- **`FilterHashElement.Key` is unused** — present in config schema but never referenced in code.
---
*Documentation generated from provided source files. No behavior inferred beyond explicit code.*