Files
DP44/enriched-qwen3-coder-next/Common/DTS.Common.Core/PluginLib.md
2026-04-17 14:55:32 -04:00

7.5 KiB
Raw Blame History

source_files, generated_at, model, schema_version, sha256
source_files generated_at model schema_version sha256
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
2026-04-16T02:06:04.645753+00:00 Qwen/Qwen3-Coder-Next-FP8 1 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.