Architecture — Overview¶
The NKO ERP is a single ASP.NET Core MVC application (.NET 6) that renders HTML on the server and talks to two SQL Server databases. There is no SPA front end, no separate API tier and no microservices: one deployable web application, one shared business-logic layer, and two Entity Framework contexts.
At a glance
5 projects · 44 controller classes / 947 actions · 44 business-logic classes · 161 entities across 2 databases · 219 permission keys · .NET 6 / EF Core 6.0.15 / SQL Server.
Layering¶
The solution follows a conventional three-layer shape. Controllers hold almost no business logic;
they resolve the current user, call a B* / *Handler class, and hand the result to a view.
flowchart TD
subgraph P["Presentation"]
V["Razor Views and ViewComponents"]
C["Controllers - 44 classes, 947 actions"]
end
subgraph B["Business logic - BAL"]
M["NKO.Main BAL - 32 classes"]
W["NKO.Main WareBAL - 8 classes"]
S["NKO.Shared BAL - 7 classes"]
end
subgraph D["Data access - EF Core 6"]
X1["NKO_DBEntities1 - 140 entities"]
X2["WarehouseDBContext - 21 entities"]
end
subgraph DB["SQL Server"]
DB1[(NKO_DB)]
DB2[(NKO_DB_Warehouse)]
DB3[(Hangfire store)]
end
V --> C
C --> M
C --> W
C --> S
M --> X1
S --> X1
W --> X2
M --> X2
X1 --> DB1
X2 --> DB2
B --> DB3
| Layer | Where | Responsibility |
|---|---|---|
| Presentation | NKO., NKO., Controllers/, Warehouse |
Razor pages, modal partials, JSON endpoints for jQuery. Controllers validate input and dispatch. |
| Business logic | NKO., NKO., NKO. |
All rules: approval state machines, payroll and leave calculation, budget roll-ups, CBA scoring, permission resolution. |
| Data access | NKO., NKO. |
EF Core entities, two Dbs, migrations. No repository abstraction — the BAL uses Db directly. |
There is no repository or unit-of-work layer: business classes take a DbContext in their
constructor and query it with LINQ. Likewise there are no interfaces for most BAL classes — only
five are registered against a contract (IS3ServiceHandler, IProcBudgetLineHandler,
IAllocationHandler, IBudgetLinesHandler, IAttendancesHandler) — four of them business
classes, plus S3ServiceHandler from Configs/. The other 40 business classes are injected as
concrete types.
Lazy loading is not enabled — UseLazyLoadingProxies() is never called, so every navigation
property a query needs must be .Included explicitly. See Technology Stack.
The two databases¶
This is the most important structural fact about the system.
| Core | Warehouse | |
|---|---|---|
| Context | NKO_DBEntities1 |
Warehouse |
| Connection string | Hr |
Ware |
| Database | NKO_DB |
NKO_DB_Warehouse |
| Entities | 140 | 21 |
| Migrations | 18 | 5 |
| Covers | HR, payroll, procurement, finance, recruitment, training, administration, identity | Stock, assets, maintenance, destruction, waybills |
They are separate databases with no foreign keys between them. The warehouse reuses core
identity, signatures, notifications and permissions by calling core BAL classes, and receives goods
through one explicit hand-off: BGRN.TransfareToWarehouse writes CenterStock / Asset rows into
NKO_DB_Warehouse and flags the core GoodReceivedNote. A third connection string,
HangfireConnection, points at the Hangfire job store.
How a request flows¶
flowchart TD
R["Browser request"] --> MW["Middleware pipeline"]
MW --> RT["Routing - controller slash action"]
RT --> F{"ClaimRequirementFilter"}
F -- "not signed in" --> L["302 to Account slash Login"]
F -- "key missing" --> U["403 then Error slash UnAuthorised"]
F -- "allowed" --> A["Controller action"]
A --> BAL["BAL class"]
BAL --> EF["EF Core DbContext"]
EF --> SQL[(SQL Server)]
BAL --> EXT["S3, SMTP"]
A --> OUT{"Response"}
OUT --> PV["View or PartialView"]
OUT --> JS["JSON for AJAX"]
OUT --> RD["Redirect with a message"]
Detail lives in Request Pipeline and Security & Permissions.
Cross-cutting concerns¶
| Concern | Mechanism |
|---|---|
| Authentication | ASP.NET Core Identity (Application) over NKO_DB; cookie-based. A JWT bearer scheme is registered but never takes effect — see Security & Permissions |
| Authorization | Method-level [Claim, 219 keys, re-read from the database on every request |
| Row-level scoping | BUser. returning All / Location / Custom / Default, applied inside the BAL |
| Org hierarchy | Staff walks Manager / Tech to limit a manager to their own staff |
| Approval workflow | One generic engine: an integer Status plus Next, driven by 23 Context endpoints |
| E-signature | BSign. gates procurement, warehouse, leave, advance and overtime transitions — but not payroll, training, jobs, evaluation, PO justification or destruction (R3-9) |
| Notifications | BNotification rows plus SignalR push over the / endpoint |
| Background work | Three schedulers — Quartz.NET, Hangfire and a custom IHosted. See External Services |
| Localization | English, Arabic and Turkish; resource files under Resources/, culture from query string, cookie or Accept-Language |
| File storage | AWS S3 through IS3; documents are referenced by storage key |
| Reporting | EPPlus for Excel, Rotativa (wkhtmltopdf) for PDF |
Design characteristics worth knowing¶
- Server-rendered, jQuery-driven. 379 actions return a full page, 276 return a modal fragment and 270 return JSON to in-page scripts. There is no client-side framework.
- Conventional routing only. One route template, no areas — see Request Pipeline.
- Permissions are data, not code. Keys are free-form strings stored in
AppPermissionand attached to groups; there is no fixed role list. - Workflow is uniform. Every multi-step document behaves the same way, which makes the system much easier to learn than its size suggests.
- Business logic is concentrated.
BAsset(4 205 lines),BStock(4 024) andBRFQ(3 843) are the three largest classes and hold the most intricate rules.
Known architectural issues
UseDeveloperExceptionPage() is called unconditionally, so production shows full stack
traces (R1-1); there is no default-deny authorization policy (R3-2); and several
referenced packages are never used (R1-4). See Recommendations.