InzDynamicModuleLoader is a .NET 9.0 library that enables plugin-based architecture by loading modules at startup time. This allows for better separation of concerns, module isolation, and flexible infrastructure switching while maintaining clean architecture boundaries.
- Features
- Prerequisites
- How to Use
- Project Structure
- Managing Dependencies
- IAmModule Interface Explained
- Example Implementation
- Troubleshooting
- Learn More
- Contributing
- License
- Dynamic Module Loading: Load modules at runtime based on configuration
- Plugin Architecture: Create extensible applications with modular functionality
- Easy Integration: Simple setup with .NET's built-in dependency injection
- Dependency Management: Automatic resolution of module-specific dependencies
- Configuration-Driven: Control which modules load through configuration files
- Development & Production: Works seamlessly in both environments
- .NET 9.0 or higher
Add the Inz Dynamic Module Loader to your startup project:
dotnet add package InzSoftwares.NetDynamicModuleLoaderCreate a Directory.Build.targets file in your solution root directory with this content. This ensures all module dependencies are properly copied:
<Project>
<PropertyGroup Condition="'$(IsModuleProject)' == 'true'">
<!-- Forces MSBuild to copy all dependencies to the output directory -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<!-- Generates dependency file for proper resolution -->
<GenerateDependencyFile>true</GenerateDependencyFile>
</PropertyGroup>
<!-- Custom target to deploy modules to a unified folder after build -->
<Target Name="DeployModuleToUnifiedFolder" AfterTargets="Build" Condition="'$(IsModuleProject)' == 'true'">
<PropertyGroup>
<UnifiedModulePath>$(MSBuildThisFileDirectory)BuiltModules/$(MSBuildProjectName)/</UnifiedModulePath>
</PropertyGroup>
<ItemGroup>
<ModuleFiles Include="$(OutputPath)**/*.*"/>
</ItemGroup>
<Message Text="[Module Deployment] Copying $(MSBuildProjectName) to $(UnifiedModulePath)" Importance="High"/>
<Copy SourceFiles="@(ModuleFiles)"
DestinationFolder="$(UnifiedModulePath)%(RecursiveDir)"
SkipUnchangedFiles="true"/>
</Target>
</Project>This configuration ensures each dynamically loaded module includes all its dependencies and the deps.json file. The custom target creates a
dedicated BuiltModules directory in the solution directory where each dynamically loaded module's dependencies can be found and resolved correctly.
For more details about this file and its purpose, see the Directory.Build.targets Documentation.
- Create a new .NET Class Library project
- In the
.csprojfile, add theIsModuleProjectproperty:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Enables the module deployment logic -->
<IsModuleProject>true</IsModuleProject>
</PropertyGroup>
</Project>In your module project, create a class that implements the IAmModule interface:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using InzDynamicLoader.Core;
public class MyExampleModule : IAmModule
{
public IServiceCollection RegisterServices(IServiceCollection services, IConfiguration configuration)
{
// Register your module's services here
// Example: services.AddScoped<IMyService, MyServiceImplementation>();
return services;
}
public IServiceProvider InitializeServices(IServiceProvider services, IConfiguration configuration)
{
// Initialize services after registration (optional)
// Example: Initialize database connections, event handlers, etc.
return services;
}
}In your main application's appsettings.json, specify which modules to load:
{
"Modules": [
"MyExampleModule",
"AnotherModule"
]
}In your main application's Program.cs (or Startup.cs), register and initialize the modules:
using InzDynamicLoader.Core;
var builder = WebApplication.CreateBuilder(args);
// Register modules and their services
builder.Services.RegisterModules(builder.Configuration);
var app = builder.Build();
// Initialize modules after services are registered
app.Services.InitializeModules(builder.Configuration);
// Continue with your application setup
app.Run();After setup, your solution will have this structure:
YourSolution/
├── MainApplication/ # Your main application
├── MyExampleModule/ # First module project
├── MyOtherModule/ # Second module project
├── Directory.Build.targets # Build configuration
├── Directory.Packages.props # Centralized package versions
└── BuiltModules/ # Automatically created - contains compiled modules
├── MyExampleModule/
│ ├── MyExampleModule.dll
│ ├── MyExampleModule.deps.json
│ └── Dependencies...
└── MyOtherModule/
├── MyOtherModule.dll
├── MyOtherModule.deps.json
└── Dependencies...
When working with multiple modules, you may encounter the "Diamond Dependency" problem. This occurs when different modules require the same dependency but at different versions. For example:
- Module A requires Newtonsoft.Json v13
- Module B requires Newtonsoft.Json v9
- At runtime, only one version can be loaded, which may cause compatibility issues
To solve this problem and ensure version consistency across all modules, create a Directory.Packages.props file in your solution root. This file
enables Central Package Management, which defines package versions in one central location:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- Define package versions once for the entire solution -->
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0"/>
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.0"/>
<!-- Add other package versions here -->
</ItemGroup>
</Project>When using this approach, reference packages without specifying versions in your project files:
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions"/>For more information about this file and its content, see the Directory.Packages.props Documentation.
The IAmModule interface has two methods:
- RegisterServices: Called first, registers services with the dependency injection container
- InitializeServices: Called after registration, allows for service initialization and configuration
The project includes a comprehensive example that demonstrates the dynamic loading capabilities of the InzDynamicModuleLoader system. The example showcases a real-world scenario where database infrastructure can be switched at startup without code changes.
The example includes:
- Multiple database provider implementations (MySQL and PostgreSQL)
- Core shared components with contracts and data models
- Common EF Core repository patterns
- Runtime switching between database providers based on configuration
- Clean separation of concerns between modules
The example uses modules with the Example. prefix to demonstrate:
Example.Module.Common- Contains shared contracts, entities, and configurationsExample.Module.EFCore.MySQL- MySQL-specific implementationExample.Module.EFCore.PostgreSQL- PostgreSQL-specific implementationExample.Module.EFCore.Repositories- Repository implementationsExample.Module.WebStartup- Web application startup project
This architecture demonstrates how to build flexible applications where infrastructure concerns can be swapped out dynamically, maintaining clean separation of concerns while enabling maximum flexibility.
For step-by-step instructions on how to run and understand the example, see the Example Breakdown documentation.
Problem: Module dependencies not found
- Solution: Ensure
IsModuleProject=trueis set in your module project
Problem: Type conflicts between modules
- Solution: Use
Directory.Packages.propsto ensure version consistency
Problem: Module not loading
- Solution: Check that the module name in
appsettings.jsonmatches the assembly name exactly
For detailed technical information about how the module loading system works, check out the Module System Architecture documentation.
Contributions are welcome! Feel free to submit issues or pull requests to improve this library.
This project is licensed under the MIT License.