FluentSpecification is .NET implementation of Specification design pattern with many small, built-in reusable specifications, perfect for validation of Domain Model in Domain-Driven-Design approach and other similar, where system is built around domain objects.
Other important features:
Fluent API
- allows to build and combine specifications in simple and clear way.Built-in specifications
- with validation and Linq expression support. Many of them available as negationObjects validation
- special result with error messages, trace of all used specifications, specifications parameters etc. Can be used not only for domain, but also for DTOs and user input, when you need to log result or present it on UI.Linq expression
- specifications can be used in collections querying. Business logic of repositories can be separated to specifications and use interchangeably.EF Core support
- all specifications are tested with EF Core on real database and ca be used without errors.EF 6 partial support
- many of specifications support LinqToEntities (LinqToSql) and can be used in Entity Framework 6 queries.
using FluentSpecification;
using FluentSpecification.Core;
var customerSpec = Specification
// Specify not null Customer
.NotNull<Customer>()
// with Id between 100 and 200
.And()
.InclusiveBetween(c => c.CustomerId, 100, 200)
// and LastName is "Smith"
.And()
.Equal(c => c.LastName, "Smith")
// and Customer is active
.And()
.True(c => c.IsActive)
// and Customer property "Email" is ...
.And(Specification.ForProperty<Customer, string>(c => c.Email, Specification
// ... valid email
.Email()
// longer than 15 characters
.And()
.MinLength(15)
// on "gmail.com"
.And()
.Match("^.*@gmail.com$")))
// with not empty Item collection ...
.And()
.ForProperty(c => c.Items, Specification
.NotEmpty<ICollection<Item>>()
// ... contains Item '1000'
.And()
.Contains(new Item { ItemId = 1000 }))
// and Customer has credit card ...
.And()
.NotNull(c => c.CreditCard)
// ... with valid number
.And()
.CreditCard(c => c.CreditCard.CardNumber)
// and credit card is valid between 2019-03-12 ...
.And(Specification
.GreaterThanOrEqual<Customer, DateTime>(c => c.CreditCard.ValidityDate,
new DateTime(2019, 3, 12))
// ... and 2019-05-31
.Or()
.LessThan(c => c.CreditCard.ValidityDate, new DateTime(2019, 6, 1)));
customerSpec.IsSatisfiedBy(new Customer
{
CustomerId = 125,
LastName = "Smith",
IsActive = true,
Email = "asmith@gmail.com",
Items = new List<Item>()
{
new Item { ItemId= 1000 }
},
CreditCard = new CreditCard
{
CardNumber = "5500 0000 0000 0004",
ValidityDate = DateTime.Parse("2019-03-12")
}
}); // return true
customerSpec.IsSatisfiedBy(new Customer
{
CustomerId = 90,
LastName = "Jones",
IsActive = false,
Email = "mjones@hotmail.com",
Items = null,
CreditCard = new CreditCard
{
CardNumber = "5500 0000 1",
ValidityDate = DateTime.Parse("2019-03-01")
}
}, out var specResult); // return false
Console.WriteLine(specResult.ToString());
// Field 'CustomerId' value is not valid
// Field 'CustomerId': [Value is not between [100] and [200]]
// Field 'LastName' value is not valid
// Field 'LastName': [Object is not equal to [Smith]]
// Field 'IsActive' value is not valid
// Field 'IsActive': [Value is False]
// Field 'Email' value is not valid
// Field 'Email': [String not match pattern [^.*@gmail.com$]]
// Field 'Items' value is not valid
// Field 'Items': [Object is empty]
// Field 'Items': [Collection not contains [FluentSpecification.Integration.Tests.Data.Item]]
// Field 'CreditCard.CardNumber' value is not valid
// Field 'CreditCard.CardNumber': [Value is not correct credit card number]
var customers = new List<Customer>()
{
// fill customers
};
var result = customers
.Where(customerSpec.AsPredicate()).ToList();
var dbResult = Context.Customers
.Where(customerSpec.GetExpression()).ToList(); // Or customerSpec.AsExpression()
// Single error message for whole specifications chain
customerSpec
.WithMessage(c => $"Validation failed: Incorrect Customer - ID '{c.CustomerId}'")
.IsSatisfiedBy(new Customer
{
CustomerId = 90,
LastName = "Jones",
IsActive = false,
Email = "mjones@hotmail.com",
Items = null,
CreditCard = new CreditCard
{
CardNumber = "5500 0000 1",
ValidityDate = DateTime.Parse("2019-03-01")
}
}, out var specResult); // return false
Console.WriteLine(specResult.ToString());
// Validation failed: Incorrect Customer - ID '90'
// Custom messages for each specification
var idSpec = Specification
.NotEmpty<Customer, int>(c => c.CustomerId)
.WithMessage(c => $"Unknown Customer ID: '{c.CustomerId}'");
var activeSpec = Specification
.True<Customer>(c => c.IsActive)
.WithMessage("Customer is archived");
idSpec.And(activeSpec)
.IsSatisfiedBy(new Customer(), out var specResult); // return false
Console.WriteLine(specResult.ToString());
// Unknown Customer ID: '0'
// Customer is archived
And, AndNot
- join two specifications with logical ANDOr, OrNot
- join two specifications with logical ORNot
- specification logical negationForProperty
- Verifies if Specification is satisfied by property valueCast
- converts candidate to typeExpression
- use external Linq expressionTrue
- is boolean trueFalse
- is boolean falseNull, NotNull
- if object reference nullEmpty, NotEmpty
- if object, string or collection emptyEqual, NotEqual
- equal to expected objectLessThan, LessThanOrEqual, NotLessThan, NotLessThanOrEqual
- less than expected valueGreaterThan, GreaterThanEqual, NotGreaterThan, NotGreaterThanEqual
- greater than expected valueInclusiveBetween, ExclusiveBetween, NotInclusiveBetween, NotExclusiveBetween
- is in rangeLength, NotLength
- if length is equal to expected valueMinLength, NotMinLength
- if length is greater than expected valueMaxLength, NotMaxLength
- if length is lower than expected valueLengthBetween, NotLengthBetween
- if length is in rangeContains, NotContains
- is contain expected valueEmail, NotEmail
- if candidate is valid email addressCreditCard, NotCreditCard
- if candidate is valid credit card numberMatch, NotMatch
- match Regex patternAll
- checks if all elements in collection is satisfied by specificationAny
- checks if any element in collection is satisfied by specificationIsType, IsNotType
- checks if candidate is specific type
Install-Package FluentSpecification.Abstractions
Abstraction layer for Specification design pattern. Contains also interfaces and structures for validation and Linq scenarios.
Commonly used types:
FluentSpecification.Abstractions.Generic.ISpecification<>
FluentSpecification.Abstractions.Generic.IValidationSpecification<>
FluentSpecification.Abstractions.Generic.ILinqSpecification<>
Install-Package FluentSpecification.Core
Core implementation of Specification design pattern. Contains:
- Specifications composition (And, Or, AndNot, OrNot)
- Specifications negation with validation handling (error message negation, linq negation etc.)
- Error handling for validation scenarios
- Linq expressions composing
Install-Package FluentSpecification
Common implementation of small reusable Specifications. All Specifications are based on Specification design pattern. Specifications support validation scenarios and also can be used like Linq expressions, because they are designed and implemented especially for Entity Framework Core support and partially for Entity Framework 6 and tested with these frameworks.