Backend Architecture
Program.cs anatomy, middleware pipeline, DI service map, module registration pattern, and controller catalog.
The SCRIPE backend is a .NET 10 Modular Monolith with 288 lines in Program.cs that wire together 16 service registrations, 10 middleware components, and 18 REST controllers. This page dissects every layer of the backend architecture.
Program.cs Anatomy
Program.cs is the application's entry point and wiring center. It detects the deployment mode, registers services in a specific order, and builds the middleware pipeline. The file follows a clear 5-section structure.
var builder = WebApplication.CreateBuilder(args);
// ─── 1. Environment Detection ───────────────────────────
var moduleName = Environment.GetEnvironmentVariable("MODULE_NAME") ?? "";
var isMonolith = string.IsNullOrEmpty(moduleName);
var isGateway = moduleName.Equals("Gateway", StringComparison.OrdinalIgnoreCase);
// ─── 2. Core Infrastructure (must come first) ───────────
builder.Services.AddInfrastructureServices(builder.Configuration);
builder.Services.AddCorsConfiguration(builder.Configuration);
builder.Services.AddRateLimitingConfiguration();
builder.Services.AddCoreInfrastructure(builder.Configuration);
// ─── 3. Module Registration (guarded by mode) ───────────
var handlerAssemblies = new List<Assembly>();
if (isMonolith || moduleName == "Identity")
{
handlerAssemblies.Add(typeof(Identity.Application.DependencyInjection));
builder.Services.AddIdentityModule(builder.Configuration);
}
// ─── 4. Application Layer (needs module assemblies) ─────
builder.Services.AddCoreApplication(handlerAssemblies);
// ─── 5. Build & Configure Pipeline ─────────────────────
var app = builder.Build();
app.UseMiddleware<RequestLoggingMiddleware>();
app.UseMiddleware<AuditableMiddleware>();
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();Middleware Pipeline
The middleware pipeline processes every HTTP request in a specific order. Each middleware can short-circuit the pipeline (e.g., rate limiter returns 429, auth returns 401). Order matters changing it can break security.
DI Service Map
The following table shows all major service interfaces, their implementations, lifetimes, and where they are registered. Understanding this map is essential for debugging and extending the system.
| Service Interface | Implementation | Lifetime | Registered In |
|---|---|---|---|
| ICurrentUser | CurrentUser | Scoped | CoreInfrastructure |
| IApiService | ApiService | Scoped | CoreInfrastructure |
| IAuditService | AuditService | Scoped | CoreInfrastructure |
| ICacheService | MemoryCacheService / RedisCacheService | Singleton | CoreInfrastructure |
| IEmailService | SmtpEmailService | Scoped | CoreInfrastructure |
| IBlobStorageService | LocalBlobStorage / AzureBlobStorage | Singleton | BlobStorage |
| IAdminRepository | AdminRepository | Scoped | IdentityModule |
| IUserRepository | UserRepository | Scoped | IdentityModule |
| IRoleRepository | RoleRepository | Scoped | IdentityModule |
| ITenantRepository | TenantRepository | Scoped | IdentityModule |
| IMenuRepository | MenuRepository | Scoped | IdentityModule |
Module Registration Pattern
Every new module follows the same DI registration pattern. The AddXxxModule() extension method registers the module's DbContext, repositories, services, and module registration marker.
public static class DependencyInjection
{
public static IServiceCollection AddIdentityModule(
this IServiceCollection services,
IConfiguration configuration)
{
// 1. DbContext registration (multi-provider)
services.AddDbContext<IdentityDbContext>(options =>
{
var provider = configuration["DatabaseProvider"];
switch (provider)
{
case "SqlServer":
options.UseSqlServer(connectionString);
break;
case "PostgreSQL":
options.UseNpgsql(connectionString);
break;
case "Oracle":
options.UseOracle(connectionString);
break;
}
});
// 2. Repository registrations
services.AddScoped<IAdminRepository, AdminRepository>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IRoleRepository, RoleRepository>();
// 3. Module-specific services
services.AddScoped<IPermissionService, PermissionService>();
services.AddScoped<IJwtService, JwtService>();
// 4. Module registration (for runtime introspection)
services.AddSingleton<IModuleRegistration, IdentityModuleRegistration>();
return services;
}
}Controllers
| Controller | Base Route | Endpoints | Auth Required |
|---|---|---|---|
| AuthController | /api/v1/auth | Login, Refresh, Logout, Verify2FA | Partial |
| AdminController | /api/v1/admin | CRUD + Block + Impersonate | Yes |
| UserController | /api/v1/users | CRUD + Profile + Avatar | Yes |
| RoleController | /api/v1/roles | CRUD + Assign Permissions | Yes |
| TenantController | /api/v1/tenants | CRUD + Settings + Logo | Yes |
| PermissionController | /api/v1/permissions | List + Categories + Assign | Yes |
| MenuController | /api/v1/menus | CRUD + Reorder + Tree | Yes |
| AuditController | /api/v1/audit | Search + Export + Stream | Yes |
| DashboardController | /api/v1/dashboard | Stats + Charts + Feed | Yes |
| FileController | /api/v1/files | Upload + Download + Delete | Yes |
| RecycleBinController | /api/v1/recycle-bin | List + Restore + Purge | Yes |
| NotificationController | /api/v1/notifications | List + Read + Settings | Yes |
| SettingsController | /api/v1/settings | Get + Update + Reset | Yes |
| HealthController | /health | Liveness + Readiness | No |