Skip to content

Carrot.Data.Scaffold.Process.Api

net

API scaffolding process -- discovers API definitions, controllers, models, commands, enums, types, traits, and rate limits from an assembly and builds a complete API graph.

Installation

Project reference:

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

Architecture

Process & Concepts

ScaffoldApiProcess extends ScaffoldProcess<ScaffoldApi, ScaffoldApiOptions> and registers 12 concepts covering the full API surface:

ConceptFlavorKeyGraph Type
CollectionDeclarativeSingle(singleton)ScaffoldApiGraphCollection
ApisDeclarativestringScaffoldApiGraphApi
ControllersDeclarativestringScaffoldApiGraphController
ModelsIntrospectiveTypeScaffoldApiGraphModel
ModelAbstractIntrospectiveAbstractTypeScaffoldApiGraphModel
CommandsIntrospectiveTypeScaffoldApiGraphCommand
EnumsIntrospectiveTypeScaffoldApiGraphEnum
TypesIntrospectiveTypeScaffoldApiGraphType
TraitsIntrospectiveTypeScaffoldApiGraphTrait
TraitBehavioursDeclarativestringScaffoldApiGraphTraitBehaviour
RateLimitsDeclarativestringScaffoldApiGraphRateLimit
ResponseStatusSetsDeclarativestringScaffoldApiGraphResponseStatusSet

Controller Action System

Controllers use a partial class pattern with HTTP verb methods (ActionGet, ActionPost, ActionPut, ActionDelete, ActionPatch, ActionHead, ActionUpload). Each action is keyed by (route, verb) and supports:

  • Request/response type configuration (JSON, JSON:API, stream, download, none)
  • Authentication requirements (per-action or inherited from controller)
  • Capabilities: filter, search, sort, pagination
  • Rate limiting policy references
  • Custom parameters (route, query, header)
  • Status code overrides

Model System

Models are introspective (ScaffoldApiDefineModel<TModel> where TModel : IEntity). The configure API supports:

  • Primary key and identity definitions via expression selectors
  • Property configuration with type-safe expression-based accessors
  • Projections (named property subsets, e.g., "summary", "detail")
  • Relationships (one-to-one, one-to-many) with visibility, inclusion, detail level, and pagination config
  • Inheritance (InheritsFrom<TBase>)
  • Abstract base model scaffolds that apply to all concrete inheritors

Trait System

Traits define reusable property/projection sets that can be applied across multiple models via trait behaviours.

Naming Policy

IScaffoldApiNamingPolicy / ScaffoldApiNamingPolicyDefault controls generated API names.

Dependencies

  • Carrot.Data.Scaffold.Process -- core scaffolding framework
  • Carrot.Web.Http -- HTTP verb types
  • .NET 10

Build

bash
dotnet build

Usage Guide

Getting Started

csharp
using Carrot.Data.Scaffold;
using Carrot.Data.Scaffold.Process;

var process = new ScaffoldApiProcess(
    typeof(MyAssemblyMarker).Assembly,
    new ScaffoldApiOptions(),
    log);

ScaffoldApi result = process.Scaffold();
// result.Controllers, result.Models, result.Apis, etc.

Common Patterns

Defining an API

csharp
public class PlatformApi : ScaffoldApiDefineApi
{
    public override string ApiName => "Platform";
    public override string DomainName => "Platform Services";

    public override void Scaffold(ScaffoldApiConfigureApi config, ILog? log = null)
    {
        // API-level configuration
    }
}

Defining a Controller

csharp
public class UsersController : ScaffoldApiDefineController
{
    public override string BaseRoute => "platform/users";

