ScoBro.Foundation 0.2.2-alpha

This is a prerelease version of ScoBro.Foundation.
dotnet add package ScoBro.Foundation --version 0.2.2-alpha
                    
NuGet\Install-Package ScoBro.Foundation -Version 0.2.2-alpha
                    
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="ScoBro.Foundation" Version="0.2.2-alpha" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ScoBro.Foundation" Version="0.2.2-alpha" />
                    
Directory.Packages.props
<PackageReference Include="ScoBro.Foundation" />
                    
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 ScoBro.Foundation --version 0.2.2-alpha
                    
#r "nuget: ScoBro.Foundation, 0.2.2-alpha"
                    
#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 ScoBro.Foundation@0.2.2-alpha
                    
#: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=ScoBro.Foundation&version=0.2.2-alpha&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ScoBro.Foundation&version=0.2.2-alpha&prerelease
                    
Install as a Cake Tool

ScoBro.Foundation

NuGet NuGet Downloads License: MIT Target Framework

A lightweight .NET foundation library that provides four focused building blocks — fluent validation, smart enums, a structured result pattern, and a lightweight mediator — designed to reduce boilerplate in modern .NET applications.


Installation

dotnet add package ScoBro.Foundation

What's Included

Module Namespace Key Types
Validation ScoBro.Foundation.Validation Validator<T>, extension methods
Smart Enums ScoBro.Foundation EnumBase<T>, EnumTypeWithDescription<T>
Results ScoBro.Foundation SimpleResult, SimpleResult<T>, FailureType
Mediator ScoBro.Foundation.Mediators IMediator, IRequestHandler<T,R>

Validation

A fluent, expression-based validation framework with sync and async support, nullable-aware rules, and structured constraint metadata.

Defining a validator

using ScoBro.Foundation.Validation;

public class CreateOrderValidator : Validator<CreateOrderRequest>
{
    public CreateOrderValidator()
    {
        RuleFor(x => x.CustomerId)
            .IsNotEmpty();                           // Guid must not be Guid.Empty

        RuleFor(x => x.ProductCode)
            .IsNotNullOrEmpty()                      // required: null/empty fails
            .HasMaxLength(20);

        RuleFor(x => x.Notes)
            .IsNotEmpty()                            // optional: null passes, "" fails
            .HasMaxLength(500);

        RuleFor(x => x.Quantity)
            .IsPositive();

        RuleFor(x => x.ShipDate)
            .IsNotInPast();
    }
}

Running validation

// Synchronous
var result = validator.Validate(request);
if (!result.IsValid)
{
    foreach (var error in result.Errors)
        Console.WriteLine($"{error.FieldName}: {error.ErrorMessage}");
}

// Async (when rules include async predicates)
var result = await validator.ValidateAsync(request);

// Integrated with the result pattern
SimpleResult<CreateOrderRequest> result = validator.ValidateToSimpleResult(request);

Inline validation (no class needed)

var result = Validator.Validate(request, v =>
{
    v.Rule(x => x.Email).IsNotNullOrEmpty().IsEmail();
    v.Rule(x => x.Age).IsInRange(18, 120);
});

Nullable vs required string fields

RuleFor(x => x.RequiredName)
    .IsNotNullOrEmpty()    // null → fail, "" → fail (required guard)
    .HasMaxLength(100);

RuleFor(x => x.OptionalBio)
    .IsNotEmpty()          // null → pass, "" → fail (optional guard)
    .HasMaxLength(500);    // null → pass, value validated if present

RuleFor(x => x.OptionalTag)
    .HasMaxLength(50);     // null → pass, applied only when a value is given

Custom predicates

RuleFor(x => x.Username)
    .IsNotNullOrWhiteSpace()
    .Must(name => !name.Contains(' '), "Username cannot contain spaces.");

RuleFor(x => x.ExternalId)
    .MustAsync(async id => await repository.ExistsAsync(id), "ExternalId not found.");

Dependency injection

// Register all Validator<T> subclasses in the assembly
services.AddValidatorsFromAssemblyContaining<CreateOrderValidator>();

// Then inject normally
public class OrderService(IValidator<CreateOrderRequest> validator) { ... }

Available string rules

