You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- First parameter: primary assembly name (must have `EnableDynamicLoading=true`)
87
-
- Additional params: dependent assemblies in same LoadContext
88
-
- Specify all assemblies that are not referenced by any other assembly, grouped by module. Plugin == Module.
89
-
- One LoadContext is created for each defined plugin.
90
-
- Convention: `{ModuleName}.{AssemblySuffix}` matches folder structure. Note: the `Modules` and `Infra` folders are NOT part of the namespace, as it is a physical organization only.
91
-
- See `UI/ConsoleUi/Program.cs` for full bootstrap example
-`Initialize()` called once at app startup, on `Main()` function
80
+
### 3) Plugin Loading & Module Initialization
81
+
Modules load dynamically via `.AddPlugin("{Module}.Services", "{Module}.DbContext", ...)` in `UI/ConsoleUi/Program.cs`; each implements `IModule` for startup logic.
106
82
107
-
### 5) Entity Interceptors
83
+
> For registration rules, `IModule` pattern, and step-by-step setup, use the **add-module-plugin** skill.
84
+
85
+
### 4) Entity Interceptors
108
86
Hook into EF lifecycle via `IEntityInterceptor<T>` or `IEntityInterceptor`:
109
87
- Registered automatically via `[Service]` attribute
110
88
- Applied by DataAccess layer (no direct EF SaveChanges calls)
111
89
112
-
#### 5.1.) Specific Entity Interceptor
90
+
#### 4.1.) Specific Entity Interceptor
113
91
114
92
- Use `IEntityInterceptor<T>` to register interceptors that will be applied to a specific entity type ONLY.
115
93
- Implement by inheriting from `EntityInterceptor<T>` and overriding methods like `OnSave()`, `OnDelete()`, etc.
@@ -125,7 +103,7 @@ class SalesOrderCalculationsInterceptor : EntityInterceptor<SalesOrderHeader>
125
103
}
126
104
```
127
105
128
-
#### 5.2.) Global Entity Interceptor
106
+
#### 4.2.) Global Entity Interceptor
129
107
130
108
- Use `IEntityInterceptor` to register interceptors that will be applied to ALL entities.
131
109
- Implement by inheriting from `GlobalEntityInterceptor` and overriding methods like `OnSave()`, `OnDelete()`, etc.
@@ -155,12 +133,9 @@ dotnet run --project UI/ConsoleUi # Run console app
155
133
```
156
134
157
135
### Plugin Build Dependencies
158
-
**Problem:**Plugin assemblies (e.g., `Sales.Services`) aren't referenced directly by host, so VS may not build them.
136
+
Plugin assemblies are not referenced directly, so `dotnet build` skips them unless build-order dependencies are declared in `AppInfraDemo.sln`.
159
137
160
-
**Solution:** Add to Visual Studio build dependencies:
- Add plugin projects as dependencies of `UI/ConsoleUi`
163
-
- For multi-assembly plugins (e.g., `Sales.Services` + `Sales.DbContext`), add dependent asseblies as dependencies of the primary plugin assembly only (e.g., `Sales.Services`)
138
+
> For the full pattern and GUID lookup steps, use the **add-module-plugin** skill.
164
139
165
140
### Project Configuration
166
141
- Plugin assemblies which are dynamically loaded, as no other assemblies references them, have `<EnableDynamicLoading>true</EnableDynamicLoading>`
@@ -183,7 +158,7 @@ dotnet run --project UI/ConsoleUi # Run console app
183
158
## Protected Areas (DO NOT MODIFY)
184
159
-`Infra/**` - Framework code, touch only via extension methods/adapters
185
160
-`*/DbContext/**` - EF-generated or scaffolded, modify via migrations
186
-
-`*.csproj` files - Avoid manual edits (managed by SDK/tooling)
161
+
-`*.csproj` files - Avoid manual edits to existing project files unless adding a new module or a test project (see `add-module-plugin` or `unit-testing` skills).
187
162
188
163
If modification requested in these areas, suggest:
189
164
- Extension methods (for Infra)
@@ -224,19 +199,37 @@ Checklist:
224
199
| Add service |`[Service(typeof(IFoo))]` on implementation in `*.Services/`|
225
200
| Read data | Inject `IRepository`, use `GetEntities<T>()`|
226
201
| Write data |`using var uof = repository.CreateUnitOfWork()`|
227
-
| Add module | Create folder under `Modules/`, add to `Program.cs``.AddPlugin()`|
228
202
| Console command | Implement `IConsoleCommand` in `*.ConsoleCommands/`|
229
203
| Share types | Add to `Modules/Contracts/{Module}/` (interfaces/DTOs only) |
230
204
231
205
---
232
206
207
+
## Skills
208
+
209
+
Domain-specific guidance documents located in `.github/skills/`. When a task matches a skill's domain, read its `SKILL.md` file for detailed instructions.
210
+
211
+
| Skill | Purpose |
212
+
|-------|---------|
213
+
|**add-module-plugin**| Step-by-step guide for adding new modules: folder structure, plugin registration, `IModule` initialization, build-order dependencies in `.sln`|
214
+
|**unit-testing**| Test patterns using xUnit, NSubstitute, FluentAssertions: AAA structure, fake naming (`Stub`/`Mock`), collection assertions, `GetTarget` helpers |
215
+
216
+
Usage pattern in this file: `> For [topic], use the **skill-name** skill.`
217
+
218
+
---
219
+
233
220
## Tests
234
-
- Unit tests in corresponded test project named as `{Assembly}.UnitTests` example: `Infra/AppBoot.UnitTests` (xUnit)
235
-
- Test plugin loading with `AssembliesLoaderTests.cs` examples
description: "Step-by-step guide for adding a new module as a plugin: folder structure, plugin registration in Program.cs, module initialization, and build-order dependencies in AppInfraDemo.sln."
4
+
version: 1.0.0
5
+
language: C#
6
+
framework: .NET 10.0
7
+
---
8
+
9
+
# Add Module as Plugin Skill
10
+
11
+
## Overview
12
+
13
+
A **plugin** is one isolated `AssemblyLoadContext`.
14
+
It has one primary assembly (the first argument in `.AddPlugin(...)`) and zero or more co-loaded assemblies (additional arguments in `.AddPlugin(...)` that are not referenced by any other assembly).
15
+
Logically, may contain more **modules** if multiple `IModule` implementations are present, but we follow a one-plugin-per-module convention for simplicity.
16
+
Follow the steps below in order when adding a new module.
17
+
18
+
---
19
+
20
+
## Step 1 — Folder & Project Structure
21
+
22
+
Create the standard sub-projects under `Modules/{Module}/`:
23
+
24
+
```
25
+
Modules/{Module}/
26
+
├─ {Module}.DataModel/ # Entities only — no logic, no EF references
27
+
├─ {Module}.Services/ # Business logic — [Service] attribute for DI
28
+
├─ {Module}.DbContext/ # EF DbContext (scaffolded/generated)
.AddPlugin("{Module}.Services", "{Module}.DbContext", "{Module}.ConsoleCommands"); // new
48
+
```
49
+
50
+
Rules:
51
+
-**First argument** — primary assembly name; must have `<EnableDynamicLoading>true</EnableDynamicLoading>`
52
+
-**Additional arguments** — all co-loaded assemblies in the same `LoadContext` that are not referenced by any other assembly; must also have `<EnableDynamicLoading>true</EnableDynamicLoading>`
53
+
- One `LoadContext` is created per `.AddPlugin(...)`
54
+
- Naming convention: `{ModuleName}.{AssemblySuffix}` — `Modules/` and `Infra/` folders are physical only, not part of the namespace
55
+
56
+
---
57
+
58
+
## Step 3 — Module Initialization
59
+
60
+
Implement `IModule` in `{Module}.Services` for startup logic:
internalsealed class {Module}ServicesModule(INotificationServicenotificationService) :IModule
65
+
{
66
+
publicvoidInitialize(IHosthost)
67
+
{
68
+
notificationService.NotifyAlive(this);
69
+
}
70
+
}
71
+
```
72
+
73
+
-`Initialize()` is called once at app startup from `Main()`
74
+
- Keep it lightweight — wire up cross-module notifications, warm caches, etc.
75
+
- Inject only `Contracts` interfaces (no cross-module service types)
76
+
77
+
### Initialization Order
78
+
79
+
By default the order of `IModule.Initialize()` calls is non-deterministic.
80
+
To control the order use `[Priority(int)]` attribute from `AppBoot/DependencyInjection` on the `IModule` implementation.
81
+
82
+
---
83
+
84
+
## Step 4 — Build-Order Dependencies in AppInfraDemo.sln
85
+
86
+
Plugin assemblies have `<EnableDynamicLoading>true</EnableDynamicLoading>` and are **not** referenced directly, so `dotnet build AppInfraDemo.sln` skips them unless build-order dependencies are declared explicitly.
87
+
88
+
Add `ProjectSection(ProjectDependencies) = postProject` blocks inside the relevant `Project(...)` entries in `AppInfraDemo.sln`.
89
+
90
+
### Rules
91
+
92
+
- The **primary** plugin assembly must be declared as a Project Dependency of `ConsoleUi`
93
+
- Each co-loaded assembly (the additional params in `.AddPlugin(...)`) must be declared as a Project Dependency of the **primary** assembly
Right-click the solution → **Project Build Dependencies** → select the dependant project and tick its dependencies. This writes the same `ProjectSection(ProjectDependencies)` blocks automatically.
0 commit comments