CrudKit.Api 1.2.0

dotnet add package CrudKit.Api --version 1.2.0
                    
NuGet\Install-Package CrudKit.Api -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CrudKit.Api" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CrudKit.Api" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="CrudKit.Api" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CrudKit.Api --version 1.2.0
                    
#r "nuget: CrudKit.Api, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CrudKit.Api@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CrudKit.Api&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=CrudKit.Api&version=1.2.0
                    
Install as a Cake Tool

<picture> <source media="(prefers-color-scheme: dark)" srcset="assets/banner-dark.png"> <source media="(prefers-color-scheme: light)" srcset="assets/banner-light.png"> <img alt="CrudKit" src="assets/banner-light.png" width="300"> </picture>

A convention-based CRUD framework for .NET 10. Define entities, get endpoints.

Build Tests .NET License Version


Features

  • Auto-mapped REST endpoints (List, Get, Create, Update, Delete)
  • Soft delete with cascade and restore
  • Multi-tenant isolation (ITenantContext + 5 built-in resolvers + 3-layer cross-tenant protection + IDataFilter<T> for runtime filter control)
  • Audit trail ([Audited] + UseAuditTrail() opt-in, [AuditIgnore] per-property, [Hashed] auto-masked)
  • Per-operation authorization (Authorize() builder — role-based, permission-based, convention)
  • Custom endpoints via .WithCustomEndpoints() under same route group
  • Optimistic concurrency
  • Lifecycle hooks (ICrudHooks<T>) + global hooks (IGlobalCrudHook) for cross-cutting concerns
  • Validation (FluentValidation priority, DataAnnotation fallback)
  • State machine transitions (IStateMachine<TState>)
  • Master-child relationships (fluent .WithChild() or declarative [ChildOf])
  • Idempotency via request header
  • Bulk operations (/bulk-delete, /bulk-update)
  • ReadOnly entities (List + Get only)
  • CSV import/export ([Exportable], [Importable], per-property control)
  • Auto-registration — UseCrudKit() scans assemblies for [CrudEntity] types and registers endpoints automatically
  • [CreateDtoFor] / [UpdateDtoFor] — manual DTOs discovered by auto-registration
  • Optional<T> for partial updates (distinguishes null from missing)
  • Property attributes: [Hashed], [SkipResponse], [SkipUpdate], [Protected], [Unique], [Searchable]
  • Filter/sort control per entity and property ([NotFilterable], [NotSortable])
  • Modular monolith support (IModule with assembly scan, multi-DbContext auto-resolution)
  • Multi-database dialect (SQLite, PostgreSQL, SQL Server, MySQL/MariaDB) — auto-detected
  • Entity base class hierarchy (Entity, AuditableEntity, FullAuditableEntity — with <TUser> variants)
  • Enum properties stored as strings automatically
  • Structured error responses (409 concurrency, dev/prod stack trace toggle)
  • Domain events (IDomainEvent, IHasDomainEvents, IDomainEventHandler<T>, automatic dispatch after SaveChanges)
  • Auto-sequence ([AutoSequence] with {year}, {month}, {seq:N} tokens, per-tenant isolation, ISequenceCustomizer<T>)
  • AggregateRoot hierarchy (AggregateRoot, AuditableAggregateRoot, FullAuditableAggregateRoot with domain event support)
  • Value object flattening ([ValueObject] + [Flatten] for flat DTO properties)
  • Smart cascade restore (DeleteBatchId — only restores children deleted in same batch)
  • Custom index support ([CrudIndex] with tenant-aware composite indexes, custom names)
  • IEndpointConfigurer<T> — auto-discovered custom endpoint configuration
  • Entity-as-DTO — MapCrudEndpoints<T>() without DTOs, entity used directly
  • [ResponseDtoFor] — custom response DTOs
  • CrudKitDbContextDependencies — simplified 2-parameter DbContext constructor
  • UseModuleSchema() — cross-provider schema support (PostgreSQL, SQL Server = schema; MySQL, SQLite = skipped)
  • Reflection metadata caching for performance
  • Hook-aware bulk operations (entities loaded, hooks triggered)
  • System fields hidden from responses (TenantId, DeleteBatchId, DomainEvents)

Quick Start

1. Register services

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(opts =>
    opts.UseSqlite("Data Source=app.db"));

builder.Services.AddCrudKit<AppDbContext>(opts =>
{
    opts.DefaultPageSize = 25;
    opts.MaxPageSize = 100;
    opts.UseAuditTrail();
    opts.UseMultiTenancy().ResolveTenantFromHeader("X-Tenant-Id");
});

2. Map endpoints

var app = builder.Build();
app.UseCrudKit(); // auto-registers all [CrudEntity] types

// Or register manually for full control:
// app.MapCrudEndpoints<Product, CreateProduct, UpdateProduct>();

app.Run();

3. Define an entity

[CrudEntity]
[Audited]
[RequirePermissions]  // auto-convention: products:read, products:create, ...
public class Product : FullAuditableEntity
{
    [Required, MaxLength(200), Searchable]
    public string Name { get; set; } = string.Empty;

