--- 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 `` elements under the `` 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() 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(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> GetPlugins() where T : class`** Returns all exported instances of type `T`. - **`List GetPluginList() 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()` expects exactly one export of `T`; otherwise, MEF throws (not explicitly handled in code). - **Plugin selection via `ToString()`:** `GetPlugin(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()` or `GetPlugins()`. - 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(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()` 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.*