IronAlpine.Framework.Kernel 3.0.8

There is a newer version of this package available.
See the version list below for details.
dotnet add package IronAlpine.Framework.Kernel --version 3.0.8
                    
NuGet\Install-Package IronAlpine.Framework.Kernel -Version 3.0.8
                    
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="IronAlpine.Framework.Kernel" Version="3.0.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="IronAlpine.Framework.Kernel" Version="3.0.8" />
                    
Directory.Packages.props
<PackageReference Include="IronAlpine.Framework.Kernel" />
                    
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 IronAlpine.Framework.Kernel --version 3.0.8
                    
#r "nuget: IronAlpine.Framework.Kernel, 3.0.8"
                    
#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 IronAlpine.Framework.Kernel@3.0.8
                    
#: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=IronAlpine.Framework.Kernel&version=3.0.8
                    
Install as a Cake Addin
#tool nuget:?package=IronAlpine.Framework.Kernel&version=3.0.8
                    
Install as a Cake Tool

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.0.8, 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.0.8" />

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() returns Name.
  • Persist with the Data package's HasEnumerationConversion() (stores Id). In EF queries compare whole values (x.Status == OrderStatus.Paid); members such as Name are 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>.Value throws on failure; check IsFailure first or use Match / TryGetValue.
  • Entity has audit properties but only AggregateRoot implements IAuditableEntity. Child entities stay unstamped (default values) unless they implement the interface.
  • ProjectionEntity.OccurredOnUtc is 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.OccurredOnUtc means skip). The Data package's OccurredOnInterceptor only fills it with the clock time when it is left null (since 3.0.5; earlier versions overwrote it on every save).
  • Specification<T>.IsSatisfiedBy compiles the expression on every call; cache the compiled delegate in hot paths.
  • DomainEvent.OccurredOnUtc uses DateTime.UtcNow, not IClock; pass both values to the protected constructor if you need deterministic timestamps.
  • new Error(code, message) is ErrorType.Unexpected. Use the typed factories so API layers can map status codes.
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.
  • 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 252 10/21/2026
3.0.9 272 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 258 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
Loading failed

IronAlpine framework release. See the package README and repository documentation for details.