    [Range(0.01, 999_999.99)]
    public decimal Price { get; set; }

    [Unique, SkipUpdate]
    public string Sku { get; set; } = string.Empty;

    [Hashed, SkipResponse]
    public string InternalToken { get; set; } = string.Empty;
}

Generated endpoints

Method Route Description
GET /api/products List (paginated, filtered, sorted)
GET /api/products/{id} Get by ID
POST /api/products Create
PUT /api/products/{id} Update (partial via Optional<T>)
DELETE /api/products/{id} Soft-delete (ISoftDeletable)
DELETE /api/products/{id}/purge Permanently delete a single soft-deleted record
DELETE /api/products/purge?olderThan=30 Bulk purge soft-deleted records older than N days
POST /api/products/{id}/restore Restore soft-deleted record
POST /api/products/{id}/transition/{action} State transition (IStateMachine<TState> only)

Mappers are optional. Without them, CrudKit uses reflection for DTO ↔ entity mapping and serializes entities directly for responses. Register IResponseMapper<T, TResponse> for custom response shapes.


Entity Hierarchy

Class Provides
Entity Guid Id
Entity<TKey> Custom key type (e.g. long, int)
AuditableEntity Id + CreatedAt, UpdatedAt
AuditableEntityWithUser<TUser> + CreatedById, UpdatedById (auto-set from ICurrentUser) + navigations
FullAuditableEntity AuditableEntity + DeletedAt (implements ISoftDeletable)
FullAuditableEntityWithUser<TUser> + CreatedById, UpdatedById, DeletedById (auto-set) + navigations
AggregateRoot Id + domain events (IHasDomainEvents)
AuditableAggregateRoot + CreatedAt, UpdatedAt + domain events
AuditableAggregateRootWithUser<TUser> + CreatedById, UpdatedById (auto-set) + domain events
FullAuditableAggregateRoot + DeletedAt, DeleteBatchId + domain events
FullAuditableAggregateRootWithUser<TUser> + CreatedById, UpdatedById, DeletedById (auto-set) + domain events
// Lookup table — Guid Id only
public class Currency : Entity { }

// Timestamps + soft delete
[CrudEntity]
[RequireAuth]
[AuthorizeOperation("Delete", "admin")]
public class Order : FullAuditableEntity
{
    public string CustomerName { get; set; } = string.Empty;
    public decimal Total { get; set; }
}

// Timestamps + user tracking + soft delete
[CrudEntity]
[RequireRole("admin")]
public class Invoice : FullAuditableEntityWithUser<AppUser>
{
    public string InvoiceNumber { get; set; } = string.Empty;
    public decimal Total { get; set; }
}

// Custom key type
public class LegacyProduct : AuditableEntity<long> { }
public class LegacyOrder : FullAuditableEntityWithUser<long, AppUser, int> { }

Auto-Sequence

Use [AutoSequence] on a string property to generate formatted sequential numbers automatically. Supports {year}, {month}, {seq:N} tokens, per-tenant isolation, and custom logic via ISequenceCustomizer<T>.

[CrudEntity]
public class Invoice : FullAuditableAggregateRoot, IMultiTenant, IStateMachine<InvoiceStatus>
{
    [AutoSequence("INV-{year}-{seq:5}")]
    public string InvoiceNumber { get; set; } = "";

    [Protected]
    public InvoiceStatus Status { get; set; } = InvoiceStatus.Draft;

    public string TenantId { get; set; } = "";

    public static IReadOnlyList<(InvoiceStatus From, InvoiceStatus To, string Action)> Transitions => [...];
}
// → INV-2026-00001 auto-generated, per-tenant sequence

Configuration

builder.Services.AddCrudKit<AppDbContext>(opts =>
{
    // Pagination
    opts.DefaultPageSize = 25;           // Default: 20
    opts.MaxPageSize = 100;              // Default: 100

    // Routing
    opts.ApiPrefix = "/api";             // Default: "/api"

    // Bulk operations
    opts.BulkLimit = 1_000;              // Default: 1,000

    // Idempotency
    opts.EnableIdempotency = true;       // Default: false

    // Audit trail — opt entities in with [Audited]
    opts.UseAuditTrail();
    // or with custom writer:
    opts.UseAuditTrail<ElasticAuditWriter>()
        .EnableAuditFailedOperations();

    // Import / Export
    opts.UseExport();                    // All entities exportable (opt-out with [NotExportable])
    opts.MaxExportRows = 50_000;         // Default: 50,000
    opts.UseImport();                    // All entities importable (opt-out with [NotImportable])
    opts.MaxImportFileSize = 10 * 1024 * 1024; // Default: 10 MB

    // Enum storage
    opts.UseEnumAsString();              // Store enums as strings in DB

    // Multi-tenancy
    opts.UseMultiTenancy()
        .ResolveTenantFromHeader("X-Tenant-Id")
        .RejectUnresolvedTenant()
        .CrossTenantPolicy(p => p.Allow("superadmin"));

    // Global hooks
    opts.UseGlobalHook<SearchIndexHook>();

    // Module discovery
    opts.ScanModulesFromAssembly = typeof(Program).Assembly;
});
Method Returns Description
UseAuditTrail() AuditTrailOptions Enable audit trail, opt entities in with [Audited]
UseAuditTrail<T>() AuditTrailOptions Same, with custom IAuditWriter implementation
UseExport() CrudKitApiOptions Enable CSV export globally
UseImport() CrudKitApiOptions Enable CSV import globally
UseEnumAsString() CrudKitApiOptions Store all enum properties as strings
UseMultiTenancy() MultiTenancyOptions Enable multi-tenancy, chain resolver method
UseGlobalHook<T>() CrudKitApiOptions Register a global IGlobalCrudHook
UseDomainEvents() CrudKitApiOptions Enable domain event dispatching after SaveChanges

