Modules (plugins)
A module is a folder under modules/ with one Razor Class Library project and a class implementing
IModule. The host references modules/*/*.csproj by glob (folders starting with _ are skipped) and
discovers the class by reflection from assemblies named ErpDarAlBer.Modules.*.
sequenceDiagram
participant H as Host (Program.cs)
participant MH as ModuleHost
participant M as RequestsModule
H->>MH: AddModules()
MH->>MH: scan ErpDarAlBer.Modules.*.dll
MH->>M: ConfigureServices(services)
H->>H: build app
H->>M: ConfigureModel(modelBuilder) (via AppDbContext)
H->>M: MapEndpoints(app)
H->>M: StartAsync(scope) (seed + warm-up)
H->>M: Nav / HomeWidget / AppBarItem (rendered by the layout)
The contract
public interface IModule
{
string Key { get; } // stable Latin key
string NameAr { get; }
int Order => 100; // position among modules
void ConfigureServices(IServiceCollection services) { }
void ConfigureModel(ModelBuilder model, bool isNpgsql) { } // entities on the shared context
void MapEndpoints(IEndpointRouteBuilder endpoints) { }
Task StartAsync(IServiceProvider services, bool isDevelopment, CancellationToken ct) => Task.CompletedTask;
Task OnBranchSavedAsync(AppDbContext db, Branch branch, string? previousNameAr, CancellationToken ct) => Task.CompletedTask;
IReadOnlyList<NavItem> Nav => []; // side menu, admin menu, phone tabs, counters
Type? HomeWidget => null; // component rendered on the home page
Type? AppBarItem => null; // component rendered in the top bar
}
Every member has a default, so a module implements only what it needs.
Data
Modules share the one AppDbContext. A module configures its entities in ConfigureModel and reaches them
through extension methods (db.ServiceRequests()), because the core has no DbSet for them. Give every
entity an explicit table name — without a DbSet, EF would otherwise fall back to the class name and the
migrations would drift. Migrations are added from the host:
dotnet ef migrations add AddFleet -p src/ErpDarAlBer.Web -s src/ErpDarAlBer.Web -o Data/Migrations
Rules
- Core and host never name a module (
ArchitectureTests.CoreAndHostNeverNameAModule). - Modules talk to the core only, never to each other. Shared needs go into the core.
- Pages need no registration:
@pagein a module component routes automatically (the host adds module assemblies to both theRouterandMapRazorComponents). - Tests reference the module directly and compose
TestDbwith the same modules as production.
Start a new one
Copy modules/_template, drop the underscore, rename — the folder's README.md walks through it.