112 lines
7.5 KiB
Markdown
112 lines
7.5 KiB
Markdown
---
|
||
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 instance’s `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 part’s 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 call’s `appPath` matters.
|
||
- **`GetPlugin<T>(string)` uses `ToString()` for identification**, which is unreliable and not part of MEF’s 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.* |