Multi-Tenant Program.cs Example

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(opts =>
    opts.UseSqlite("Data Source=app.db"));

builder.Services.AddScoped<ICurrentUser, JwtCurrentUser>();

builder.Services.AddCrudKit<AppDbContext>(opts =>
{
    opts.UseMultiTenancy()
        .ResolveTenantFromClaim("tenant_id")
        .RejectUnresolvedTenant()
        .CrossTenantPolicy(p => p.Allow("superadmin"));
});

var app = builder.Build();
app.UseCrudKit(); // auto-registers all [CrudEntity] types
app.Run();

Entities implementing IMultiTenant are automatically scoped to the resolved tenant. Inject IDataFilter<ISoftDeletable> to temporarily disable the soft-delete filter within a request:

public class OrderService
{
    private readonly IDataFilter<ISoftDeletable> _softDeleteFilter;
    private readonly IRepo<Order> _repo;

    public async Task<List<Order>> GetDeletedOrdersAsync()
    {
        using (_softDeleteFilter.Disable())
        {
            return (await _repo.List(new ListParams(), default)).Data;
        }
    }
}

Project Structure

src/
├── CrudKit.Core/                # Attributes, interfaces, models
├── CrudKit.EntityFrameworkCore/ # EF Core integration, repository, query
├── CrudKit.Api/                 # Minimal API layer, endpoint mapping, filters
└── CrudKit.Identity/            # ASP.NET Identity integration (CrudKitIdentityDbContext)
tests/
├── CrudKit.Core.Tests/
├── CrudKit.EntityFrameworkCore.Tests/
├── CrudKit.Api.Tests/
├── CrudKit.Identity.Tests/
└── CrudKit.Integration.Tests/      # Provider-agnostic tests (SQLite + PostgreSQL via Testcontainers)
samples/
└── CrudKit.Sample.Api/          # Working sample with Product, Category, Order, Unit
docs/
├── API-REFERENCE.md             # Full feature reference
└── specs/                       # Internal design specifications

Master-Child Relationships

Declare child entities with [ChildOf] and CrudKit generates nested endpoints automatically under the parent route.

[ChildOf(typeof(Order))]
public class OrderLine : AuditableEntity
{
    public Guid OrderId { get; set; }           // FK convention: {ParentType}Id
    public string ProductName { get; set; } = string.Empty;
}

// Auto-generated: GET/DELETE /api/orders/{id}/order-lines, GET/POST /api/orders/{id}/order-lines/{id}
// Custom route + FK:
[ChildOf(typeof(Order), Route = "items", ForeignKey = "ParentOrderId")]

For explicit control use the fluent API: app.MapCrudEndpoints<Order, ...>().WithChild<OrderLine, CreateOrderLine>("items", "OrderId");


Manual DTOs with [CreateDtoFor] / [UpdateDtoFor]

When a DTO is annotated with [CreateDtoFor(typeof(T))] or [UpdateDtoFor(typeof(T))], auto-registration discovers and wires them up automatically.

[CreateDtoFor(typeof(Order))]
public record CreateOrder([Required] string CustomerName, decimal Total = 0);

[UpdateDtoFor(typeof(Order))]
public record UpdateOrder
{
    public Optional<string?> CustomerName { get; init; }
}

Purge Endpoints

For ISoftDeletable entities, CrudKit exposes two purge endpoints for permanent (hard) deletion:

// Single item — must be soft-deleted first
DELETE /api/products/{id}/purge
// Response: 204 No Content

// Bulk — deletes all soft-deleted records older than N days
DELETE /api/products/purge?olderThan=30
// Response: { "purged": 15 }

Single purge requires the record to be soft-deleted already (returns 400 if active). Bulk purge requires olderThan (minimum 1 day). Both use ExecuteDeleteAsync — bypasses soft-delete interception. Respects tenant isolation for IMultiTenant entities.


Documentation

Full API reference: docs/API-REFERENCE.md


License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 107 4/16/2026
1.1.0 115 4/16/2026
1.0.0 105 4/15/2026