Skip to main content

Solution Overview

The SAPFIAI solution follows a Clean Architecture structure with clear separation between layers:
SAPFIAI.sln
├── src/
│   ├── Domain/              # Core business logic (no dependencies)
│   ├── Application/         # Use cases and business workflows
│   ├── Infrastructure/      # Data access and external services
│   └── Web/                 # API endpoints and HTTP concerns
└── tests/
    ├── Domain.UnitTests/
    ├── Application.UnitTests/
    ├── Application.FunctionalTests/
    └── Infrastructure.IntegrationTests/

Domain Layer

The Domain layer is the core of the application with zero external dependencies.

Structure

src/Domain/
├── Common/
│   ├── BaseEntity.cs              # Base class for all entities
│   ├── BaseAuditableEntity.cs     # Entities with audit fields
│   ├── BaseEvent.cs               # Domain events base
│   └── ValueObject.cs             # Value objects base
├── Constants/
│   ├── Policies.cs                # Authorization policies
│   └── Roles.cs                   # System roles
├── Entities/
│   ├── Permission.cs              # Permission entity
│   ├── RolePermission.cs          # Role-Permission mapping
│   ├── RefreshToken.cs            # JWT refresh tokens
│   ├── AuditLog.cs                # Audit trail
│   ├── LoginAttempt.cs            # Login tracking
│   └── IpBlackList.cs             # IP blocking
├── Enums/
│   ├── AuthEnums.cs               # Authentication enumerations
│   └── PriorityLevel.cs           # Priority levels
├── Exceptions/
│   └── UnsupportedColourException.cs
├── ValueObjects/
│   └── Colour.cs                  # Colour value object
└── Domain.csproj

Key Files

Base class for all entities providing:
  • Primary key (Id)
  • Domain events collection
  • Event management methods
// src/Domain/Common/BaseEntity.cs
public abstract class BaseEntity
{
    public int Id { get; set; }

    private readonly List<BaseEvent> _domainEvents = new();

    [NotMapped]
    public IReadOnlyCollection<BaseEvent> DomainEvents 
        => _domainEvents.AsReadOnly();

    public void AddDomainEvent(BaseEvent domainEvent) { }
    public void RemoveDomainEvent(BaseEvent domainEvent) { }
    public void ClearDomainEvents() { }
}
Location: src/Domain/Common/BaseEntity.cs:5-30
Domain entities are plain C# classes with no framework dependencies. They can be tested without any infrastructure.

Application Layer

The Application layer contains business workflows and use cases using CQRS pattern.

Structure

src/Application/
├── Common/
│   ├── Behaviours/                  # MediatR pipeline behaviors
│   │   ├── AuthorizationBehaviour.cs
│   │   ├── LoggingBehaviour.cs
│   │   ├── PerformanceBehaviour.cs
│   │   ├── UnhandledExceptionBehaviour.cs
│   │   └── ValidationBehaviour.cs
│   ├── Exceptions/
│   │   ├── ForbiddenAccessException.cs
│   │   └── ValidationException.cs
│   ├── Interfaces/                  # Service contracts
│   │   ├── IApplicationDbContext.cs
│   │   ├── IIdentityService.cs
│   │   ├── IEmailService.cs
│   │   ├── IPermissionService.cs
│   │   └── ... (15+ interfaces)
│   ├── Models/                      # DTOs and responses
│   │   ├── Result.cs
│   │   ├── Error.cs
│   │   ├── PermissionDto.cs
│   │   ├── RoleDto.cs
│   │   └── PagedResult.cs
│   └── Security/
│       └── AuthorizeAttribute.cs    # Custom authorization
├── Permissions/                     # Permission feature
│   ├── Commands/
│   │   ├── CreatePermission/
│   │   │   ├── CreatePermissionCommand.cs
│   │   │   ├── CreatePermissionCommandHandler.cs
│   │   │   └── CreatePermissionCommandValidator.cs
│   │   ├── UpdatePermission/
│   │   ├── DeletePermission/
│   │   ├── AssignPermissionToRole/
│   │   └── RemovePermissionFromRole/
│   └── Queries/
│       ├── GetPermissions/
│       │   ├── GetPermissionsQuery.cs
│       │   └── GetPermissionsQueryHandler.cs
│       ├── GetPermissionById/
│       └── GetRolePermissions/
├── Roles/                           # Role management
│   ├── Commands/
│   └── Queries/
├── Users/                           # User authentication
│   ├── Commands/
│   │   ├── Login/
│   │   ├── Register/
│   │   ├── RefreshToken/
│   │   ├── EnableTwoFactor/
│   │   └── ForgotPassword/
│   └── Queries/
├── Security/                        # Security operations
│   ├── Commands/
│   └── Queries/
├── DependencyInjection.cs          # Service registration
├── GlobalUsings.cs                 # Global using statements
└── Application.csproj