Method Null behaviour Notes
IsNotNullOrEmpty() null → fail Required field guard
IsNotNullOrWhiteSpace() null → fail Required field guard
IsNotEmpty() null → pass Optional field guard
IsNotWhiteSpace() null → pass Optional field guard
HasMaxLength(n) null → pass Emits StringMaxLengthConstraint
HasMinLength(n) null → pass Emits StringMinLengthConstraint
IsEmail() null → pass
IsGuid() null → pass
MatchesRegex(pattern) null → pass
Contains(sub) null → pass
DoesNotContain(sub) null → pass
StartsWith(prefix) null → pass
EndsWith(suffix) null → pass

Numeric types (int, long, short, decimal, double) and DateTime follow the same pattern with both nullable and non-nullable overloads. Key numeric rules: IsPositive, IsNotNegative, IsInRange, IsGreaterThan, IsLessThan, IsOneOf, IsEven, IsOdd.

Domain enum validation

// Required — null and unrecognised values fail
RuleFor(x => x.Status).IsNotNullOrEmpty().DomainEnum<MyRequest, OrderStatus>();

// Optional — null passes, unrecognised values fail
RuleFor(x => x.Priority).OptionalDomainEnum<MyRequest, Priority>();

Nested validators

RuleFor(x => x.Address).UseValidator(addressValidator);
RuleFor(x => x.OptionalContact).UseValidator(contactValidator);      // null → pass
RuleFor(x => x.Count).UseValidator(countValidator);                  // nullable int?

Constraint metadata

Validators expose structural metadata for consumers such as API schema generators or form builders:

var constraints = validator.GetValidationConstraints();
// Returns StringMaxLengthConstraint, StringMinLengthConstraint instances, etc.

Smart Enums

Type-safe string-keyed enumerations that support parse, try-parse, and exhaustive enumeration — without the fragility of raw string constants.

Defining an enum

using ScoBro.Foundation;

public record OrderStatus : EnumBase<OrderStatus>
{
    private OrderStatus(string id) : base(id) { }

    public static readonly OrderStatus Pending   = new("Pending");
    public static readonly OrderStatus Confirmed = new("Confirmed");
    public static readonly OrderStatus Shipped   = new("Shipped");
    public static readonly OrderStatus Cancelled = new("Cancelled");
}

With a human-readable description

public record Priority : EnumTypeWithDescription<Priority>
{
    private Priority(string id, string description) : base(id, description) { }

    public static readonly Priority Low    = new("Low",    "Low Priority");
    public static readonly Priority Medium = new("Medium", "Standard Priority");
    public static readonly Priority High   = new("High",   "Urgent");
}

Usage

// Parse from string (e.g. from JSON, database)
var status = OrderStatus.Parse("Confirmed");         // throws if not found
bool ok    = OrderStatus.TryParse("shipped", out var s); // case-insensitive
bool valid = OrderStatus.IsDefined("Pending");

// Enumerate all members
foreach (var member in OrderStatus.GetAll())
    Console.WriteLine(member.Id);

// Implicit string conversion — works with EF Core string columns etc.
string id = OrderStatus.Shipped;    // "Shipped"

// Description lookups
var p = Priority.ParseByDescription("Urgent");       // → Priority.High

Result Pattern

SimpleResult and SimpleResult<T> provide a uniform return type for service and handler methods — no exceptions for expected failures, no stringly-typed error bags.

Creating results

using ScoBro.Foundation;

// Success
SimpleResult<Order> result = SimpleResult.Ok(order);

// Failures
SimpleResult.Fail<Order>("Something went wrong.");
SimpleResult.FailWithValidationErrors<Order>(new[] { "Name is required." });
SimpleResult.FailWithUserError<Order>("That email is already registered.");
SimpleResult.FailWithSystemError<Order>("Database unavailable.");
SimpleResult.FailNotFound<Order>("Order not found.");
SimpleResult.FailWithForbidden<Order>();
SimpleResult.FailWithUnauthorized<Order>();

// Non-generic (command results)
SimpleResult.Ok();
SimpleResult.Fail("Could not delete record.");

Consuming results

if (result.WasSuccessful)
{
    Process(result.Value);
}
else
{
    // result.FailureType is a FailureType smart enum
    if (result.FailureType == FailureType.NotFound)
        return NotFound();

    Log(result.GetDelimitedErrors());
}

