FluentEnforce provides fluent API for handling unexpected errors with predefined validations. Features flexible custom validations enabling clean and readable precondition checks.
$ dotnet add package FluentEnforce
Lightweight, expressive parameter validation for .NET: 100+ built-in validations, fluent API, extensible architecture. Fail fast at method boundaries with zero boilerplate.
email.EnforceNotNull().NotWhiteSpace().MatchesEmail()ArgumentException (and its inherited types) or your custom exceptionsdotnet add package FluentEnforce
Targets: net9.0.
// Static approach
Enforce.NotNull(email)
.NotWhiteSpace()
.MatchesEmail();
Enforce.That(itemsList)
.HasMinCount(minItemsToShip)
.AllMatch(item => item.CanBeShipped);
Enforce.That(endDate).GreaterThan(startDate);
// Extension approach (equivalent)
email.EnforceNotNull()
.NotWhiteSpace()
.MatchesEmail();
itemsList.Enforce()
.HasMinCount(minItemsToShip)
.AllMatch(item => item.CanBeShipped);
endDate.Enforce().GreaterThan(startDate);
Note: For educational purposes, you will see me switch between using the regular static approach and extension approach. For readability, it's preferable to stick to one approach in a single code block. More about this in the documentation, in the
Best Practicessection.
public sealed class Email
{
public string Value { get; }
public Email(string value)
{
Value = Enforce.NotNull(value)
.NotWhiteSpace()
.MatchesEmail()
.Value; // Get validated value back
// [Note that `.Value` is optional. The Enforce type already has implicit conversion to the wrapped type]
}
}
email.EnforceNotNull(() => new DomainException("Email required"))
.MatchesEmail(() => new DomainException("Invalid email format"));
// Numbers
Enforce.That(age).InRange(18, 100, RangeBounds.LeftInclusive);
Enforce.That(price).Positive().LessThan(1000);
// Collections
Enforce.NotNull(items).NotEmpty().HasMinCount(1).HasMaxCount(10);
// Dates
Enforce.That(birthDate).InPast();
Enforce.That(appointment).InFuture();
// Extensible (For repeated validations)
Enforce.That(order1).Valid(); // And we define the extension Valid() method below
Enforce.That(order2).Valid();
static class OrderEnforceExtensions
{
public static Enforce<Order> Valid(this Enforce<Order> enforce)
{
var order = enforce.Value;
var paramName = enforce.ParamName;
order.Items.Enforce(paramName).NotEmpty();
order.Total.EnforceNotNull(paramName).Positive();
return enforce;
}
// Alternative approach.
// The above method is equavalent to:
public static Enforce<Order> Valid(this Enforce<Order> enforce)
{
var order = enforce.Value;
var paramName = enforce.ParamName;
if (!order.Items.Any())
throw new ArgumentException("Order must have items", paramName);
if(order.Total <= 0)
throw new ArgumentOutOfRangeException(paramName, order.Total, "Order total must be positive");
return enforce;
}
}
TimeProvider supportEach validation has both ArgumentException and custom exception variants (in addition to overloads for the collection types).
This README stays intentionally brief. Full documentation lives in the docs: