Architecture — Request Pipeline¶
How a request is hosted, routed and authorised, read directly from Program.cs and Startup.cs.
Hosting¶
Program.cs uses the generic host with the classic Startup class:
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddQuartz(q => { /* ExportHistoryJob, UpdateNextUserJob */ });
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
})
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
Quartz is registered here, before the web host — the other two schedulers are registered inside
Startup. See External Services.
Middleware order¶
Startup.Configure builds this pipeline, in this order:
flowchart TD
A["Request"] --> B["UseDeveloperExceptionPage - unconditional"]
B --> C["UseHsts - non development only"]
C --> D["UseHttpsRedirection"]
D --> E["UseStaticFiles"]
E --> F["Rotativa setup"]
F --> G["Custom middleware - collapse double slash to single"]
G --> H["UseRouting"]
H --> I["UseAuthentication"]
I --> J["UseAuthorization"]
J --> K["UseRequestLocalization"]
K --> L["UseEndpoints - SignalR hub at slash hub"]
L --> M["UseEndpoints - default controller route"]
M --> N["Controller action"]
| Step | What it does |
|---|---|
Use |
Detailed error page. Called unconditionally — see the warning below. |
Use |
Strict-Transport-Security, only when not in Development. The matching if (env. branch is empty. |
Use |
Redirects HTTP to HTTPS |
Use |
Serves wwwroot/ |
Rotativa |
Points the PDF renderer at wwwroot/ |
| Inline middleware | Rewrites a path beginning / to / before routing |
Use |
Matches the endpoint |
Use |
Establishes Http |
Use |
Runs authorization filters, including Claim |
Use |
Resolves the culture — see Localization |
Use ×2 |
First maps the SignalR hub, then the default controller route |
Developer exception page runs in production — R1-1
app.UseDeveloperExceptionPage(); // no environment check
if (env.IsDevelopment())
{
} // empty branch
else
{
app.UseHsts();
}
There is also no UseExceptionHandler anywhere in the pipeline, so there is no friendly
fallback error page. Tracked as R1-1 in Recommendations.
UseRequestLocalization is registered after UseRouting, UseAuthentication and
UseAuthorization. It still applies before the action executes, but it is later than the usual
placement and means authentication and authorization run under the default culture.
Routing¶
One conventional route, registered last:
- No areas. Warehouse controllers live in a different folder but share the same flat namespace,
so
/Stock/Indexand/PurchaseRequest/Indexsit side by side. - Default landing:
/resolves toHomeController.Index. - One optional
{id}segment. Everything else arrives as query string or form fields. - Four attribute-routed exceptions carry
[Route("api/…")]— the file endpoints described in the API Overview.
Because there is only one template, a route is fully predictable from the class and method name:
PurchaseRequestController.Details is /PurchaseRequest/Details/{id}.
Authorization filter¶
ClaimRequirementFilter (NKO.Lib/NKO.Shared/Helpers/Sec/ClaimRequirementFilter.cs) implements
IAsyncAuthorizationFilter and is attached per action by [ClaimRequirement("Key")]:
flowchart TD
A["Authorization filter runs"] --> B{"User authenticated?"}
B -- no --> C["Redirect to tilde slash Account slash Login with ReturnUrl"]
B -- yes --> D["PermissionsHandler.GetUserPermissions - reads the database"]
D --> E{"Required key present?"}
E -- no --> F["ForbidResult, status 403"]
F --> G["Redirect to tilde slash Error slash UnAuthorised"]
E -- yes --> H["Populate CurrentRequestData - Employee and Permissions"]
H --> I["Action executes"]
The permission set is loaded from the database on every request, and
SecurityStampValidatorOptions.ValidationInterval is set to TimeSpan.Zero, so a group change or a
disabled login takes effect on the user's very next click. The cost is a permission query per
request.
Full detail in Security & Permissions.
Response shapes¶
An action typically has several return paths:
| Shape | Count | Behaviour |
|---|---|---|
View(. |
379 | Full page in _Layout (_App for the public / pages) |
Redirect / Redirect(. |
303 | Post-Redirect-Get carrying msg / error |
Partial |
276 | Fragment loaded into a Bootstrap modal |
Json(. |
270 | AJAX payload, serialised by Newtonsoft with reference loops ignored |
File(. / File |
16 | Excel / PDF / attachment download |
Failures are normally expressed as a redirect carrying a message, not as a status code — an HTTP client cannot distinguish a refused workflow transition from success by status alone.
Localization¶
Three cultures are configured — en, ar, tr — with en as the default and dd.MM.yyyy as the
short date pattern across all of them. Culture is resolved by three providers, in order:
QueryStringRequestCultureProvider—?culture=arCookieRequestCultureProvider— set by/Culture/SetLanguageAcceptLanguageHeaderRequestCultureProvider— the browser header
Resources live under Resources/ and are resolved against SharedResource; NKO.Shared's views
are served from the assembly through EmbeddedFileProvider.
Real-time¶
services.AddSignalR() plus a hub mapped at /hub (RealTime), with a custom
IUserIdProvider (CustomUserIdProvider) so notifications can be addressed to a specific employee.
RealTimeNotifier is the BAL-side wrapper that pushes notification counts to the top bar.
Related¶
- Security & Permissions · Overview
- API Overview — every route, verb and permission key
- Authentication flow · Permissions flow