FailureType values

Value Meaning
UserError Invalid or inappropriate caller input
ValidationError Field-level validation rule violations
SystemError Unexpected internal errors
NotFound Requested resource does not exist
Forbidden Caller is authenticated but lacks permission
Unauthorized Caller is not authenticated

Integration with validation

// Validator<T>.ValidateToSimpleResult returns SimpleResult<T> directly
SimpleResult<CreateOrderRequest> result = validator.ValidateToSimpleResult(request);
SimpleResult<CreateOrderRequest> result = await validator.ValidateToSimpleResultAsync(request);

Mediator

A minimal, DI-native mediator for dispatching commands and queries to their handlers. Handlers are resolved from IServiceProvider, keeping coupling low and enabling straightforward unit testing.

Defining requests and handlers

using ScoBro.Foundation.Mediators;

// Query (read) — returns a value
public record GetOrderQuery(Guid OrderId) : IQueryRequest<Order>;

public class GetOrderHandler : IRequestHandler<GetOrderQuery, Order>
{
    public async Task<SimpleResult<Order>> HandleAsync(
        GetOrderQuery request, CancellationToken cancellationToken)
    {
        var order = await _repository.FindAsync(request.OrderId);
        return order is null
            ? SimpleResult.FailNotFound<Order>($"Order {request.OrderId} not found.")
            : SimpleResult.Ok(order);
    }
}

// Command (write) — no return value
public record CancelOrderCommand(Guid OrderId) : ICommandRequest;

public class CancelOrderHandler : IRequestHandler<CancelOrderCommand>
{
    public async Task<SimpleResult> HandleAsync(
        CancelOrderCommand request, CancellationToken cancellationToken)
    {
        await _repository.CancelAsync(request.OrderId);
        return SimpleResult.Ok();
    }
}

Registration

services.AddScoped<IMediator, Mediator>();
services.AddScoped<IRequestHandler<GetOrderQuery, Order>, GetOrderHandler>();
services.AddScoped<IRequestHandler<CancelOrderCommand>, CancelOrderHandler>();

Dispatching

// Inject IMediator and send
var result = await mediator.Send<GetOrderQuery, Order>(
    new GetOrderQuery(orderId), cancellationToken);

var result = await mediator.Send<CancelOrderCommand>(
    new CancelOrderCommand(orderId), cancellationToken);

Unhandled exceptions inside handlers are caught and returned as FailWithSystemError — the error and stack trace are logged via ILogger<Mediator>.


Requirements

  • .NET 10.0 or later
  • Microsoft.Extensions.DependencyInjection.Abstractions ≥ 10.0.5
  • Microsoft.Extensions.Logging.Abstractions ≥ 10.0.5

License

MIT © 2025 Scott Brown

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.

NuGet packages (5)

Showing the top 5 NuGet packages that depend on ScoBro.Foundation:

Package Downloads
ScoBro.Guards

A set of extensions to guard/validate values as they are assigned

ScoBro.Blazor

Package Description

ScoBro.Web

Package Description

ScoBro.DDD

Package Description

ScoBro.SemanticKernel

ScoBro Semantic Kernel

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.2-alpha 63 5/19/2026
0.2.1-alpha 64 4/24/2026
0.2.0-alpha 58 4/21/2026
0.1.9-alpha 78 1/21/2026
0.1.8-alpha 165 12/22/2025
0.1.7-alpha 277 7/9/2025
0.1.5-alpha 163 6/20/2025
0.1.4-alpha 170 6/20/2025
0.1.3-alpha 182 6/19/2025
0.1.2-alpha 1,791 9/27/2024
0.1.1-alpha 127 9/27/2024
0.1.0-alpha 130 9/26/2024
0.0.9-alpha 118 9/26/2024
0.0.8-alpha 123 9/26/2024
0.0.7-alpha 117 9/26/2024
0.0.6-alpha 122 9/26/2024
0.0.5-alpha 401 9/25/2024
0.0.4-alpha 114 9/25/2024
0.0.3-alpha 114 9/25/2024
0.0.2-alpha 137 9/22/2024
Loading failed