Skip to content

Carrot.Data.Scaffold.Compilers.Modules.Api

net

Scaffold compiler module that generates the C# API layer - controllers, models, commands, enums, types, and entity-to-API adapters - from the ScaffoldApi graph.

Installation

xml
<ProjectReference Include="..\Carrot.Data.Scaffold.Compilers.Modules.Api\Carrot.Data.Scaffold.Compilers.Modules.Api.csproj" />

Architecture

Compiler Module

ScaffoldCompilerApi extends ScaffoldCompilerModule<ScaffoldApi, ScaffoldCompilerApiOptions>. It reads the ScaffoldApi graph root from this.Scaffold.ScaffoldApi and configures renderers by iterating through:

  1. APIs - one RendererApi (static API class + rate limits) and one RendererApiReadme per API
  2. Controllers - one RendererController per controller (scoped to its parent API)
  3. Enums/Types/Commands/Models - one renderer each, only when the API contains that graph node

Each renderer implements ScaffoldCompilerRenderer<ScaffoldApi, TData> and outputs one or more IScaffoldCompilerRendererOutput items (typically ScaffoldCompilerRendererOutputCSharp wrapping a CSharpFile).

Controller Generation

The controller renderer is the most complex. For each controller it generates:

  • An abstract base class with [ApiController], [Route], rate limiting attributes
  • Per-action methods: the public {Action}Action entry point (with routing, auth, logging, try/catch), a virtual {Action}AuthorizeAsync override point, and an abstract {Action}Async implementation method
  • Search/Sort/Pagination records and interfaces when controller actions declare those capabilities
  • Domain interface types emitted to a separate Interfaces namespace

Model Generation (CQRS)

Models emit multiple record types per scaffold model based on supported CQRS roles (Get, Reference, Create, Update, Upsert, Patch). Each role gets:

  • Filtered property sets from the graph's PropertiesGet, PropertiesCreate, etc.
  • Appropriate interfaces (ICqrsReadModel<T>, ICqrsWriteModel<T>, IHasPrimaryKey, IJsonApiHasResourceId)
  • GetPrimaryKey() methods for composite or single-property keys

Model Adapters

For each non-abstract model, read and write adapter base classes are generated:

  • Read adapters - MapperFactoryBase<TEntity, TModel> with AdaptGet, AdaptReference, and projection methods
  • Write adapters - MapperFactoryBase<TModel, TEntity> with AdaptCreate, AdaptUpdate, AdaptUpsert, AdaptPatch and projection methods
  • Each adapter pair includes a DI-registered interface ([ServiceScope(Scoped)])

Type Resolution

ScaffoldApiGraphApiExtensions provides ResolveTypeName which walks the scaffold graph to resolve CLR types to their API names, handling enums, types, commands, models, primitives, and collections.

Output Structure

Generated files are placed under:

Platform/Api/{Collection}/
  Carrot.Platform.Api.{Collection}.{Api}/
    Controllers/
    Installation/
  Carrot.Platform.Api.{Collection}.{Api}.Models/
    Models/
    Types/
    Commands/
    schemas/
Platform/Domain/{Collection}/
  Carrot.Platform.Domain.{Collection}.{Api}/
    Controllers/   (domain interfaces)
Platform/Contracts/{Collection}/
  Carrot.Platform.Contracts.{Collection}.{Api}/
    Adapters/

Dependencies

DependencyKind
Carrot.Data.Scaffold.Compilersproject (scaffold compiler framework)

Build

bash
dotnet build

Targets net10.0.


Usage Guide

Generate C# API layer code (controllers, models, commands, enums, types, adapters) from a scaffold definition.

Getting Started

Register the Api module on a scaffold compiler:

csharp
IScaffoldCompiler compiler = scaffold.CreateCompiler()
    .AddModuleApi();

Or with options:

csharp
IScaffoldCompiler compiler = scaffold.CreateCompiler()
    .AddModuleApi(options =>
    {
        // Configure options as needed
    });

Then compile:

csharp
IScaffoldCompilerResult result = compiler.Compile();

Common Patterns

Generated Controller Structure

Each controller action generates three methods:

csharp
// 1. Authorization hook (virtual, returns true by default)
protected virtual Task<bool> GetUsersAuthorizeAsync(
    CancellationToken cancellationToken = default)
{
    return Task.FromResult(true);
}

// 2. Implementation method (abstract, you implement this)
protected abstract async Task<IJsonApiDataListResponse<UserGet>>
    GetUsersAsync(
        GetUsers_Search search,
        GetUsers_Pagination pagination,
        CancellationToken cancellationToken = default);

// 3. Action entry point (generated, handles routing/auth/logging/errors)
[HttpGet]
[Authorize]
[RequireHttps]
[Produces("application/json")]
public async Task<ActionResult<UserGet[]>> GetUsersAction(
    [FromQuery] GetUsers_Search search,
    [FromQuery] GetUsers_Pagination pagination,
    CancellationToken cancellationToken = default)
{
    // ... logging, auth, error handling, etc.
}

CQRS Model Projections

A single scaffold model produces multiple C# records:

csharp
// Base model with all properties
public record User : IModel, ISupportsCqrsRoles { ... }

// Read projections
public record UserGet : ICqrsReadModel<User>, IHasPrimaryKey { ... }
public record UserReference : ICqrsReadModel<User>, IHasPrimaryKey { ... }

// Write projections
public record UserCreate : ICqrsWriteModel<User> { ... }
public record UserUpdate : ICqrsWriteModel<User> { ... }
public record UserPatch  : ICqrsWriteModel<User> { ... }

Using Generated Adapters

Implement the generated adapter base to map between your EF entities and API models:

csharp
public class UserReadAdapter : UserReadAdapterBase<UserEntity>
{
    protected override User Create(UserEntity entity)
    {
        return new User
        {
            Id = entity.Id,
            Name = entity.Name,
            Email = entity.Email,
        };
    }
}

Tips

  • Controllers are abstract - you must subclass them and implement each {Action}Async method in your domain project.
  • Rate limiting is configured per-controller and per-action via the scaffold graph. The module generates rate limit installation code automatically.
  • Model adapters handle CQRS projection. The ProjectGet, ProjectReference, ProjectCreate, etc. methods are generated with property mapping from the graph - override them if you need custom logic.
  • Relationship filtering excludes properties with Included or MetaLink relationship types from model records (these are handled separately via JSON:API includes).

Carrot