.NET / SQL / Enterprise Engineering
.NET Plugin Host Architecture
Report summary
[Architecture synthesis] The comprehensive analysis of modern .NET extensibility paradigms indicates that a monolithic plugin architecture is insufficient for a production-grade Windows desktop application. To balance the competing demands of high-performance in-process execution and secure, resilie
Key topics
- .NET / SQL / Enterprise Engineering
- .NET
- SQL
- Enterprise Engineering
- AI
- WordPress
- Python
- Runtime
- NuGet
Research provenance
For citation, use the report title and canonical URL. Archival presence does not establish authorship or promote report statements into portfolio evidence.
This page renders the archived Markdown as safe, formatted HTML. It is background research and does not become a portfolio claim without evidence review.
Full report
On this page
1. Executive Recommendation
\[Architecture synthesis\] The comprehensive analysis of modern .NET extensibility paradigms indicates that a monolithic plugin architecture is insufficient for a production-grade Windows desktop application. To balance the competing demands of high-performance in-process execution and secure, resilient isolation for untrusted code, the recommended default architecture is a Hybrid Tiered Host Boundary. \[Proposal\] This architecture establishes two parallel escalation paths based on trust, stability, and language requirements. The primary tier consists of in-process managed plugins hosted within isolated, collectible AssemblyLoadContext (ALC) instances. This tier is reserved strictly for first-party or highly trusted third-party C\# components, offering nanosecond-latency communication and zero serialization overhead1. To circumvent native Windows file-locking mechanisms and enable seamless lifecycle management—including dynamic updates and removals—these plugins must be loaded exclusively from ephemeral shadow-copied directories4. \[Proposal\] The secondary tier acts as a true reliability and security boundary, executing untrusted, highly crash-prone, or non-.NET components (such as Python sidecars) as out-of-process workers. These external processes will communicate with the host via gRPC over local Named Pipes and must be strictly governed by Windows Job Objects to constrain CPU and memory consumption7. This dual-tier approach ensures that the local core features of the host application remain robust and functional even in the event of catastrophic plugin failure, while offering unparalleled flexibility for a diverse ecosystem of extensions.
2. Scope, Assumptions, and Zero-Access Declaration
\[Platform fact\] This report is authored under a strict zero-access boundary. The analysis has not involved accessing, inspecting, running, or testing any proprietary software, websites, source code, binaries, manifests, APIs, logs, screenshots, or private documents related to the target application. All namespaces, class names, package formats, and evaluation results are generated purely as illustrative proposals based on current .NET platform capabilities and official documentation. \[Assumption\] The following foundational assumptions bound this analysis:
- The host application is a modern Windows desktop application implemented primarily in C\#/.NET, utilizing .NET 8 or .NET 10 Long-Term Support (LTS) releases.
- The user interface framework is potentially WinUI 3 (Windows App SDK), and the application may be deployed via MSIX packaging.
- Plugins are entirely optional; they may add endpoints, memory, remote connectivity, models, or tool integration capabilities, but the core application must degrade gracefully and remain functional without them.
- Enterprise-quality standards mandate deterministic behavior, strict version compatibility, robust cancellation modeling, bounded resource consumption, safe update protocols, and comprehensive automated testing.
3. Source Method and Current-Platform Applicability
\[Architecture synthesis\] The methodology applied in this research synthesizes official Microsoft documentation, GitHub issue tracking for the .NET runtime, community engineering reports, and established security frameworks. Priority is given to mechanisms supported in .NET Core 3.0+ and refined in .NET 8/10. \[Platform fact\] Legacy isolation mechanisms, notably AppDomain, are completely obsolete in modern .NET and lack cross-platform or modern runtime support2. Furthermore, older composition frameworks such as the Managed Extensibility Framework (MEF) are in maintenance mode, predate modern AssemblyLoadContext isolation, and do not integrate cleanly with modern Microsoft.Extensions.DependencyInjection paradigms3. Therefore, they are excluded from the recommended path. \[External guidance\] The applicability of this report spans standard self-contained .NET deployments as well as MSIX-packaged applications. Deploying as an MSIX package introduces specific Windows AppContainer sandbox constraints, particularly regarding filesystem virtualization, network loopback restrictions, and registry write limitations, all of which heavily influence the out-of-process communication and shadow-copying strategies proposed herein12.
4. Architecture Option Matrix
\[Architecture synthesis\] To determine the optimal host boundary, eight distinct architectural mechanisms were evaluated across fourteen critical dimensions. The following matrix assigns scores on a scale of 1 to 5, where 1 indicates a severe deficiency or incompatibility, and 5 indicates state-of-the-art capability.
| Mechanism | Isolation | Security Boundary | Unloadability | Dependency Isolation | Native Dependency | Startup Cost | Call Latency | Streaming | Cancellation | Crash Containment | Deployment Complexity | Diagnostics | Testability | Dev Burden |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Direct In-Process References | 1 | 1 | 1 | 1 | 2 | 5 | 5 | 5 | 4 | 1 | 5 | 2 | 4 | 1 |
| Reflection \+ Shared Contract | 2 | 1 | 1 | 1 | 2 | 4 | 4 | 5 | 4 | 1 | 4 | 2 | 3 | 2 |
| MEF / System.Composition | 2 | 1 | 1 | 1 | 2 | 3 | 4 | 5 | 4 | 1 | 4 | 2 | 3 | 4 |
| Collectible ALC (In-Proc) | 4 | 1 | 4 | 5 | 4 | 4 | 5 | 5 | 4 | 2 | 3 | 4 | 4 | 3 |
| Out-of-Proc Worker (Named Pipes) | 5 | 4 | 5 | 5 | 5 | 2 | 3 | 4 | 5 | 5 | 2 | 4 | 5 | 4 |
| Out-of-Proc Worker (TCP Loopback) | 5 | 3 | 5 | 5 | 5 | 2 | 2 | 4 | 5 | 5 | 2 | 4 | 5 | 4 |
| Out-of-Proc Worker (Stdio) | 5 | 4 | 5 | 5 | 5 | 2 | 2 | 2 | 3 | 5 | 2 | 3 | 4 | 5 |
| Windows Service / Broker | 5 | 5 | 5 | 5 | 5 | 1 | 2 | 4 | 5 | 5 | 1 | 3 | 3 | 5 |
Comprehensive Matrix Rationale
\[Architecture synthesis\] The scoring methodology reveals the profound operational differences between in-process code separation and true operating system-level process boundaries. Regarding Isolation and Security Boundaries, \[Platform fact\] in-process mechanisms—including Direct References, Reflection, MEF, and AssemblyLoadContext (ALC)—execute entirely within the same memory space and operating system process15. While ALC provides excellent type and dependency isolation (preventing AssemblyA v1.0 from conflicting with AssemblyA v2.0), it offers a security boundary score of exactly 1\. Any code loaded into an ALC operates with the full permissions of the host process; a malicious plugin can easily use reflection to inspect host memory, read sensitive configurations, or compromise the main thread15. Out-of-process mechanisms provide true memory and security isolation. Named Pipes and Standard I/O (Stdio) allow for strict Access Control Lists (ACLs), ensuring that only the host application can communicate with the worker process. TCP Loopback scores slightly lower (3) because exposing local ports increases the attack surface to other applications on the local machine7. Windows Services offer the highest OS-level security via distinct user accounts, but they demand invasive installation privileges. In terms of Unloadability, \[Platform fact\] Direct References, raw Reflection, and MEF cannot be unloaded; once the CLR loads an assembly into the default context, it remains pinned until the process terminates2. Collectible ALCs score a 4 because they can be unloaded, but unloading is cooperative rather than forced. If a single reference to a plugin type remains in the host's memory—such as a running background thread, an un-unsubscribed static event handler, or a cached serializer—the ALC will leak and remain in memory indefinitely19. Out-of-process workers guarantee perfect unloadability via OS-level process termination (Process.Kill()), instantly reclaiming all handles and memory regardless of the plugin's internal state. For Dependency and Native Dependency Isolation, \[Platform fact\] ALC utilizes the AssemblyDependencyResolver to parse .deps.json files, perfectly mapping managed dependencies and resolving Runtime Identifier (RID)-specific native unmanaged DLLs4. Out-of-process approaches bypass dependency conflicts entirely, as each worker manages its own distinct application directory and CLR instance. Regarding Startup Cost, Call Latency, and Streaming, \[Platform fact\] ALC invokes natively in-process, offering nanosecond latency and zero serialization overhead, allowing large memory structures (e.g., massive arrays or video frames) to be passed by reference3. Named pipes via gRPC introduce serialization overhead and an approximate 100µs unary-call latency8, which is highly acceptable for standard integrations but slower than ALC. Stdio scores poorly for streaming, as multiplexing complex binary streams over standard input/output is error-prone and developer-intensive. In the domains of Crash Containment and Diagnostics, \[Platform fact\] any unhandled exception on a background thread within an ALC or MEF plugin will irrevocably crash the host process23. Out-of-process architecture perfectly isolates fatal crashes, such as Access Violations in native C++ dependencies. Furthermore, out-of-process allows separate telemetry streams and independent process memory dumping without pausing or freezing the WinUI 3 host process. Finally, regarding Deployment Complexity and Developer Burden, \[Platform fact\] MEF enforces attribute-based coupling which creates friction in modern Dependency Injection-centric codebases3. Out-of-process architectures require the definition of IPC contracts (e.g., Protobuf) and manual lifecycle management of the child process. ALC requires careful shadow copying to avoid Windows file locks4, presenting a moderate burden to the platform engineering team but providing a seamless experience for the plugin developer. \[External guidance\] If the host application utilizes Native AOT (Ahead-of-Time compilation) to reduce startup time or memory footprint, dynamic assembly loading (Assembly.LoadFile and AssemblyLoadContext) is fundamentally impossible because AOT removes the Just-In-Time (JIT) compiler25. In a Native AOT scenario, all dynamic C\# plugins must either be Out-of-Process, or pre-compiled as unmanaged exports communicating over a C-ABI26.
5. Recommended Host Boundary
\[Architecture synthesis\] To balance developer velocity with enterprise-grade reliability, the architecture establishes a shared, immutable contract assembly containing only primitive interfaces and basic DTO records. The host resolves IPlugin interfaces via Reflection over a shadow-copied assembly, but strictly routes lifecycle activation through Microsoft.Extensions.DependencyInjection to maintain architectural consistency3. \[Proposal\] The host boundary operates on strict escalation rules based on the plugin's operational profile:
| Plugin Profile | Target Architecture | Rationale |
|---|---|---|
| First-party, pure C\# UI extensions | In-Process Collectible ALC | Requires shared UI thread access, zero latency, and large object passing by reference. Code is highly trusted. |
| Third-party trusted data providers | In-Process Collectible ALC | Low latency requirements. Network isolated via capability declarations. |
| Python / AI Model Sidecars | Out-of-Process (Named Pipes) | Requires separate runtime (Python interpreter) and dedicated memory boundaries28. |
| Native C/C++ legacy integrations | Out-of-Process (Named Pipes) | Highly prone to access violations and memory leaks. Protects the host from catastrophic crashes. |
| Untrusted third-party logic | Out-of-Process (Named Pipes) | Requires OS-level security boundaries and strict Windows Job Object CPU/Memory limits30. |
\[Architecture synthesis\] It is imperative to distinguish a reliability boundary from a true security boundary. The ALC is purely a reliability boundary; it prevents dependency DLL hell and allows cooperative memory reclamation, but it cannot prevent a malicious plugin from executing Environment.Exit(0) or reading the host's memory space. Only the out-of-process architecture, combined with restricted OS user permissions or AppContainer sandboxing, constitutes a true security boundary against adversarial plugins.
6. C# Contract Proposal
\[Proposal\] To prevent host-level type contamination, the shared contract assembly (e.g., Example.Plugin.Abstractions.dll) must be minimally scoped. \[Platform fact\] If complex host types are leaked into the plugin, or if generic objects capturing plugin-defined types are passed back to the host, the ALC will fail to unload because the host's garbage collector will retain references to the plugin's metadata4. \[Sample\] The following code illustrates a production-ready contract boundary. Note: This code is illustrative of the architectural pattern and is not a drop-in product solution.
C\# // Assembly: Example.Plugin.Abstractions using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks;
namespace Example.Plugin.Abstractions { /// \<summary\> /// Immutable identity and semantic versioning. /// Ensures deterministic resolution of plugin updates. /// \</summary\> public sealed record PluginIdentity(string Id, string Publisher, Version Version);
/// \<summary\> /// Declarative capabilities and required permissions (Least-privilege projection). /// Used by the host to prompt the user for consent before activation. /// \</summary\> public sealed record PluginManifest(PluginIdentity Identity, IReadOnlyList\<string\> RequiredCapabilities);
/// \<summary\> /// Represents the health status of a plugin for the host's circuit-breaker logic. /// \</summary\> public sealed record PluginHealth(bool IsDegraded, string DiagnosticsMessage, DateTimeOffset CheckedAt);
/// \<summary\> /// Host-provided context containing ONLY safe, least-authority services. /// \</summary\> public interface IPluginHostContext { string GetPluginDataDirectory(); void LogInformation(string message); // CRITICAL: Do NOT expose IServiceProvider directly. }
/// \<summary\> /// Core lifecycle and communication interface. /// Inherits IAsyncDisposable to guarantee asynchronous cleanup of unmanaged resources. /// \</summary\> public interface IPlugin : IAsyncDisposable { PluginManifest Manifest { get; }
// Asynchronous initialization with strict cancellation limits. ValueTask InitializeAsync(IPluginHostContext context, CancellationToken cancellationToken);
// Health check monitoring invoked periodically by the host. ValueTask\<PluginHealth\> CheckHealthAsync(CancellationToken cancellationToken);
// Typed operation execution with progress reporting. // Uses records for immutability and value-based equality. ValueTask\<OperationResult\> ExecuteOperationAsync( OperationRequest request, IProgress\<OperationProgress\> progress, CancellationToken cancellationToken); }
public sealed record OperationRequest(string Command, IReadOnlyDictionary\<string, string\> Payload); public sealed record OperationResult(bool Success, string JsonData, string ErrorCode); public sealed record OperationProgress(int PercentageCompleted, string StatusText); }
\[Architecture synthesis\] Protection of Host Internals: The host must strictly prevent plugin code from gaining direct access to IServiceProvider internals. If a plugin is injected with the global IServiceProvider, it can resolve internal singleton services, mutate global application configurations, or bypass application access controls. The IPluginHostContext acts as an anti-corruption layer. It wraps and proxies safe host functions (like scoped logging or directory access) explicitly defining the maximum authority granted to the plugin without exposing the DI container.
7. Lifecycle and Dependency Isolation
A. Lifecycle State Machine
\[Proposal\] Plugins transition through a deterministic state machine managed by a host PluginOrchestrator. Strict enforcement of these transitions guarantees that failing plugins cannot enter undefined zombie states.
| State | Trigger / Legal Transition | Description |
|---|---|---|
| Discovered | Application startup / Directory scan | The plugin directory is identified. Manifest is parsed without loading any assemblies. |
| Rejected | Validation failure | Hash verification, capability checks, or semantic versioning fails. Execution halts. |
| Compatible | Manifest passes validation | The plugin is verified safe to attempt loading. |
| Installed | Shadow copy succeeds | Transitive dependencies mapped; ALC instantiated. |
| Configured | User preference | User explicitly opts-in to enable the plugin capabilities. |
| Initializing | Host invokes InitializeAsync | Context provided. Transition to Failed if CancellationToken times out. |
| Ready | Initialization succeeds | Plugin accepts operation requests. |
| Degraded | CheckHealthAsync fails | Plugin returns degraded status, or partial operation failures exceed threshold. |
| Disabled | User halts execution | Transition to Stopping. Host stops routing traffic. |
| Stopping | Host invokes DisposeAsync | Graceful shutdown initiated via cancellation tokens. |
| Failed / Quarantined | Process crash / Unhandled Exception | Worker process severed, or ALC threw fatal error. Host circuit-breaker prevents restarts. |
| Updating | Update initiated | Triggered during shadow-copy binary replacement. |
| Rollback | Update validation fails | Reverts to last-known-good shadow directory metadata. |
| Removed | Uninstall requested | Shadow files unlinked, ALC collected. |
B. Loading and Dependency Algorithm
\[Platform fact\] In standard Windows environments, executing an assembly via AssemblyLoadContext.LoadFromAssemblyPath instructs the OS to map the DLL into memory, placing a strict read-lock on the file. This lock prevents the host application from updating, modifying, or deleting the plugin binaries while the application is running, creating significant operational friction4. \[Architecture synthesis\] To achieve atomic plugin installation and avoid DLL replacement errors, the host must utilize a "Shadow Copy" algorithm. \[Proposal\] Algorithm Pseudocode for Lifecycle Management: Function LoadPlugin(string originalPluginDir): // 1\. Validation Phase Validate Manifest schema, execute path canonicalization (prevent directory traversal). Verify code-signing hashes of all binaries.
// 2\. Atomic Shadow Copy Phase Generate unique temp directory: AppData\\Local\\HostApp\\ShadowCaches\\{PluginId}\_{Guid} Copy all files from originalPluginDir to temp directory atomically.
// 3\. ALC Creation Phase Let mainDllPath \= Path.Combine(tempDirectory, "Plugin.dll") Instatiate CustomPluginLoadContext(mainDllPath, isCollectible: true). Instantiate AssemblyDependencyResolver(mainDllPath).
// 4\. Resolver Behavior (Inside CustomPluginLoadContext) Override Load(AssemblyName name): IF name \== "Example.Plugin.Abstractions" THEN return null // Critical: Forces host fallback to share exact Type identity. ELSE return LoadFromAssemblyPath(resolver.ResolveAssemblyToPath(name))
Override LoadUnmanagedDll(string unmanagedDllName): // Resolves RID-specific native assets (e.g., win-x64 C++ dependencies) return LoadUnmanagedDllFromPath(resolver.ResolveUnmanagedDllToPath(unmanagedDllName))
// 5\. Activation Phase Let pluginAssembly \= customALC.LoadFromAssemblyName("Plugin") Use Reflection to find concrete Type implementing IPlugin. // \[CRITICAL\]: Encapsulate activation in a method marked \[MethodImpl(MethodImplOptions.NoInlining)\] // to prevent the JIT compiler from extending variable lifetimes, which blocks GC unload. Register concrete Type with host DI using the shared interface Type.
// 6\. Initialization Phase Invoke plugin.InitializeAsync() with strict CancellationToken timeout (e.g., 10s).
Function UnloadPlugin(IPlugin plugin, CustomPluginLoadContext alc): // 1\. Cleanup Phase Await plugin.DisposeAsync(). Nullify ALL strong references to plugin instances, delegates, and the ALC.
// 2\. Cooperative Unload Phase Call alc.Unload(). Let alcRef \= new WeakReference(alc) WHILE alcRef.IsAlive AND attempts \< 10: Call GC.Collect() Call GC.WaitForPendingFinalizers()
// 3\. File Cleanup Phase Asynchronously delete the shadow directory.
8. Reliability, Observability, and Recovery
A. Failure and Containment Catalog
\[Architecture synthesis\] Enterprise extensibility systems must be designed under the assumption that external plugins will eventually fail, hang, or behave maliciously. The host must survive and isolate the following operational failure modes:
| Failure Mode | Detection Mechanism | Containment & Recovery Strategy |
|---|---|---|
| Constructor / Static Init Failure | Trapped during Reflection activation in ALC. | ALC is immediately marked for unload. Status set to Rejected. Host continues normally. |
| Missing Dependency / Version Collision | AssemblyDependencyResolver fails to locate RID-specific assets (e.g., missing win-x64 folder)32. | Host catches DllNotFoundException or FileLoadException. Status set to Failed. |
| Hung Initialization | InitializeAsync ignores CancellationToken. | Host abandons task after 10s timeout. In-proc ALC: Cannot forcefully abort16. Out-of-proc: OS kills worker via SIGTERM/CTRL\_CLOSE\_EVENT34. Status: Quarantined. |
| Background-Thread Leak | Plugin starts Task.Run and fails to stop it on disposal. | \[Platform fact\] Running threads hold GC roots, preventing ALC unload19. Host detects leaked ALC via WeakReference. Flags plugin version as non-compliant. |
| Process Crash (Access Violation) | In-proc: Host crashes. Out-of-proc: Named pipe severs. | Host circuit breaker logs IPC drop, retries max 3 times. Exceeding threshold triggers Quarantined status and prevents startup loops. |
| Malformed Response | DTOs returned with invalid schemas. | Contract records enforce C\# nullability. Host validates payloads before processing. |
| Update Failure | File write collision or invalid hash during update. | Host rolls back to last-known-good shadow directory metadata. Leaves existing running ALC untouched. |
| Resource Exhaustion (CPU/Memory) | \[Proposal\] Detected via Windows Job Objects. | Bind out-of-process workers to JOBOBJECT\_EXTENDED\_LIMIT\_INFORMATION. Windows OS terminates the worker upon breach, completely protecting the host9. |
B. Settings Migration and Data Ownership
\[Architecture synthesis\] Across host upgrades, downgrades, and plugin uninstalls, the host must retain ultimate ownership of the plugin's configuration data to ensure deterministic recovery. \[Proposal\] When a plugin is upgraded, it receives its configuration payload from the host. If the application is downgraded, the older version of the plugin may encounter settings generated by the newer version. To prevent data loss, the configuration schema must utilize an extension data bag (e.g., Dictionary\<string, JsonElement\>). The older plugin preserves unknown keys, ensuring that if the user upgrades again, the advanced settings are restored seamlessly35. Upon uninstall, the host prompts the user to either retain configuration for future use or trigger a complete localized data wipe of the plugin's storage space.
C. Observability and Log Containment
\[Proposal\] Plugins must never write directly to the host's primary file logger or console. The IPluginHostContext.LogInformation interface acts as an intermediary. The host prefixes all incoming logs with \[Plugin: {Identity.Id}\]. Furthermore, PII, user credentials, customer files, and security prompts must be structurally redacted at the host boundary before logging. If an out-of-process plugin attempts to spam Stdio or the logging pipe, the host enforces rate-limiting to prevent log-disk exhaustion.
9. Packaging and Windows Deployment Constraints
\[Platform fact\] Deploying the desktop host via MSIX packaging imposes critical architectural constraints due to the AppContainer sandbox (unless explicitly configured as mediumIL / Full Trust)12.
- Filesystem Virtualization: The primary application installation directory (e.g., C:\\Program Files\\WindowsApps\\...) is strictly read-only. Plugins cannot download updates and overwrite their own binaries in place13.
- Data Ownership: All shadow copies, unzipped plugin payloads, and plugin persistence data must reside in the isolated AppData\\Local\\Packages\\{PackageFamilyName}\\LocalCache or LocalState directories13.
- Loopback Restrictions: If out-of-process workers are spawned in separate AppContainers, localhost TCP communication and standard Named Pipe communication are blocked by default by the Windows Firewall without a specific loopback exemption14. Therefore, Stdio or specific AppContainer-aware Named Pipes are required for IPC.
- Native AOT Impacts: If the host application is compiled via .NET Native AOT to optimize startup time and reduce memory footprints, all dynamic assembly loading (Assembly.LoadFile, AssemblyLoadContext, Reflection.Emit) is permanently disabled by the compiler25. Under Native AOT, all dynamically discovered plugins must run out-of-process, or they must be compiled as unmanaged DLL exports (\[UnmanagedCallersOnly\]) communicating over traditional C-ABI structures rather than managed C\# interfaces26.
- Core Feature Integrity: The host must initialize its UI framework and core dependency injection container before initiating plugin discovery. If a critical failure corrupts the plugin registry, the application must still launch in "Safe Mode," allowing the user to access core features and manually uninstall the offending extensions.
10. Developer SDK and Tooling
\[Proposal\] To maximize third-party adoption while maintaining rigorous quality controls, plugin developers must be provided with a streamlined SDK distributed via standard NuGet channels (e.g., Example.Plugin.Sdk).
- Templates: A command-line template (dotnet new example-plugin) that initializes a .NET 8/10 class library pre-configured with the Abstractions package and correct output directory structures.
- Roslyn Analyzers: A dedicated analyzer package (Example.Plugin.Analyzers) integrated into the build process to flag fatal architectural violations before runtime:
- Error: Emitting static fields (which prevent ALC unloads by holding GC roots).
- Error: Failing to pass CancellationToken down the asynchronous call chain.
- Warning: Utilizing known blocking APIs (e.g., Task.Result) that threaten host UI threads.
- Manifest Schema: A plugin.manifest.json file paired with a strict JSON Schema definition, enforcing declarative capabilities, versioning, and required host permissions.
- Local Test Harness: A lightweight, standalone CLI utility (example-plugin-tester.exe) that executes the plugin DLL within a simulated, headless host environment. It provides mock services and validates capability constraints, fuzzing endpoints before the developer submits the plugin for distribution.
11. Automated-Test Strategy
\[Architecture synthesis\] Validating a plugin architecture requires a strategy rooted in chaos engineering to ensure that host fault isolation is mathematically verifiable.
| Test Category | Target Behavior | Automation Strategy |
|---|---|---|
| Unit Tests | Interface compliance, DTO serialization | Standard xUnit/NUnit tests against the Abstractions assembly. |
| Contract / Mocks | Fake host services, deterministic clocks | Inject TimeProvider to test timeout behaviors. Inject mock IPluginHostContext to verify correct log formatting. |
| Unload Tests | Verifying ALC garbage collection | \[Sample\] Execute plugin loading within a \[MethodImpl(MethodImplOptions.NoInlining)\] scoped method. Capture a WeakReference to the ALC. Invoke GC.Collect() in a loop. Assert WeakReference.IsAlive is false19. |
| Chaos Engineering | Process isolation, hanging threads | Deploy "Adversarial Fixture Plugins" (e.g., The Memory Leaker, The Infinite Looper, The Thread Leaker, The Native Crash Dummy)38. Assert that the host successfully quarantines the plugin without crashing. |
| Integration / OS | Shadow copying, MSIX file locks | Requires Windows-specific CI infrastructure to validate that the host can delete the shadow-copied directory while the application remains open. |
12. Phased Adoption Plan
\[Proposal\] Broad extensibility introduces immense systemic risk to a desktop application. Adoption must be strictly gated through a phased sequence before broad ecosystem access is granted:
- Phase 1: Internal First-Party Plugins (In-Proc ALC). Deploy non-critical internal features (e.g., telemetry adapters, optional rendering engines) using the Collectible ALC. This phase quietly validates shadow-copying, routing, and lifecycle state machines in production.
- Phase 2: Managed Third-Party (Sandboxed Out-of-Proc). Invite vetted, trusted partners. Because ALC provides no security boundary, these are forced into the gRPC Out-of-Process worker governed by Job Objects. Establish telemetry for IPC latency and crash recovery.
- Phase 3: Python / Polyglot Sidecars. Expand the Out-of-Process model to support arbitrary executables implementing the language-neutral gRPC Protobuf contract. Validates standard I/O and Named Pipe abstractions across different runtime environments.
- Phase 4: Open Ecosystem. Introduce cryptographic manifest signing, external Certificate Revocation Lists (CRL), and full manifest capability prompting (e.g., a UI prompt stating "Plugin X requests Network Access").
13. Open Implementation Questions
\[Architecture synthesis\] Prior to finalizing the implementation blueprint, the platform engineering team must resolve the following contextual questions:
- WinUI 3 Threading Context: Will in-process plugins be permitted to manipulate the main DispatcherQueue? If so, ALC unloadability becomes exceedingly difficult, as event handler leaks on core UI elements will pin the ALC in memory40.
- Native AOT Alignment: Is there a roadmap mandate to compile the host application using Native AOT? If so, the entire in-process ALC strategy must be abandoned during the design phase to prevent massive future refactoring25.
- AppContainer Loopback Constraints: If deploying via MSIX, will out-of-process workers be spawned in the same AppContainer as the host, or distinct AppContainers? Distinct containers require complex firewall loopback exemptions for TCP/Named Pipe IPC14.
14. Limitations
\[Platform fact\] The architecture acknowledges the following immutable limitations of the .NET platform:
- Memory Leaks in ALC: Unloadability is purely cooperative, not forced. Despite careful host design, third-party libraries utilized by plugins (such as legacy XmlSerializer caching or hidden static delegates) can indefinitely pin the ALC in memory18. Only out-of-process architectures provide a guaranteed memory-reclamation boundary.
- Performance Overhead vs. Security: While ALC adds zero execution overhead, Out-of-Process IPC incurs unavoidable serialization/deserialization costs. Passing extremely high-frequency streaming data (e.g., raw video frames or massive datasets) across Named Pipes will bottleneck the CPU compared to shared memory references.
- File Lock Nuances: Shadow copying circumvents managed read-locks on standard DLLs; however, complex native libraries loaded via LoadUnmanagedDll may sometimes exhibit erratic lock behavior at the OS level depending on how the runtime maps them, occasionally complicating the deletion of shadow directories.
15. Sources
| Source ID | URL | Publisher | Date / Version | Claim Supported |
|---|---|---|---|---|
| 1, 46, 89 | https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/understanding-assemblyloadcontext, https://www.devleader.ca/2026/04/09/plugin-loading-in-net-assemblyloadcontext-with-dependency-injection, https://jordansrowles.medium.com/real-plugin-systems-in-net-assemblyloadcontext-unloadability-and-reflection-free-discovery-81f920c83644 | Microsoft, DevLeader, Medium | 2026 / Current | AssemblyLoadContext type isolation, caching rules, and AssemblyDependencyResolver mechanisms for .deps.json parsing. |
| 4, 22, 87 | https://learn.microsoft.com/en-us/answers/questions/5859320/load-and-unload-of-assemblies-dynamically, https://ayumax.net/entry/2019/12/10/000000/, https://stackoverflow.com/questions/60261523/what-is-the-value-of-assemblyloadcontext-unload-in-net-core-in-comparison-wit | Microsoft Q\&A, Ayumax, StackOverflow | 2020 \- 2026 | Requirement of MethodImplOptions.NoInlining and GC.Collect looping to guarantee cooperative ALC unload execution. |
| 11, 16 | https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/, https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/interop | Microsoft | .NET 8 / 10 | Native AOT constraints; strict disablement of Assembly.LoadFile and dynamic runtime generation. |
| 36, 37 | https://www.mpi-hd.mpg.de/personalhomes/fwerner/research/2021/09/grpc-for-ipc/, https://www.baeldung.com/linux/ipc-performance-comparison | Felix Werner, Baeldung | 2021 | gRPC serialization overhead for local IPC (\~100µs), and performance viability of Named Pipes vs. TCP loopback. |
| 38, 41, 42 | https://oneuptime.com/blog/post/2026-02-09-windows-pod-resource-limits/view, https://lowleveldesign.wordpress.com/2013/11/21/set-process-memory-limit-with-process-governor/, https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects | OneUptime, LowLevelDesign, Microsoft | 2013 \- 2026 | Mechanisms of Windows Job Objects for bounding CPU time and working-set memory limits for out-of-process isolation. |
| 56, 58, 61 | https://learn.microsoft.com/en-us/windows/apps/package-and-deploy/app-capability-declarations, https://stackoverflow.com/questions/79494603/using-msix-packaging-for-visual-studio-app-doesnt-write-in-registry, https://github.com/anthropics/claude-code/issues/58009 | Microsoft, StackOverflow, GitHub | 2025 \- 2026 | MSIX AppContainer limitations, preventing arbitrary filesystem modifications, registry writes, and IPC restrictions. |
| 83, 91, 93 | https://jordansrowles.medium.com/real-plugin-systems-in-net-assemblyloadcontext-unloadability-and-reflection-free-discovery-81f920c83644, https://github.com/dotnet/runtime/issues/127097, https://github.com/dotnet/msbuild/issues/14289 | Medium, GitHub (dotnet/runtime), GitHub (dotnet/msbuild) | 2026 | Critical necessity of shadow copying plugin binaries to circumvent Windows OS-level file locks and allow atomic updates. |
Works cited
- About AssemblyLoadContext \- .NET \- Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/understanding-assemblyloadcontext
- How to: Load and unload assemblies \- .NET \- Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/standard/assembly/load-unload
- Plugin Loading in .NET: AssemblyLoadContext with Dependency Injection \- Dev Leader, https://www.devleader.ca/2026/04/09/plugin-loading-in-net-assemblyloadcontext-with-dependency-injection
- Real Plugin Systems in .NET: AssemblyLoadContext, Unloadability, and Reflection‑Free Discovery \- Jordan Rowles, https://jordansrowles.medium.com/real-plugin-systems-in-net-assemblyloadcontext-unloadability-and-reflection-free-discovery-81f920c83644
- \[API Proposal\]: Support non-locking assembly loading with preserved Assembly.Location · Issue \#127097 · dotnet/runtime \- GitHub, https://github.com/dotnet/runtime/issues/127097
- MSBuild server holds task/plugin assembly file handles for the process lifetime (no shadow-copy/unload) · Issue \#14289 \- GitHub, https://github.com/dotnet/msbuild/issues/14289
- Benchmark TCP/IP, Unix domain socket and Named pipe \- Home page of Xurui Yan, https://www.yanxurui.cc/posts/server/2023-11-28-benchmark-tcp-uds-namedpipe/
- Using gRPC for (local) inter-process communication | F. Werner's Research Page, https://www.mpi-hd.mpg.de/personalhomes/fwerner/research/2021/09/grpc-for-ipc/
- Job Objects \- Win32 apps \- Microsoft Learn, https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects
- AssemblyLoadContext in C\#. The AssemblyLoadContext class in .NET… | by Vikas Jindal | Medium, https://medium.com/@vikkasjindal/assemblyloadcontext-in-c-bbaacd692989
- AppDomain Class (System) | Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=net-10.0
- App capability declarations \- Windows \- Microsoft Learn, https://learn.microsoft.com/en-us/windows/apps/package-and-deploy/app-capability-declarations
- Using MSIX packaging for Visual Studio, App doesn't write in registry \- Stack Overflow, https://stackoverflow.com/questions/79494603/using-msix-packaging-for-visual-studio-app-doesnt-write-in-registry
- \[BUG\] Claude Desktop for Windows (MSIX/AppContainer) breaks Native Messaging with "Claude in Chrome" extension (related to \#56949, \#52766) \#58009 \- GitHub, https://github.com/anthropics/claude-code/issues/58009
- AssemblyLoadContext Class (System.Runtime.Loader) \- Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext?view=net-10.0
- Edge Cases in .NET Framework 4.x to .NET 10 Migration \- GAPVelocity AI, https://www.gapvelocity.ai/blog/edge-cases-in-.net-framework-4.x-to-.net-10-migration
- When should I use Pipes or gRPC for interprocess communication (in C\# .NET Core)?, https://stackoverflow.com/questions/65186138/when-should-i-use-pipes-or-grpc-for-interprocess-communication-in-c-sharp-net
- Add support for unloading assemblies from memory (re: caching) · Issue \#2095 · MarimerLLC/csla \- GitHub, https://github.com/MarimerLLC/csla/issues/2095
- Unloading Assemblies in .NET Core | AYU MAX, https://ayumax.net/entry/2019/12/10/000000/
- What is the value of AssemblyLoadContext.Unload() in .NET Core in comparison with regular garbage collection? \- Stack Overflow, https://stackoverflow.com/questions/60261523/what-is-the-value-of-assemblyloadcontext-unload-in-net-core-in-comparison-wit
- AssemblyDependencyResolver.ResolveUnmanagedDllToPath(String) Method (System.Runtime.Loader) | Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblydependencyresolver.resolveunmanageddlltopath?view=net-10.0
- AssemblyDependencyResolver.ResolveAssemblyToPath(AssemblyName) Method (System.Runtime.Loader) | Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblydependencyresolver.resolveassemblytopath?view=net-10.0
- Managed assembly loading algorithm \- .NET Core \- Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/loading-managed
- Plugin Architecture Pattern Overview (.NET) \- NashTech Blog, https://blog.nashtechglobal.com/plugin-architecture-pattern-overview-net/
- Native AOT deployment overview \- .NET | Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/
- Native code interop with Native AOT \- .NET \- Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/interop
- RDP DVC advanced plugin – .NET 8 \- Code Samples \- Microsoft Learn, https://learn.microsoft.com/en-us/samples/microsoft/rdp-dvc-plugin-samples/rdp-dvc-advanced-plugin-dotnet/
- GeoLibre/README.md at main \- GitHub, https://github.com/opengeos/GeoLibre/blob/main/README.md
- I took the NousResearch Hermes Agent and built a native Windows desktop app around it — added a soul system, 94 skills, multi-agent profiles, and a full WinUI 3 interface, 2nd Side Project. : r/SideProject \- Reddit, https://www.reddit.com/r/SideProject/comments/1sdaojm/i\_took\_the\_nousresearch\_hermes\_agent\_and\_built\_a/
- How to Configure Windows Pod Resource Limits for CPU and Memory \- OneUptime, https://oneuptime.com/blog/post/2026-02-09-windows-pod-resource-limits/view
- Set process memory limit with Process Governor \- Debug notes by Sebastian Solnica, https://lowleveldesign.wordpress.com/2013/11/21/set-process-memory-limit-with-process-governor/
- NET Runtime Identifier (RID) catalog \- Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/core/rid-catalog
- Breaking change: Host determines RID-specific assets \- .NET \- Microsoft Learn, https://learn.microsoft.com/en-us/dotnet/core/compatibility/deployment/8.0/rid-asset-list
- Can a .NET Core application on Windows trap a SIGTERM event? \- Stack Overflow, https://stackoverflow.com/questions/60517716/can-a-net-core-application-on-windows-trap-a-sigterm-event
- 10 common mistakes when migrating to .NET Core and .NET 8 (and how to fix them), https://www.tymiq.com/post/10-common-mistakes-when-migrating-to-net-core
- How to upgrade from .NET 7 to .NET 8 \- Uno Platform, https://platform.uno/docs/articles/migrating-from-net7-to-net8.html
- How to Download Offline Installer (APPX/MSIX) for Microsoft Store App | Windows OS Hub, https://woshub.com/how-to-download-appx-installation-file-for-any-windows-store-app/
- LLM Security, https://llmsecurity.net/
- Adversarial AI Digest — November 2025 | by Tal Eliyahu | AISecHub \- Medium, https://medium.com/ai-security-hub/adversarial-ai-digest-november-2025-a7c7776c2f2a
- Debugging assembly unloadability in .NET 5 \- Stack Overflow, https://stackoverflow.com/questions/71500090/debugging-assembly-unloadability-in-net-5
- Most underrated technology in .NET? : r/dotnet \- Reddit, https://www.reddit.com/r/dotnet/comments/1ga36zt/most\_underrated\_technology\_in\_net/
- IndividualAssemblyLoadContext XmlSerializers.dll memory leak \- Stack Overflow, https://stackoverflow.com/questions/73927636/individualassemblyloadcontext-xmlserializers-dll-memory-leak
- AssemblyLoadContext unloadability and 3rd party libraries · Issue \#116142 · dotnet/runtime, https://github.com/dotnet/runtime/issues/116142