Skip to content

Pragmatic.Actions

Source-generated CQRS-style domain actions for .NET 10. Declare the operation; the generator writes the pipeline — DI, validation, authorization, telemetry, persistence — at compile time, zero reflection.

Every business operation repeats the same ceremony: resolve dependencies, validate, check authorization, execute, handle errors, persist, log, record telemetry. Across a service with 10+ methods, the plumbing dwarfs the logic.

// Without Pragmatic: 60+ lines per operation, most of it plumbing
public async Task<Result<Guid>> CreateReservationAsync(
CreateReservationRequest request, ClaimsPrincipal user, CancellationToken ct)
{
if (!(await auth.AuthorizeAsync(user, "booking.reservation.create")).Succeeded)
return Result<Guid>.Failure(new ForbiddenError());
var validation = await validator.ValidateAsync(request, ct);
if (!validation.IsValid) return Result<Guid>.Failure(new ValidationError(validation.Errors));
logger.LogInformation("Creating reservation for {GuestId}", request.GuestId);
// ... business logic ...
reservations.Add(reservation);
await unitOfWork.SaveChangesAsync(ct);
return reservation.Id;
}
// ...repeated for every method.

Declare what the operation does; the generator handles how.

[DomainAction]
[RequirePolicy<ReservationManagementPolicy>]
[Endpoint(HttpVerb.Post, "api/v1/reservations")]
[Validate]
public partial class CreateReservationAction : DomainAction<Guid, RoomUnavailableError>
{
private IRepository<Reservation, Guid> _reservations = null!; // injected by the generator
private IReadRepository<Property, Guid> _properties = null!;
public required CreateReservationRequest Request { get; init; }
public override async Task<Result<Guid, IError>> Execute(CancellationToken ct = default)
{
var property = await _properties.GetByIdAsync(Request.PropertyId, ct);
if (property is null) return NotFoundError.For<Property, Guid>(Request.PropertyId);
var reservation = Reservation.Create(/* ... */);
_reservations.Add(reservation);
return reservation.Id;
}
}

The generator produces the invoker pipeline, DI/field injection, authorization enforcement, validation, telemetry, and persistence. For entity CRUD it collapses even further — a Mutation<T> is the whole class (property mapping, lifecycle, validation, persistence, DI all generated):

[Mutation(Mode = MutationMode.Create)]
[RequirePermission(CatalogPermissions.Amenity.Create)]
[Endpoint(HttpVerb.Post, "api/v1/amenities")]
public partial class CreateAmenityMutation : Mutation<Amenity>
{
public required string Name { get; init; }
public AmenityCategory Category { get; init; }
}
Base classForReturns
DomainAction<TReturn> (<TReturn, TError>)Custom logic: orchestrate repos, compute, call servicesResult<TReturn, …>
VoidDomainActionCommands with no return valueVoidResult
Mutation<TEntity>Entity CRUD (create/update/delete) — minimal boilerplatethe entity

Operation taxonomy (Mutation / SideEffect / Query) and the pipeline (filters → execute → save → after-hooks) are covered in Concepts.

Terminal window
dotnet add package Pragmatic.Actions
dotnet add package Pragmatic.SourceGenerator # the unified analyzer

(Building inside this monorepo? See Monorepo Structure.)

  • Invoker pipeline — before/after filters, validation, authorization, save ordering, telemetry.
  • DI & field injection — private fields resolved automatically; no constructor boilerplate.
  • Authorization[RequirePermission] / [RequirePolicy] enforced in the pipeline.
  • Validation[Validate] runs L1/L2 before Execute().
  • Boundaries[BelongsTo<T>] keys the unit of work / DbContext.
  • Action versioning, entity pre-loading, and composite (single-transaction) actions.

Core DomainAction/Mutation pipeline, filters, authorization, validation, boundaries, and versioning are functional within the 0.8 preview. See the roadmap.

| Concepts | Pipeline lifecycle, operation taxonomy, base-class decision guide, composite actions | | Getting Started | Your first DomainAction and Mutation | | Mutations | Mutation<T>, modes, property mapping, entity lifecycle | | Pipeline | Filters, ordering, authorization, validation, save/event ordering | | Boundaries | [BelongsTo<T>], sub-boundaries, keyed unit of work | | Common Mistakes | The most frequent action pitfalls | | Troubleshooting | Problem/solution guide with diagnostics |

Actions are invoked by Endpoints, persist via Persistence, validate via Validation, authorize via Authorization, and are wired by Composition.

  • .NET 10.0+
  • Pragmatic.SourceGenerator analyzer

Part of the Pragmatic.Design ecosystem — see Licensing. Pragmatic.Actions is licensed under the PolyForm Small Business 1.0.0 license (free for small businesses; commercial license above the threshold).