PANiXiDA.Core.Application
4.0.2
See the version list below for details.
dotnet add package PANiXiDA.Core.Application --version 4.0.2
NuGet\Install-Package PANiXiDA.Core.Application -Version 4.0.2
<PackageReference Include="PANiXiDA.Core.Application" Version="4.0.2" />
<PackageVersion Include="PANiXiDA.Core.Application" Version="4.0.2" />
<PackageReference Include="PANiXiDA.Core.Application" />
paket add PANiXiDA.Core.Application --version 4.0.2
#r "nuget: PANiXiDA.Core.Application, 4.0.2"
#:package PANiXiDA.Core.Application@4.0.2
#addin nuget:?package=PANiXiDA.Core.Application&version=4.0.2
#tool nuget:?package=PANiXiDA.Core.Application&version=4.0.2
PANiXiDA.Core.Application
PANiXiDA.Core.Application is a .NET library with application-layer abstractions for Clean Architecture, CQRS, and DDD-based services.
It defines contracts and small reusable building blocks for commands, queries, request behaviors, domain event publishing, unit-of-work orchestration, read repositories, aggregate tracking, and read-side paging helpers. The package intentionally does not provide a concrete mediator, database provider, dependency injection module, or transport-specific implementation.
Status
Features
- CQRS request contracts:
ICommand<TResult>,IQuery<TResult>, andIRequest<TResult>. - Mediator contracts for command/query dispatch and handler implementation.
- Pipeline behavior contracts for before, after, and finally request stages, including before-stage success/failure results.
- Built-in behaviors for FluentValidation request validation, transaction start, commit, cleanup, and domain event publishing.
- FluentValidation extensions for converting single-property and complex domain factory
Result<T>errors into validation failures. - Event bus and event handler abstractions for
DomainEventintegration. - Unit of work, read repository, and aggregate tracker abstractions for application persistence boundaries.
IReadModelmarker interface for immutable read-side result models.- Read-side helper models for page-based pagination, cursor pagination, multi-field sorting, filtering, and validated result limits.
- Immutable sorting criteria, optional default merging, and generated read-model-specific FluentValidation validators.
Requirements
- .NET 10 SDK
- Nullable reference types enabled in consuming projects is recommended
Installation
<ItemGroup>
<PackageReference Include="PANiXiDA.Core.Application" Version="4.0.0" />
</ItemGroup>
Basic Usage
Command Contract
using PANiXiDA.Core.Application.Messaging.Mediator.Contracts;
using PANiXiDA.Core.Application.Messaging.Mediator.Handlers;
using PANiXiDA.Core.ResultPattern;
public sealed record PingCommand : ICommand<Result>;
public sealed class PingCommandHandler : ICommandHandler<PingCommand, Result>
{
public Task<Result> HandleAsync(
PingCommand command,
CancellationToken cancellationToken)
{
return Task.FromResult(Result.Success());
}
}
Query Contract
using PANiXiDA.Core.Application.Messaging.Mediator.Contracts;
using PANiXiDA.Core.Application.Messaging.Mediator.Handlers;
using PANiXiDA.Core.Application.Querying;
using PANiXiDA.Core.ResultPattern;
public sealed record NameReadModel(Guid Id, string Name) : IReadModel;
public sealed record GetNameQuery(Guid Id) : IQuery<Result<NameReadModel>>;
public sealed class GetNameQueryHandler
: IQueryHandler<GetNameQuery, Result<NameReadModel>>
{
public Task<Result<NameReadModel>> HandleAsync(
GetNameQuery query,
CancellationToken cancellationToken)
{
var readModel = new NameReadModel(query.Id, "PANiXiDA");
return Task.FromResult(Result.Success(readModel));
}
}
Query result payloads should implement IReadModel. Collections, pagination
models, and result wrappers may contain read models, but domain entities,
aggregate roots, value objects, enumerations, and strongly typed identifiers
must not cross the read-side boundary.
Concrete read models and custom filters should be declared as records. Because marker interfaces cannot enforce the declaration kind, consuming applications should protect this convention with architecture tests.
Page-Based Query Result
using PANiXiDA.Core.Application.Querying.Pagination;
var result = PaginationResult<string>.Create(
items: ["first", "second"],
pageNumber: 1,
pageSize: 10,
totalCount: 2);
var hasNextPage = result.HasNextPage;
Cursor-Based Query Result
using PANiXiDA.Core.Application.Querying.Cursor;
var result = CursorPaginationResult<string>.Create(
items: ["first", "second"],
limit: 10,
nextCursor: "cursor-2",
hasNextPage: true);
Limited Queries
LimitParameters carries the requested result count without pagination and defaults to 20. Its validator
accepts values from 1 through 200. Constructing the parameters preserves the supplied
value; validation reports invalid input without clamping it.
using FluentValidation;
using PANiXiDA.Core.Application.Querying.Limiting;
public sealed record GetOptionsQuery(LimitParameters Limit);
public sealed class GetOptionsQueryValidator : AbstractValidator<GetOptionsQuery>
{
public GetOptionsQueryValidator()
{
RuleFor(query => query.Limit)
.NotNull()
.SetValidator(new LimitParametersValidator());
}
}
For example, new GetOptionsQuery(new LimitParameters()) uses the default limit of 20
and passes validation. An explicit limit overrides the default.
Use NotNull() alongside SetValidator() to reject missing parameters.
Pagination Validation
PaginationParametersValidator requires a positive PageNumber, a PageSize from
1 through 200, and an offset that fits in Int32. Compose it into the query validator:
using FluentValidation;
using PANiXiDA.Core.Application.Querying.Pagination;
public sealed record GetPageQuery(PaginationParameters Pagination);
public sealed class GetPageQueryValidator : AbstractValidator<GetPageQuery>
{
public GetPageQueryValidator()
{
RuleFor(query => query.Pagination)
.NotNull()
.SetValidator(new PaginationParametersValidator());
}
}
Validation leaves the supplied parameters unchanged. Existing Skip and Take
calculations keep their behavior; validate the parameters before using them in a query.
Sorting
SortingParameters(SortField[] Fields) preserves criterion order. Directions are
SortDirection.Asc and Desc; empty sorting adds no implicit Id.
using PANiXiDA.Core.Application.Querying.Sorting;
var sorting = SortingParameters.Of(
new SortField("department.name", SortDirection.Desc),
new SortField("name"));
var effectiveSorting = sorting.WithDefault(SortingParameters.Descending("createdAt"));
SortField.TryParse("department.name:desc", out var field);
WithDefault appends missing fields, preserving explicit directions and precedence.
Paths are compared without regard to case. Use None for empty sorting; Ascending
and Descending create a single criterion. The supplied array is retained; validate
parameters before use or merging.
TryParse accepts field, field:asc, and field:desc without regard to case.
It parses individual values such as those in ?sort=name:asc&sort=department.name:desc.
HTTP binding and OpenAPI documentation belong to the transport adapter.
Sorting Validation
The included generator creates <ReadModelName>SortingValidator for concrete
source-declared IReadModel types, without attributes or runtime property reflection.
using FluentValidation;
using PANiXiDA.Core.Application.Querying;
using PANiXiDA.Core.Application.Querying.Sorting;
public sealed record DepartmentReadModel(string Name);
public sealed record UserReadModel(string Name, DepartmentReadModel? Department) : IReadModel;
public sealed record GetUsersQuery(SortingParameters Sorting);
public sealed class GetUsersQueryValidator : AbstractValidator<GetUsersQuery>
{
public GetUsersQueryValidator()
{
RuleFor(query => query.Sorting)
.NotNull()
.SetValidator(new UserReadModelSortingValidator());
}
}
The abstract SortingParametersValidator holds all rules: non-null criteria, valid
paths and directions, supported fields, and no duplicates. Empty sorting is valid;
there is no criterion count limit. Errors retain paths such as Sorting.Fields[0].Field.
- Paths use public scalar CLR properties, including inherited and nested properties.
nameandnameof(UserReadModel.Name)are equivalent; JSON renames are ignored. - Collections, indexers, and unreadable properties are excluded. At the first repeated type, scalar fields such as
manager.nameremain available; further nesting stops. Generic roots and ambiguous names produce compiler errors. - Register the query validator in the application's validation pipeline.
Applying sorting to
IQueryablebelongs to the persistence adapter.
Request Behaviors
The package includes reusable mediator behavior implementations for request validation, command transaction orchestration, and domain event publication:
ValidationBehavior<TRequest, TResult>validates requests with registered FluentValidationIValidator<TRequest>implementations and returns a failedResultbefore the handler runs when validation fails.BeginTransactionBehavior<TCommand, TResult>starts a transaction before a command handler runs.PublishDomainEventsBehavior<TRequest, TResult>publishes domain events collected from tracked aggregate roots after a successful request result and clears tracked events after a failed result or completed successful publication.CommitTransactionBehavior<TCommand, TResult>commits the active transaction after a successful command result.CleanupTransactionBehavior<TCommand, TResult>rolls back failed command transactions and disposes transaction resources.
A consuming mediator implementation should register these behaviors in a deterministic order. A typical command pipeline is:
before: ValidationBehavior
before: BeginTransactionBehavior
handler: ICommandHandler<TCommand, TResult>
after: PublishDomainEventsBehavior
after: CommitTransactionBehavior
finally: CleanupTransactionBehavior
The exact registration mechanism depends on the mediator or composition root used by the consuming application.
A consuming mediator should continue to the handler when a before behavior returns Result.Success().
When a before behavior returns a failed Result, the mediator should stop the pipeline and return a failed request TResult with the same errors.
Domain Value Validation
MustBeValidDomainValue adds a FluentValidation rule that calls a domain value factory returning Result<T>.
When the factory fails, each result error message is added as a validation failure for the current property.
using FluentValidation;
using PANiXiDA.Core.Application.Validation;
using PANiXiDA.Core.ResultPattern;
public sealed record CreateUserCommand(string Email);
public sealed class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(command => command.Email)
.MustBeValidDomainValue(Email.Create);
}
}
public sealed record Email(string Value)
{
public static Result<Email> Create(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return Result.Failure<Email>(Error.Validation("Email is required."));
}
return Result.Success(new Email(value));
}
}
MustBeValidDomainResult validates several request values with one domain factory and uses each error's
Error.FieldMetadataKey value as the FluentValidation property path. Errors without field metadata fall back
to the current rule path.
using FluentValidation;
using PANiXiDA.Core.Application.Validation;
using PANiXiDA.Core.ResultPattern;
public sealed record CreateDamageRangeCommand(
int MinimumDamage,
int MaximumDamage);
public sealed class CreateDamageRangeCommandValidator
: AbstractValidator<CreateDamageRangeCommand>
{
public CreateDamageRangeCommandValidator()
{
RuleFor(command => command)
.MustBeValidDomainResult(command => DamageRange.Create(
command.MinimumDamage,
command.MaximumDamage));
}
}
public sealed record DamageRange(
int MinimumDamage,
int MaximumDamage)
{
public static Result<DamageRange> Create(
int minimumDamage,
int maximumDamage)
{
if (maximumDamage < minimumDamage)
{
return Result.Failure<DamageRange>(
Error.Validation(
"Maximum damage cannot be less than minimum damage.")
.WithField(nameof(MaximumDamage)));
}
return Result.Success(
new DamageRange(minimumDamage, maximumDamage));
}
}
Repository Abstraction Ownership
Repository contracts are split by architectural responsibility:
| Contract | Package | Namespace | Responsibility |
|---|---|---|---|
IReadRepository<TId> |
PANiXiDA.Core.Application |
PANiXiDA.Core.Application.Persistence |
Read-side existence checks used by application queries and validation. |
IRepository<TId, TAggregateRoot> |
PANiXiDA.Core.Domain |
PANiXiDA.Core.Domain.Abstractions |
Loading and persisting aggregate roots through the domain boundary. |
IReadRepository<TId> provides ExistsByIdAsync and AnyAsync.
Its identifier should be a primitive read-side value such as Guid.
Additional read repository methods may accept primitive values or application
parameter models composed exclusively from primitive values, and should return
IReadModel payloads, optionally wrapped in collections or pagination models.
Read repository contracts must not use types from the Domain layer.
The aggregate repository contract is intentionally not defined by this package; reference PANiXiDA.Core.Domain when a repository works with aggregate roots.
API Overview
Messaging
IMediatordispatches commands and queries.ICommandHandler<TCommand, TResult>handles state-changing requests.IQueryHandler<TQuery, TResult>handles read-only requests.IReadModelidentifies query and read repository result payloads.IBeforeRequestBehavior<TRequest, TResult>runs before a handler and returnsResult.Success()to continue request processing, or a failedResultto stop it.IAfterRequestBehavior<TRequest, TResult>runs after a handler returns a result and is defined in the mediator behavior abstractions namespace.IFinallyRequestBehavior<TRequest, TResult>runs after request processing completes or fails and is defined in the mediator behavior abstractions namespace.
Validation
MustBeValidDomainValuevalidates a property through a domain factory that returnsResult<T>and maps failed result errors to FluentValidation failures.MustBeValidDomainResultvalidates a property or request through a domain factory and maps error field metadata to FluentValidation property paths.
Domain Events
IEventBuspublishes domain events.IEventHandler<TEvent>handles a specific domain event type.IAggregateTrackertracks aggregate roots touched during a request so their domain events can be published and cleared.
Persistence
IUnitOfWorkdefines persistence and transaction operations.IReadRepository<TId>defines read-onlyExistsByIdAsyncandAnyAsyncchecks.- Aggregate persistence uses
IRepository<TId, TAggregateRoot>fromPANiXiDA.Core.Domain.
Querying Models
IReadModelidentifies immutable read-side result models.PaginationParameterscalculatesSkipandTakefor page-based reads.PaginationResult<TItem>returns page metadata and items.CursorPaginationParametersrepresents cursor pagination input.CursorPaginationResult<TItem>returns cursor pagination metadata and items.SortingParametersis a positional record with aSortField[], aSortDirectionper field, and optional default merging.SortingParametersValidatoris the abstract base for validating criterion structure, supported fields, and duplicates.- Generated
<ReadModelName>SortingValidatorclasses validate supported CLR paths discovered fromIReadModelat compile time. IFilteridentifies application query filter records.
Configuration
The package does not require runtime configuration. Consumers provide concrete implementations for mediator dispatch, persistence, event bus delivery, aggregate tracking, and dependency injection registration.
Development
Restore
dotnet restore
Format
Build once before formatting a fresh checkout so the formatter can load the source generator and resolve generated validators.
dotnet build --no-restore
dotnet format
Build
dotnet build --configuration Release
Test
dotnet test --configuration Release
Pack
dotnet pack --configuration Release
Continuous integration
Every pull request and push to main runs formatting, tests, and mandatory
SonarQube analysis. Publishing from main starts only after the SonarQube
Quality Gate succeeds.
Project Structure
.
├── src/
│ ├── PANiXiDA.Core.Application/
│ └── PANiXiDA.Core.Application.Generators/
├── tests/
│ └── PANiXiDA.Core.Application.UnitTests/
├── Directory.Build.props
├── Directory.Build.targets
├── Directory.Packages.props
├── global.json
├── version.json
├── icon.png
├── LICENSE
└── README.md
License
This project is licensed under the Apache-2.0 license. See the LICENSE file for details.
| Product | Versions 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. |
-
net10.0
- FluentValidation (>= 12.1.1)
- PANiXiDA.Core.Domain (>= 2.0.2)
- PANiXiDA.Core.ResultPattern (>= 1.0.4)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on PANiXiDA.Core.Application:
| Package | Downloads |
|---|---|
|
PANiXiDA.Core.Infrastructure.Persistence.Ef
Core infrastructure abstractions and utilities for Entity Framework Core persistence adapters. |
|
|
PANiXiDA.Core.Presentation.Http
Reusable HTTP presentation layer abstractions, endpoint conventions, and ASP.NET Core integration utilities for PANiXiDA applications. |
|
|
PANiXiDA.Core.Infrastructure.Messaging.Wolverine
Wolverine-based messaging infrastructure adapter for PANiXiDA.Core applications, providing mediator, command, query and event publishing integration. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.1.1 | 82 | 9/27/2026 |
| 4.0.3 | 177 | 9/21/2026 |
| 4.0.2 | 186 | 9/17/2026 |
| 4.0.1 | 113 | 9/14/2026 |
| 3.1.2 | 146 | 9/11/2026 |
| 3.1.1 | 110 | 9/11/2026 |
| 3.0.1 | 278 | 8/29/2026 |
| 2.0.7 | 110 | 8/28/2026 |
| 2.0.6 | 265 | 8/1/2026 |
| 2.0.5 | 126 | 7/30/2026 |
| 2.0.4 | 196 | 7/28/2026 |
| 2.0.3 | 131 | 7/27/2026 |
| 2.0.2 | 230 | 6/16/2026 |
| 2.0.1 | 167 | 6/16/2026 |
| 1.0.3 | 246 | 6/12/2026 |
| 1.0.2 | 168 | 5/10/2026 |