ScoBro.Foundation
0.2.2-alpha
dotnet add package ScoBro.Foundation --version 0.2.2-alpha
NuGet\Install-Package ScoBro.Foundation -Version 0.2.2-alpha
<PackageReference Include="ScoBro.Foundation" Version="0.2.2-alpha" />
<PackageVersion Include="ScoBro.Foundation" Version="0.2.2-alpha" />
<PackageReference Include="ScoBro.Foundation" />
paket add ScoBro.Foundation --version 0.2.2-alpha
#r "nuget: ScoBro.Foundation, 0.2.2-alpha"
#:package ScoBro.Foundation@0.2.2-alpha
#addin nuget:?package=ScoBro.Foundation&version=0.2.2-alpha&prerelease
#tool nuget:?package=ScoBro.Foundation&version=0.2.2-alpha&prerelease
ScoBro.Foundation
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.5Microsoft.Extensions.Logging.Abstractions≥ 10.0.5
License
MIT © 2025 Scott Brown
| 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
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
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 |