Appearance
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:
- APIs - one
RendererApi(static API class + rate limits) and oneRendererApiReadmeper API - Controllers - one
RendererControllerper controller (scoped to its parent API) - 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}Actionentry point (with routing, auth, logging, try/catch), a virtual{Action}AuthorizeAsyncoverride point, and an abstract{Action}Asyncimplementation method - Search/Sort/Pagination records and interfaces when controller actions declare those capabilities
- Domain interface types emitted to a separate
Interfacesnamespace
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>withAdaptGet,AdaptReference, and projection methods - Write adapters -
MapperFactoryBase<TModel, TEntity>withAdaptCreate,AdaptUpdate,AdaptUpsert,AdaptPatchand 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
| Dependency | Kind |
|---|---|
Carrot.Data.Scaffold.Compilers | project (scaffold compiler framework) |
Build
bash
dotnet buildTargets 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}Asyncmethod 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
IncludedorMetaLinkrelationship types from model records (these are handled separately via JSON:API includes).