Key Patterns

Each business feature (Permissions, Roles, Users) has its own folder with:
  • Commands: Operations that modify state
  • Queries: Operations that retrieve data
Example: Application/Permissions/Commands/CreatePermission/
  • CreatePermissionCommand.cs - The request
  • CreatePermissionCommandHandler.cs - Business logic
  • CreatePermissionCommandValidator.cs - Validation rules
Commands and Queries are strictly separated:Commands (Write operations):
Commands/CreatePermission/
Commands/UpdatePermission/
Commands/DeletePermission/
Queries (Read operations):
Queries/GetPermissions/
Queries/GetPermissionById/
Queries/GetRolePermissions/
Each use case is self-contained in its own folder:
CreatePermission/
├── CreatePermissionCommand.cs       # Request model
├── CreatePermissionCommandHandler.cs # Business logic
└── CreatePermissionCommandValidator.cs # Validation
This makes features easy to find, modify, and test.

Critical Files

Registers all Application layer services:
// src/Application/DependencyInjection.cs
public static class DependencyInjection
{
    public static IServiceCollection AddApplicationServices(
        this IServiceCollection services)
    {
        // AutoMapper for DTO mapping
        services.AddAutoMapper(Assembly.GetExecutingAssembly());

        // FluentValidation for validation
        services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());

        // MediatR for CQRS
        services.AddMediatR(cfg => {
            cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
            cfg.AddBehavior(typeof(IPipelineBehavior<,>), 
                typeof(UnhandledExceptionBehaviour<,>));
            cfg.AddBehavior(typeof(IPipelineBehavior<,>), 
                typeof(AuthorizationBehaviour<,>));
            cfg.AddBehavior(typeof(IPipelineBehavior<,>), 
                typeof(ValidationBehaviour<,>));
            cfg.AddBehavior(typeof(IPipelineBehavior<,>), 
                typeof(PerformanceBehaviour<,>));
        });

        return services;
    }
}
Location: src/Application/DependencyInjection.cs:8-24

Infrastructure Layer

The Infrastructure layer provides concrete implementations of Application interfaces.

Structure

src/Infrastructure/
├── Authorization/
│   ├── PermissionAuthorizationHandler.cs
│   └── PermissionRequirement.cs
├── BackgroundJobs/
│   └── SecurityCleanupJob.cs         # Cleanup expired tokens/logs
├── Data/
│   ├── Configurations/               # Entity Framework configurations
│   │   ├── PermissionConfiguration.cs
│   │   ├── RolePermissionConfiguration.cs
│   │   ├── AuditLogConfiguration.cs
│   │   └── ... (6 configurations)
│   ├── Interceptors/
│   │   ├── AuditableEntityInterceptor.cs
│   │   └── DispatchDomainEventsInterceptor.cs
│   ├── Migrations/                   # EF Core migrations
│   │   ├── 20260212195337_InitialAuthMigration.cs
│   │   ├── 20260215221946_InitialCreate.cs
│   │   └── ApplicationDbContextModelSnapshot.cs
│   ├── ApplicationDbContext.cs       # Main DbContext
│   └── ApplicationDbContextInitialiser.cs
├── Identity/
│   ├── ApplicationUser.cs            # ASP.NET Identity user
│   └── IdentityService.cs            # Identity operations
├── Services/                         # Service implementations
│   ├── JwtTokenGenerator.cs
│   ├── PermissionService.cs
│   ├── RoleService.cs
│   ├── EmailService.cs
│   ├── TwoFactorService.cs
│   ├── AuditLogService.cs
│   ├── RefreshTokenService.cs
│   ├── IpBlackListService.cs
│   ├── LoginAttemptService.cs
│   └── AccountLockService.cs
├── DependencyInjection.cs            # Service registration
└── Infrastructure.csproj

Key Files

