Architecture

Understand how Spiderly works under the hood — from C# entities to generated backend and frontend code, and how to extend it.

Overview

Spiderly's core idea is simple: you define C# entity classes with attributes, and the framework generates everything else.

At build time, Roslyn source generators read your entity definitions and produce:

  • .NET services, controllers, DTOs, mappers, validators, and permission codes
  • Angular entities, API services, form components, validators, and enums

You never edit generated files. Instead, you extend them through inheritance and partial classes.

Your Entity Classes (C# + Attributes)


  Source Generators (build time)


  Generated Base Code (*.generated.cs / *.generated.ts)


  Your Code (extends generated base classes)

Project Structure

Running spiderly init creates the following structure:

your-app-name/
├── Backend/
│   ├── YourApp.Business/
│   │   ├── Entities/           ← Your entity classes (source of truth)
│   │   ├── Services/           ← Entity services, AuthorizationService, SecurityService
│   │   ├── DTO/                ← Custom DTO extensions (partial classes)
│   │   ├── DataMappers/        ← Custom Mapster mappings (partial class)
│   │   ├── Enums/              ← Custom PermissionCodes (partial class)
│   │   └── Settings.cs
│   │
│   ├── YourApp.Infrastructure/ ← EF Core DbContext, migrations
│   ├── YourApp.WebAPI/         ← Custom controllers, Program.cs
│   ├── YourApp.Shared/
│   │   └── Translations/       ← Translation JSON files (en.json, sr-Latn-RS.json, ...)
│   └── YourApp.sln

└── Frontend/
    └── src/app/
        ├── business/
        │   ├── components/     ← Generated base detail components
        │   ├── entities/       ← Generated TypeScript entity classes
        │   ├── enums/          ← Generated TypeScript enums
        │   ├── layout/         ← Navigation menu and layout
        │   └── services/
        │       ├── api/        ← API service (extends generated base)
        │       ├── auth/       ← Auth service (extends generated base)
        │       └── validators/ ← Generated form validators
        └── pages/              ← Your list and details page components

What Gets Generated

Spiderly has 14 source generators (9 for .NET, 5 for Angular) that run at build time:

.NET Generators

GeneratorOutput FilePurpose
ServicesGenerator{Entity}Service.generated.csPer-entity CRUD operations, data retrieval, Excel export
ControllerGeneratorBaseControllers.generated.csREST API endpoints for each entity
EntitiesToDTOGeneratorDTOList.generated.csDTO classes mirroring your entities
MapperGeneratorMapper.generated.csMapster config for entity ↔ DTO mapping
FluentValidationGeneratorValidationRules.generated.csFluentValidation rules from attributes
AuthorizationServicesGeneratorAuthorizationService.generated.csPermission checks for CRUD operations
PermissionCodesGeneratorPermissionCodes.generated.csStatic permission code constants
PaginatedResultGeneratorPaginatedResultGenerator.generated.csDynamic EF Core filtering queries
ExcelPropertiesGeneratorExcelPropertiesToExclude.generated.csProperties to exclude from Excel export

Angular Generators

GeneratorOutput FilePurpose
NgEntitiesGeneratorentities.generated.tsTypeScript classes mirroring C# DTOs
NgControllersGeneratorapi.service.generated.tsTyped HTTP methods for all API endpoints
NgBaseDetailsGeneratorbase-details.generated.tsForm components for entity detail pages
NgValidatorsGeneratorvalidators.generated.tsAngular form validators from C# attributes
NgEnumsGeneratorenums.generated.tsTypeScript enums from C# enums

When a generator encounters a contract violation on your entity (bad [ForeignKey], missing base class, malformed [DisplayName] path, etc.), it emits a located SPIDERLY### diagnostic rather than crashing the build. See Build Diagnostics for the full code reference.

What You Write by Hand

  • Entity classes with attributes (the single source of truth)
  • Business logic overrides in entity service classes (e.g., ProductService)
  • Custom controllers for non-CRUD endpoints
  • Angular list and details page components
  • Custom DTO properties (via partial classes)
  • Custom mapper configurations (via partial class)
  • Translations

You Own the Generated Code

Spiderly is MIT-licensed, and so is everything it produces. The generators run at build time: the Angular generators write .generated.ts files into your project, and the Roslyn source generators emit C# straight into the compiled assembly (or onto disk too, if you set EmitCompilerGeneratedFiles). Either way, the output is plain C# and TypeScript — no runtime magic, no proprietary format. Your app depends on a handful of base classes from the Spiderly NuGet packages (ServiceBase, SpiderlyBaseController, AuthorizationServiceBase, …), nothing more.

A Spiderly project is a normal ASP.NET Core + EF Core project. Anything you'd reach for in a regular .NET app — your own controllers and minimal API endpoints, Hangfire, SignalR, custom middleware, Dapper alongside EF Core, your preferred logging stack (Serilog, OpenTelemetry) — drops in the same way it would anywhere else. The framework adds scaffolding on top; it doesn't fork the runtime or close off the ecosystem.

If you ever want to stop using Spiderly, you have two paths:

  • Freeze the generated output. Your Angular .generated.ts files are already on disk. For the .NET side, set <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> in Directory.Build.props and build once to write the C# to disk. Commit both, vendor a copy of the runtime base classes you use, drop the Spiderly package references — your hand-written code already extends the generated base classes through inheritance, so the app still compiles and runs.
  • Fork the generators. The source generators live in the Spiderly repo under MIT. Copy the ones you use into your codebase and evolve them on your own schedule.

The Inheritance Pattern

Spiderly uses a three-tier inheritance pattern. The framework provides a base class, generators produce a middle layer, and you extend at the top:

Services

ServiceBase                      ← Framework (Spiderly NuGet package)

{Entity}ServiceGenerated   ← Generated per entity (readonly, rebuilt every build)

{Entity}Service            ← Your code (override virtual methods here)

Controllers

SpiderlyBaseController       ← Framework

{Entity}BaseController       ← Generated (e.g., ProductBaseController)

{Entity}Controller           ← Your code (optional, only if you need overrides)

Authorization

AuthorizationServiceBase     ← Framework

AuthorizationServiceGenerated ← Generated

AuthorizationService         ← Your code

Angular API Service

ApiSecurityService           ← Framework (Spiderly Angular library)

ApiGeneratedService          ← Generated

ApiService                   ← Your code

Never edit files with .generated.cs or .generated.ts suffixes — they are overwritten on every build. Always extend through the hand-written classes above.

How Angular and .NET Stay in Sync

Both the .NET and Angular code are generated from the same source: your C# entity classes. This means:

  • TypeScript entities mirror your C# DTOs — same property names and types
  • Angular API service methods mirror your .NET controller endpoints — same method names and parameter types
  • Angular form validators mirror your FluentValidation rules — same validation logic on both sides
  • TypeScript enums mirror your C# enums — same values and names

When you add a property to an entity, rebuild, and both sides update automatically.

Base Entities

Every entity in Spiderly inherits from one of two base classes.

BusinessObject<T>

For entities that support full CRUD (create, read, update, delete) operations:

public class BusinessObject<T> : IBusinessObject<T> where T : struct
{
    public T Id { get; set; }

    [ConcurrencyCheck]
    [Required]
    public int Version { get; set; }

    [Required]
    public DateTime CreatedAt { get; set; }

    [Required]
    public DateTime ModifiedAt { get; set; }
}
  • Id — Auto-generated primary key. T can be long, int, or byte. Guid is not supported as the PK typeServiceSaveGenerator emits dto.Id > 0 arithmetic checks that fail with CS0019 when T is Guid. Guid scalar properties on entities are fine; only the PK type argument is restricted.
  • Version — Optimistic concurrency control. Spiderly checks this on every update to prevent conflicting writes.
  • CreatedAt / ModifiedAt — Automatically managed by the framework.
namespace YourAppName.Business.Entities
{
    public class BlogPost : BusinessObject<long>
    {
        [Required]
        [StringLength(200, MinimumLength = 1)]
        public string Title { get; set; }

        [UIControlType(nameof(UIControlTypeCodes.TextArea))]
        [StringLength(5000, MinimumLength = 1)]
        public string Content { get; set; }
    }
}

ReadonlyObject<T>

For lookup/reference tables that are read-only from the UI (no create, update, or delete operations):

public class ReadonlyObject<T> : IReadonlyObject<T> where T : struct
{
    public T Id { get; set; }
}

No Version, CreatedAt, or ModifiedAt — these are simple lookup entities with data typically seeded in migrations.

namespace YourAppName.Business.Entities
{
    public class PostStatus : ReadonlyObject<byte>
    {
        [Required]
        [StringLength(100, MinimumLength = 1)]
        public string Name { get; set; }
    }
}

Entity classes must be in a namespace ending with .Entities (e.g., YourAppName.Business.Entities) to be discovered by the source generators.