Part of DKNet.SlimBus.Generators. Mark a public constructor or method with [CrudCreate], [CrudUpdate] or [CrudAction] — all three live in DKNet.EfCore.Abstractions.Attributes, so the domain layer takes on no messaging dependency — and the generator turns it into a sealed partial record request:
// Domain project
public class Product : Entity
{
private Product() { } // EF
[CrudCreate]
public Product([Required, MaxLength(100)] string name, decimal price)
{
Name = name;
Price = price;
}
public string Name { get; private set; } = null!;
public decimal Price { get; private set; }
[CrudUpdate]
public void UpdatePrice([Range(0, 1_000_000)] decimal price) => Price = price;
}
// API project
[GenerateDto(typeof(Product))]
public partial record ProductDto;
emits — verbatim, from obj/.../generated/.../ProductCrudRequests.g.cs after compiling the Quick Start project:
// <auto-generated by DKNet.SlimBus.Generators />
#nullable enable
namespace QsApi.Crud;
/// <summary>Create request generated from Product's [CrudCreate] constructor.</summary>
public sealed partial record CreateProductRequest : global::DKNet.SlimBus.Extensions.Fluents.Requests.IWitResponse<global::Api.ProductDto>
{
/// <summary>Maps to constructor parameter 'name'.</summary>
[global::System.ComponentModel.DataAnnotations.Required]
[global::System.ComponentModel.DataAnnotations.MaxLength(100)]
public required string Name { get; init; }
/// <summary>Maps to constructor parameter 'price'.</summary>
public required decimal Price { get; init; }
}
/// <summary>Update request generated from Product.UpdatePrice.</summary>
public sealed partial record UpdatePriceProductRequest :
global::DKNet.SlimBus.Extensions.Fluents.Requests.IWitResponse<global::Api.ProductDto>,
global::DKNet.SlimBus.Extensions.Fluents.Requests.IWithKey<global::System.Guid>
{
/// <summary>The target Product identifier (bound from route).</summary>
public global::System.Guid Id { get; set; }
/// <summary>Maps to method parameter 'price'.</summary>
[global::System.ComponentModel.DataAnnotations.Range(0, 1000000)]
public required decimal Price { get; init; }
}
/// <summary>Delete request generated for Product, carrying the target's key bound from the route.</summary>
public sealed partial record DeleteProductRequest : global::DKNet.SlimBus.Extensions.Fluents.Requests.IWithKey<global::System.Guid>
{
/// <summary>The target Product identifier (bound from route).</summary>
public global::System.Guid Id { get; set; }
}
![An attributed member is checked for public accessibility, then the compiling project’s [GenerateDto] DTO and the entity’s IEntity
The generator scans both the compiling project and its referenced assemblies, so an entity in a separate Domain project is discovered from the API project with nothing redeclared. Referenced assemblies whose name is, or starts with a dotted prefix of, System, Microsoft, netstandard, mscorlib, FluentResults, Mapster, SlimMessageBus, Shouldly, or xunit are skipped — the match is exact-or-dot-prefixed, so a project named Systemic.Domain is still scanned.
DTO resolution. The generated IWitResponse<TDto> needs a concrete DTO, resolved at generation time by scanning the compiling project for the partial record marked [GenerateDto(typeof(TEntity))] (from DKNet.EfCore.DtoGenerator). Exactly one match is required — zero is DKCRUDGEN001, more than one is DKCRUDGEN002 — because the domain-side attribute cannot reference an API-layer type itself. GenerateDtoAttribute is internal, which is fine for a package consumer: the attribute ships as a source file compiled directly into the consuming assembly.
Request shape. Properties mirror the member’s parameters (PascalCase names, original types), required unless the parameter is nullable. System.ComponentModel.DataAnnotations attributes on the parameters ([Required], [Range], …) are copied onto the generated properties — metadata only, see Gotchas.
[CrudCreate] → implements IWitResponse<TDto>.[CrudUpdate] and [CrudAction] → implement IWitResponse<TDto> and IWithKey<TKey>, whose Id is bound from the route.| Option | Type | Default | Effect |
|---|---|---|---|
[CrudCreate].Name / [CrudUpdate].Name |
string? |
null |
Overrides the generated request type name (default Create{Entity}Request / {Method}{Entity}Request). |
The entity must implement DKNet.EfCore.Abstractions.Entities.IEntity<TKey> (DKCRUDGEN006), and every marked member must be public (DKCRUDGEN004). DKNet.EfCore.DtoGenerator supplies the DTO this step resolves against; Generate handlers is what dispatches the request this step produces.
[CrudCreate] per entity. A second constructor you would like exposed needs a hand-written slice (DKCRUDGEN003 otherwise).[CrudCreate] is also valid on a public method, but the generated handler always calls the entity’s constructor with the marked member’s parameter list — a marked factory method’s body is never executed, and the code only compiles when a matching constructor exists. Prefer marking the constructor.DataAnnotations attributes are metadata only; minimal APIs do not run DataAnnotations validation on their own.