    protected override void ScaffoldImpl(ScaffoldApiConfigureController config, ILog? log = null)
    {
        config.AuthenticationRequirement(ScaffoldApiAuthenticationRequirement.Required);

        config.ActionGet("{id}", action =>
        {
            action.Response.Json<UserDetailResponse>();
        });

        config.ActionGet("", action =>
        {
            action.Response.JsonApi();
            action.Capabilities.Paginated();
            action.Capabilities.CanFilter(f => f.Add("status"));
            action.Capabilities.CanSearch();
            action.Capabilities.CanSort();
        });

        config.ActionPost("", action =>
        {
            action.Request.Json<CreateUserRequest>();
            action.Response.Json<UserDetailResponse>();
        });

        config.ActionDelete("{id}");

        config.ActionUpload("{id}/avatar", action =>
        {
            action.UploadLimits(limits =>
            {
                limits.MaxFileSize(5_000_000);
                limits.MaxFileCount(1);
            });
        });
    }
}

Defining a Model

csharp
public class UserScaffold : ScaffoldApiDefineModel<User>
{
    public override void Scaffold(ScaffoldApiConfigureModel<User> config, ScaffoldApiReflectedModel model, ILog? log = null)
    {
        config.HasPrimaryKey(x => x.Id);

        config.HasIdentity("slug", x => x.Slug);

        config.Property<string>(x => x.Email, p => p.ReadOnly());
        config.Property<string>(x => x.DisplayName);
        config.Property<DateTimeOffset>(x => x.CreatedAt, p => p.ReadOnly());

        config.ProjectionSummary(p =>
        {
            p.Include(x => x.Id);
            p.Include(x => x.DisplayName);
        });

        config.ProjectionDetail(p =>
        {
            p.Include(x => x.Id);
            p.Include(x => x.Email);
            p.Include(x => x.DisplayName);
            p.Include(x => x.CreatedAt);
        });

        config.RelationshipMany<ICollection<Role>>(x => x.Roles, rel =>
        {
            rel.DetailLevel(dl => dl.Summary());
        });

        config.RelationshipOne<Organisation?>(x => x.Organisation, rel =>
        {
            rel.DetailLevel(dl => dl.Summary());
        });
    }
}

Abstract Base Model

Apply shared configuration to all models inheriting from a base type:

csharp
public class PlatformEntityScaffold : ScaffoldApiDefineAbstract<PlatformEntity>
{
    public override void Scaffold(ScaffoldApiConfigureModel<PlatformEntity> config, ScaffoldApiReflectedModel model, ILog? log = null)
    {
        config.HasPrimaryKey(x => x.Id);
        config.Property<DateTimeOffset>(x => x.CreatedAt, p => p.ReadOnly());
        config.Property<DateTimeOffset>(x => x.UpdatedAt, p => p.ReadOnly());
    }
}

Defining an Enum

csharp
public class UserStatusScaffold : ScaffoldApiDefineEnum<UserStatus>
{
    public override void Scaffold(ScaffoldApiConfigureEnum config, ILog? log = null)
    {
        // Enum values are auto-discovered; configure overrides here
    }
}

Defining a Rate Limit

csharp
public class StandardRateLimit : ScaffoldApiDefineRateLimit
{
    public override string GraphKey => "standard";
    public override string LogicalName => "Standard";

    public override void Scaffold(ScaffoldApiConfigureRateLimit config, ILog? log = null)
    {
        config.Fixed(options =>
        {
            options.Window(TimeSpan.FromMinutes(1));
            options.PermitLimit(100);
        });
    }
}

Tips / Gotchas

  • Controller names are auto-derived from the class name by stripping "Scaffold" and "Controller". Override ControllerName to set explicitly.
  • TargetApi is auto-resolved from the first segment of BaseRoute if not set. E.g., platform/users resolves to API "Platform".
  • Projection names are normalized to kebab-case internally. "Detail" and "detail" are the same projection.
  • Abstract model scaffolds run at priority 1 (before everything else), so base configurations are in place before concrete models scaffold.
  • ScaffoldApiReflectedModel provides IsConcrete -- by default only concrete types are scaffolded. Override TypeValid() to change this.

Carrot