IronAlpine.Framework.Kernel
3.1.1
dotnet add package IronAlpine.Framework.Kernel --version 3.1.1
NuGet\Install-Package IronAlpine.Framework.Kernel -Version 3.1.1
<PackageReference Include="IronAlpine.Framework.Kernel" Version="3.1.1" />
<PackageVersion Include="IronAlpine.Framework.Kernel" Version="3.1.1" />
<PackageReference Include="IronAlpine.Framework.Kernel" />
paket add IronAlpine.Framework.Kernel --version 3.1.1
#r "nuget: IronAlpine.Framework.Kernel, 3.1.1"
#:package IronAlpine.Framework.Kernel@3.1.1
#addin nuget:?package=IronAlpine.Framework.Kernel&version=3.1.1
#tool nuget:?package=IronAlpine.Framework.Kernel&version=3.1.1
IronAlpine.Framework.Kernel
Domain and application primitives for the IronAlpine stack: entities, aggregates, projections, domain events,
Result / Error, smart enumerations, value objects, strongly typed ids, guards, specifications, paging and time.
- Package id:
IronAlpine.Framework.Kernel(3.1.1,net10.0) - Root namespace:
IronAlpine.Kernel - Dependencies: none beyond the BCL. Safe to reference from every layer, including the domain layer.
The Kernel only defines types. Behaviour attached to them (audit stamping, soft delete, versioning, domain event dispatch, exception-to-HTTP mapping) is implemented by other packages; the tables below say which one.
Installation
<PackageReference Include="IronAlpine.Framework.Kernel" Version="3.1.1" />
There is nothing to register. IClock is registered (as SystemClock) by IronAlpine.Framework.Data; register it
yourself if you use the Kernel without the Data package:
services.AddSingleton<IClock, SystemClock>();
Namespaces
| Namespace | Contents |
|---|---|
IronAlpine.Kernel.Primitives |
Entity, AggregateRoot, ProjectionEntity, DomainEvent, Enumeration<T>, ValueObject, marker interfaces, INotification, IDomainEvent, IAuditUser |
IronAlpine.Kernel.Results |
Result, Result<T>, Error, ErrorType, ValidationError, TechnicalErrors, ResultExtensions, ITransactionalResult |
IronAlpine.Kernel.Exceptions |
BaseException and its subclasses |
IronAlpine.Kernel.Guarding |
Guard |
IronAlpine.Kernel.Identifiers |
StronglyTypedId<TValue> |
IronAlpine.Kernel.Specifications |
ISpecification<T>, Specification<T>, SpecificationEvaluator |
IronAlpine.Kernel.Paging |
PagedRequest, PagedResult<T> |
IronAlpine.Kernel.Time |
IClock, SystemClock, SaveInstantScope |
Result and Error
Handlers return Result / Result<T> instead of throwing for expected failures.
using IronAlpine.Kernel.Results;
public static class OrderErrors
{
public static readonly Error NotFound = Error.NotFound("Order.NotFound", "Order was not found.");
public static readonly Error AlreadyShipped = Error.Conflict("Order.AlreadyShipped", "Order is already shipped.");
}
public Result<decimal> CalculateDiscount(Order order)
{
if (order.IsShipped)
{
return Result.Failure<decimal>(OrderErrors.AlreadyShipped);
}
return Result.Success(order.Total * 0.1m);
}
Result / Result<T>
| Member | Behaviour |
|---|---|
Result.Success(), Result.Success<T>(value) |
successful result (Error.None) |
Result.Failure(error), Result.Failure<T>(error) |
failed result; error must not be null or Error.None |
IsSuccess, IsFailure, Error |
state |
Result<T>.Value |
value; throws InvalidOperationException on a failed result |
GetValueOrDefault(defaultValue), TryGetValue(out value) |
safe access |
implicit T -> Result<T> |
non-null value → success, null → failure with Error.NullValue() |
explicit (T)result |
same as .Value |
Result.Combine(params Result[]) |
first failure, otherwise success |
Constructing a success with an error (or a failure with Error.None) throws.
Result implements ITransactionalResult (IsSuccess). The Data package's result-aware transaction behaviour uses it
to roll back a transaction when a handler returns a failure.
Functional helpers (ResultExtensions)
Result<OrderDto> dto = GetOrder(id)
.Map(order => new OrderDto(order.Id, order.Total)) // transform value
.Tap(d => logger.LogDebug("Loaded {Id}", d.Id)); // side effect on success
Result<Invoice> invoice = GetOrder(id).Bind(order => order.CreateInvoice()); // chain Result-returning calls
string text = GetOrder(id).Match(
onSuccess: order => $"Order {order.Id}",
onFailure: error => error.Message);
Error
Error is a record: Error(string Code, string Message, ErrorType Type = ErrorType.Unexpected, IReadOnlyList<ValidationError>? ValidationErrors = null, IReadOnlyDictionary<string, string?>? Metadata = null).
| Factory | ErrorType |
|---|---|
Error.Validation(code, message, validationErrors, metadata?) |
Validation |
Error.ValidationError(validationErrors, code = "Validation.Failed", message = ..., metadata?) |
Validation |
Error.NotFound(code, message, metadata?) |
NotFound |
Error.Conflict(code, message, metadata?) |
Conflict |
Error.Unauthorized(code, message, metadata?) |
Unauthorized |
Error.Forbidden(code, message, metadata?) |
Forbidden |
Error.Unexpected(code, message, metadata?) |
Unexpected |
Error.NullValue(message?) |
Unexpected, code Error.NullValue |
new Error(code, message) |
defaults to Unexpected - pass the type explicitly |
ValidationError(string PropertyName, string Message, string? Code = null) carries field level errors.
TechnicalErrors contains ready-made errors: General.NotFound/NullValue/Unexpected, Auth.Unauthorized/Forbidden/InvalidToken,
Network.Timeout/Unavailable, Concurrency.Conflict.
ErrorType is not mapped to HTTP status codes by any IronAlpine package; map it in your API layer, for example:
protected IActionResult ToActionResult(Result result) => result.IsSuccess
? Ok()
: result.Error.Type switch
{
ErrorType.Validation => BadRequest(result.Error),
ErrorType.NotFound => NotFound(result.Error),
ErrorType.Conflict => Conflict(result.Error),
ErrorType.Unauthorized => Unauthorized(result.Error),
ErrorType.Forbidden => StatusCode(StatusCodes.Status403Forbidden, result.Error),
_ => StatusCode(StatusCodes.Status500InternalServerError, result.Error)
};
Entities, aggregates and projections
using IronAlpine.Kernel.Primitives;
public sealed class Order : AggregateRoot, ISoftDeletable
{
private readonly List<OrderLine> _lines = [];
private Order() { } // for EF Core
private Order(Guid id, Guid customerId) : base(id)
{
CustomerId = customerId;
}
public Guid CustomerId { get; private set; }
public bool IsDeleted { get; private set; }
public IReadOnlyCollection<OrderLine> Lines => _lines;
public static Order Create(Guid customerId)
{
var order = new Order(Guid.NewGuid(), customerId);
order.AddDomainEvent(new OrderCreatedDomainEvent(order.Id, customerId));
return order;
}
}
public sealed record OrderCreatedDomainEvent(Guid OrderId, Guid CustomerId) : DomainEvent;
| Type | Notes |
|---|---|
Entity<TId> / Entity (Guid) |
Id (protected setter), domain event list (AddDomainEvent, GetDomainEvents() returns a copy, ClearDomainEvents()), and CreatedBy, CreatedOnUtc, ModifiedBy, ModifiedOnUtc properties. Entity does not implement IAuditableEntity; implement it on child entities that must be stamped. |
AggregateRoot<TId> / AggregateRoot |
Entity + IAggregateRoot + IAuditableEntity (stamped automatically by the Data package). |
ProjectionEntity<TId> / ProjectionEntity |
read-model row; implements IProjectionEntity and IOccurredOnUtc (OccurredOnUtc { get; set; }): the occurrence time of the last applied event. Handlers must set it. See the pitfall below. |
DomainEvent |
abstract record; EventId = Guid.NewGuid(), OccurredOnUtc = DateTime.UtcNow (not IClock); a second constructor takes both values. |
IDomainEvent |
INotification; dispatched in-process by the mediator. |
INotification |
marker for in-process notifications (lives here, not in the Mediator package). |
Marker interfaces and who acts on them
| Interface | Effect | Implemented by |
|---|---|---|
IAuditableEntity / IAuditableEntity<TUserId> |
created/modified user and timestamp stamping, audit log rows | Data (AuditableEntityInterceptor) |
ISoftDeletable |
delete becomes IsDeleted = true; global named query filter |
Data |
IDeletionAuditableEntity / <TUserId> |
DeletedBy, DeletedOnUtc stamped on soft delete, cleared on restore |
Data |
IActivatable |
IsActive = false on soft delete, true on restore |
Data |
IConcurrencyTracked |
RowVersion configured as row version |
Data |
IVersioned |
Version = 1 on insert, +1 on update |
Data |
IOccurredOnUtc |
OccurredOnUtc filled with the save instant (IClock) on insert/update only when it is null/default |
Data |
IHasDomainEvents / IAggregateRoot |
pending domain events dispatched during SaveChangesAsync |
Data |
IEntity<TId>, IProjectionEntity<TId> |
repository constraints | Data |
IAuditUser |
UserId, IsAuthenticated of the current actor |
implemented by Security |
Smart enumerations (Enumeration<TEnum>)
public sealed class OrderStatus : Enumeration<OrderStatus>
{
public static readonly OrderStatus Draft = new(1, "Draft");
public static readonly OrderStatus Paid = new(2, "Paid");
public static readonly OrderStatus Shipped = new(3, "Shipped");
private OrderStatus(int id, string name) : base(id, name) { }
}
var paid = OrderStatus.FromValue(2); // throws KeyNotFoundException if unknown
var maybe = OrderStatus.FromNameOrDefault("paid"); // case-insensitive, null if unknown
bool ok = OrderStatus.TryFromValue(9, out var none);
IReadOnlyCollection<OrderStatus> all = OrderStatus.GetValues();
- Values are discovered once (lazy, thread safe) from public static fields of type
TEnum; duplicate ids throw. - Equality is by type and
Id;ToString()returnsName. - Persist with the Data package's
HasEnumerationConversion()(storesId). In EF queries compare whole values (x.Status == OrderStatus.Paid); members such asNameare not translatable.
Value objects
public sealed class Money : ValueObject
{
public Money(decimal amount, string currency)
{
Amount = Guard.AgainstNegative(amount);
Currency = Guard.NotNullOrWhiteSpace(currency);
}
public decimal Amount { get; }
public string Currency { get; }
protected override IEnumerable<object?> GetEqualityComponents()
{
yield return Amount;
yield return Currency;
}
}
== / != and GetHashCode use the equality components; values of different types are never equal.
Strongly typed ids
using IronAlpine.Kernel.Identifiers;
public sealed record OrderId(Guid Value) : StronglyTypedId<Guid>(Value);
var id = new OrderId(Guid.NewGuid()); // a default value (Guid.Empty, 0, ...) throws ArgumentException
The Data repositories are keyed by TId : notnull; configure an EF value converter for strongly typed ids yourself.
Guard
using IronAlpine.Kernel.Guarding;
Name = Guard.NotNullOrWhiteSpace(name, nameof(name));
CustomerId = Guard.NotEmpty(customerId, nameof(customerId));
Quantity = Guard.AgainstNegativeOrZero(quantity, nameof(quantity));
Discount = Guard.AgainstOutOfRange(discount, 0m, 100m, nameof(discount));
Available: NotNull, NotNullOrWhiteSpace, NotEmpty(Guid), NotNullOrEmpty(IReadOnlyCollection<T>),
AgainstDefault, AgainstNegative, AgainstNegativeOrZero, AgainstOutOfRange. Guards throw ArgumentException
family exceptions; use them for programming errors, and Result for business rule violations.
Specifications
using IronAlpine.Kernel.Specifications;
public sealed class OpenOrdersSpecification : Specification<Order>
{
public override Expression<Func<Order, bool>> ToExpression() => o => !o.IsDeleted && o.ShippedOnUtc == null;
}
var spec = new OpenOrdersSpecification()
.And(Specification<Order>.Create(o => o.CustomerId == customerId));
IQueryable<Order> query = SpecificationEvaluator.Apply(dbContext.Orders, spec); // query.Where(spec.ToExpression())
bool matches = spec.IsSatisfiedBy(order);
And, Or and Not rewrite the expression parameters, so combined specifications stay translatable by EF Core.
IReadRepository<T>.GetListAsync(ISpecification<T>) (Data) accepts specifications directly.
Paging
public sealed record GetOrdersQuery : PagedRequest; // Page, Size, SortBy, SortDirection
var request = new GetOrdersQuery { Page = 2, Size = 500, SortBy = "createdOnUtc", SortDirection = "DESC" };
// request.Page == 2, request.Size == 100 (clamped), request.SortDirection == "desc", request.Descending == true
var page = PagedResult<OrderDto>.Create(items, page: 2, size: 20, totalCount: 135);
// page.TotalPages == 7, page.HasNextPage == true, page.HasPreviousPage == true
PagedRequest |
Normalisation |
|---|---|
Page |
< 1 → 1 |
Size (alias PageSize) |
< 1 → 10, > 100 → 100 |
SortDirection |
anything other than desc (case-insensitive) → asc; Descending mirrors it |
SortBy |
free text; resolved and validated by the Data package's ToPagedResultAsync |
PagedResult<T> exposes Items, Page, Size (alias PageSize), TotalCount, TotalPages, HasNextPage,
HasPreviousPage. A negative totalCount throws.
Time
public sealed class InvoiceDueDateCalculator(IClock clock)
{
public DateTime DueDate() => clock.UtcNow.Date.AddDays(30);
}
Use IClock instead of DateTime.UtcNow in code you want to test with a fixed clock. The Data interceptors use
IClock for every timestamp they write: they read it once per save and write that single save instant everywhere.
SaveInstantScope is the ambient (AsyncLocal) form of that instant. The Data package opens it around domain event
dispatch; SaveInstantScope.Current is null elsewhere. IntegrationEventBase (EventBus package) uses it as the
default OccurredOnUtc. SaveInstantScope.Begin(instant) returns an IDisposable; nested scopes restore the previous
value when disposed.
Exceptions
| Type | Default code | HTTP status in IronAlpine.Web |
|---|---|---|
BaseException (abstract; Code property) |
- | - |
DomainException |
- | 400 |
ApplicationException (IronAlpine.Kernel.Exceptions) |
- | 400 |
InfrastructureException |
- | 503 |
TransientException : InfrastructureException |
Infrastructure.Transient |
503 |
NonRetryableException : InfrastructureException |
Infrastructure.NonRetryable |
503 |
PersistenceException |
e.g. Persistence.ConcurrencyConflict (Data) |
503 |
ApplicationException has the same simple name as System.ApplicationException; qualify it when both namespaces are
imported.
Pitfalls
Result<T>.Valuethrows on failure; checkIsFailurefirst or useMatch/TryGetValue.Entityhas audit properties but onlyAggregateRootimplementsIAuditableEntity. Child entities stay unstamped (default values) unless they implement the interface.ProjectionEntity.OccurredOnUtcis the occurrence time of the last applied event; handlers must set it from the incoming event and can use it as a stale-event guard (existing.OccurredOnUtc >= e.OccurredOnUtcmeans skip). The Data package'sOccurredOnInterceptoronly fills it with the clock time when it is leftnull(since 3.0.5; earlier versions overwrote it on every save).Specification<T>.IsSatisfiedBycompiles the expression on every call; cache the compiled delegate in hot paths.DomainEvent.OccurredOnUtcusesDateTime.UtcNow, notIClock; pass both values to the protected constructor if you need deterministic timestamps.new Error(code, message)isErrorType.Unexpected. Use the typed factories so API layers can map status codes.
| 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
- No dependencies.
NuGet packages (7)
Showing the top 5 NuGet packages that depend on IronAlpine.Framework.Kernel:
| Package | Downloads |
|---|---|
|
IronAlpine.Framework.Mediator
High-performance mediator runtime with request/response, notifications, streaming, and configurable behavior pipeline. Includes validation (FluentValidation) support. Caching behavior lives in IronAlpine.Framework.Caching. |
|
|
IronAlpine.Framework.Data
EFCore data stack for IronAlpine: repositories, interceptors (audit, soft-delete, versioning), transaction and auditing behaviors, and domain event dispatching. Unified entry point: AddIronAlpineData<TDbContext>. Repository base classes remain public extension points for custom repositories. |
|
|
IronAlpine.Framework.Web
ASP.NET Core defaults, Swagger, health checks, correlation middleware, and YARP gateway transform for IronAlpine microservices. Single entry point: AddIronAlpineWebDefaults + UseIronAlpineWebDefaults. All implementations internal. |
|
|
IronAlpine.Framework.EventBus
Kafka-backed event bus with outbox/inbox pattern, dead letter support, and replay functionality. Unified namespace: IronAlpine.EventBus. Kafka implementation is internal; only contracts and topology builder are public. SSL cert parameters fully configurable (SslCaLocation, SslCertLocation, SslKeyLocation, SslKeyPassword). |
|
|
IronAlpine.Framework.Security
JWT authentication, claim-based authorization, and EFCore permission store for IronAlpine microservices. Provides ICurrentUser, permission policy provider, security builder, and public authorization store contracts. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.1.1 | 246 | 10/21/2026 |
| 3.0.9 | 271 | 10/15/2026 |
| 3.0.8 | 174 | 10/13/2026 |
| 3.0.7 | 152 | 10/13/2026 |
| 3.0.6 | 164 | 10/9/2026 |
| 3.0.5 | 241 | 10/5/2026 |
| 3.0.4 | 257 | 10/2/2026 |
| 3.0.3 | 235 | 9/30/2026 |
| 3.0.2 | 1,139 | 7/22/2026 |
| 3.0.1 | 201 | 7/22/2026 |
| 3.0.0 | 417 | 7/20/2026 |
| 2.0.3 | 219 | 7/20/2026 |
| 2.0.2 | 253 | 7/13/2026 |
| 2.0.1 | 258 | 7/13/2026 |
| 2.0.0 | 236 | 7/13/2026 |
| 2.0.0-preview.2 | 103 | 7/13/2026 |
| 2.0.0-preview.1 | 108 | 7/6/2026 |
| 1.0.2 | 212 | 7/3/2026 |
| 1.0.1 | 199 | 7/3/2026 |
IronAlpine framework release. See the package README and repository documentation for details.