Entity Framework Core database context:
// src/Infrastructure/Data/ApplicationDbContext.cs
public class ApplicationDbContext 
    : IdentityDbContext<ApplicationUser>, IApplicationDbContext
{
    public ApplicationDbContext(
        DbContextOptions<ApplicationDbContext> options) 
        : base(options) { }

    public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
    public DbSet<Permission> Permissions => Set<Permission>();
    public DbSet<RolePermission> RolePermissions => Set<RolePermission>();
    public DbSet<RefreshToken> RefreshTokens => Set<RefreshToken>();
    public DbSet<IpBlackList> IpBlackLists => Set<IpBlackList>();
    public DbSet<LoginAttempt> LoginAttempts => Set<LoginAttempt>();

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.ApplyConfigurationsFromAssembly(
            Assembly.GetExecutingAssembly());
        base.OnModelCreating(builder);
    }
}
Location: src/Infrastructure/Data/ApplicationDbContext.cs:11-33

Web Layer

The Web layer handles HTTP requests and API presentation.

Structure

src/Web/
├── Endpoints/                        # Minimal API endpoints
│   ├── Authentication.cs             # Login, register, refresh token
│   ├── Permissions.cs                # Permission management
│   ├── Roles.cs                      # Role management
│   └── Security.cs                   # IP blocking, account locks
├── Infrastructure/
│   ├── CustomExceptionHandler.cs     # Global exception handling
│   ├── EndpointGroupBase.cs          # Base class for endpoints
│   ├── IEndpointRouteBuilderExtensions.cs
│   ├── ResultExtensions.cs           # Result to IResult conversion
│   └── WebApplicationExtensions.cs   # App configuration
├── Middleware/
│   └── IpBlockingMiddleware.cs       # IP blacklist middleware
├── Services/
│   ├── CurrentUser.cs                # Current user context
│   └── HttpContextInfo.cs            # HTTP context information
├── Pages/                            # Razor Pages (if needed)
│   ├── Error.cshtml
│   └── Shared/
├── wwwroot/                          # Static files
│   └── api/
├── appsettings.json                  # Configuration
├── appsettings.Development.json
├── DependencyInjection.cs            # Web service registration
├── Program.cs                        # Application entry point
└── Web.csproj

Key Files

Application entry point and configuration:
// src/Web/Program.cs (simplified)
var builder = WebApplication.CreateBuilder(args);

// Add services from each layer
builder.Services.AddApplicationServices();
builder.Services.AddInfrastructureServices(builder.Configuration);
builder.Services.AddWebServices();

var app = builder.Build();

// Initialize database in development
if (app.Environment.IsDevelopment())
{
    await app.InitialiseDatabaseAsync();
}

// Configure HTTP pipeline
app.UseHttpsRedirection();
app.UseMiddleware<IpBlockingMiddleware>();  // Custom middleware
app.UseAuthentication();
app.UseAuthorization();
app.UseSwaggerUi3();
app.MapEndpoints();                          // Register all endpoints

app.Run();
Location: src/Web/Program.cs:36-105

Tests

Comprehensive test coverage across all layers:
tests/
├── Domain.UnitTests/
│   ├── Common/
│   └── Entities/
├── Application.UnitTests/
│   ├── Common/
│   │   └── Behaviours/          # Test pipeline behaviors
│   ├── Permissions/
│   │   ├── Commands/
│   │   └── Queries/
│   └── Roles/
├── Application.FunctionalTests/
│   ├── IEndpointRouteBuilderExtensions/
│   └── Testing.cs               # Test infrastructure setup
└── Infrastructure.IntegrationTests/
    ├── Data/
    └── Identity/
Each test project targets a specific layer and test type:
  • Unit Tests: Fast, isolated, no external dependencies
  • Integration Tests: Test with real database
  • Functional Tests: End-to-end API tests

Configuration Files

Root Level

Solution file organizing all projectsLocation: SAPFIAI.sln
1

Finding a Feature

To find code for a feature like “Create Permission”:
  1. Go to src/Application/Permissions/
  2. Look in Commands/CreatePermission/
  3. Find the Command, Handler, and Validator
2

Finding an Entity

Domain entities are in:
  • src/Domain/Entities/
Entity configurations are in:
  • src/Infrastructure/Data/Configurations/
3

Finding an API Endpoint

API endpoints are in:
  • src/Web/Endpoints/
Each feature has its own endpoint file (e.g., Permissions.cs)
4

Finding Service Implementations

Service implementations are in:
  • src/Infrastructure/Services/
Service interfaces are in:
  • src/Application/Common/Interfaces/

Next Steps

CQRS Pattern

Learn how Commands and Queries work

Creating Use Cases

Add new features to the application

Domain Entities

Understanding domain models

Testing

Writing tests for each layer

Build docs developers (and LLMs) love