Unleashing the power of domain primitives creation with a fluent Api. Based on a source generator and code analyzer.
public class TypesSpecification : ITypelySpecification
{
public void Create(ITypelyBuilder builder)
{
builder.OfInt().For("ContactId").GreaterThan(0);
builder.OfString()
.For("InsuranceCode")
.Length(10)
.Must(x => x.StartsWith("A91"))
.Normalize(x => x.ToUpper());
builder.OfString()
.For("Phone")
.MaxLength(12)
.Matches(new Regex("[0-9]{3}-[0-9]{3}-[0-9]{4}"));
var title = builder.OfString()
.NotEmpty()
.MaxLength(100)
.Normalize(x => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(x));
title.For("FirstName");
title.For("LastName");
builder.OfDecimal()
.For("CouponDiscount")
.WithName("Coupon discount")
.NotEmpty().WithMessage("{Name} cannot be empty").WithErrorCode("ERR-001")
.GreaterThan(0).WithMessage(() => LocalizedMessages.CustomMessage)
.LessThanOrEqualTo(24.99M);
}
}
- Supported .NET versions
- .NET 7.0 and greater
-
Install packages
dotnet add package Typely.Core dotnet add package Typely.Generators
-
Create a class inheriting from
ITypelySpecification
public class TypesSpecification : ITypelySpecification { public void Create(ITypelyBuilder builder) { builder.OfString().For("FirstName").NotEmpty(); } }
-
Usage
var firstName = FirstName.From("Adam"); FirstName.From(""); //Throws ValidationException if(!FirstName.TryFrom("value", out FirstName instance, out ValidationError? validationError)) { // Handle error }
Serialization using System.Text.Json is supported by default and will only write the underlying value.
To support validation handling and MVC model binding, include Typely.AspNetCore
in your projects.
dotnet add package Typely.AspNetCore
- By default Minimal Apis are supported by implementing a
TryParse
function for generated types. - Post requests are supported by
TypelyJsonConverter
. - Other bindings are supported by
TypelyTypeConverter
.
These are included by default with Typely.Core
.
If you want to add validation errors into the model state of MVC during the binding phase of the request, configure the option below:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options => options.UseTypelyModelBinderProvider());
It supports many validation errors without using exceptions.
The following middleware allows you to return neatly structured Json error responses compatible with Microsoft.AspNetCore.Http.HttpValidationProblemDetails
.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Register this middleware before other middleware components
app.UseTypelyValidationResult();
It works by catching a ValidationException
thrown by Typely and returns a list of errors associated to their value object type name as well as the templates used, if you want to modfify the messages in your client application.
{
"templates": {
"ZipCode": [
{
"code": "Matches",
"message": "'{Name}' is not in the correct format. Expected format '{RegularExpression}'.",
"typeName": "ZipCode",
"placeholderValues": {
"RegularExpression": "^((\\d{5}-\\d{4})|(\\d{5})|([A-Z|a-z]\\d[A-Z|a-z]\\d[A-Z|a-z]\\d))$",
"Name": "ZipCode",
"ActualLength": "7"
}
}
]
},
"errors": {
"ZipCode": [
"'ZipCode' is not in the correct format. Expected format '^((\\d{5}-\\d{4})|(\\d{5})|([A-Z|a-z]\\d[A-Z|a-z]\\d[A-Z|a-z]\\d))$'."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400
}
To add support for OpenAPI specs and Swagger UI, include Typely.AspNetCore.Swashbuckle
in your projects.
dotnet add package Typely.AspNetCore.Swashbuckle
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSwaggerGen(options => options.UseTypelySchemaFilter());
To use your value objects with EF Core, include Typely.EfCore
in your projects.
dotnet add package Typely.EfCore
Apply Typely conventions to your DbContext
to automatically configure the database. By default, the conventions will tell EF Core how to save and load your value objects and set the maximum data length.
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.AddTypelyConventions();
//OR
configurationBuilder.Conventions
.AddTypelyConversionConvention()
.AddTypelyMaxLengthConvention();
}
If you don't use conventions, you can configure your types manually. Typely will generate a MaxLength
property when using a validation that sets the maximum length.
builder.Property(x => x.LastName)
.HasMaxLength(LastName.MaxLength)
.HasConversion<TypelyValueConverter<string, LastName>>();
You can also override the default conventions:
builder.Property(x => x.FirstName)
.HasMaxLength(80)
.HasConversion((x) => x + "-custom-conversion", (x) => FirstName.Create(x.Replace("-custom-conversion", "")));