Table of Contents

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: @page in a module component routes automatically (the host adds module assemblies to both the Router and MapRazorComponents).
  • Tests reference the module directly and compose TestDb with the same modules as production.

Start a new one

Copy modules/_template, drop the underscore, rename — the folder's README.md walks through it.