Appearance
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:
| Concept | Flavor | Key | Graph Type |
|---|---|---|---|
| Collection | DeclarativeSingle | (singleton) | ScaffoldApiGraphCollection |
| Apis | Declarative | string | ScaffoldApiGraphApi |
| Controllers | Declarative | string | ScaffoldApiGraphController |
| Models | Introspective | Type | ScaffoldApiGraphModel |
| ModelAbstract | IntrospectiveAbstract | Type | ScaffoldApiGraphModel |
| Commands | Introspective | Type | ScaffoldApiGraphCommand |
| Enums | Introspective | Type | ScaffoldApiGraphEnum |
| Types | Introspective | Type | ScaffoldApiGraphType |
| Traits | Introspective | Type | ScaffoldApiGraphTrait |
| TraitBehaviours | Declarative | string | ScaffoldApiGraphTraitBehaviour |
| RateLimits | Declarative | string | ScaffoldApiGraphRateLimit |
| ResponseStatusSets | Declarative | string | ScaffoldApiGraphResponseStatusSet |
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 frameworkCarrot.Web.Http-- HTTP verb types- .NET 10
Build
bash
dotnet buildUsage 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
ControllerNameto set explicitly. - TargetApi is auto-resolved from the first segment of
BaseRouteif not set. E.g.,platform/usersresolves 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.
ScaffoldApiReflectedModelprovidesIsConcrete-- by default only concrete types are scaffolded. OverrideTypeValid()to change this.