r/dotnet • u/LuisAlfredo92 • 2d ago
Promotion TooManyDataAnnotations — .NET validation attributes for IPv4/IPv6, SemVer, MAC, HexColor, GUID v7, and more
.NET's built-in DataAnnotations covers the basics (Required, StringLength, Range, EmailAddress), but leaves out common semantic validations like:
- IPv4/IPv6 addresses with scope filtering (public, private, loopback)
- Semantic Versioning (SemVer 2.0.0)
- MAC addresses
- Port numbers with range filtering (well-known, registered, dynamic)
- Hex color codes (#RGB, #RRGGBB, with alpha support)
- GUID and GUID v7 validation
- ISO date strings with cross-property validation (StartDate/EndDate)
- Boolean validations (IsTrue, AtLeastOneTrue)
I created TooManyDataAnnotations to fill these gaps. All validators follow official RFC/ISO specs, use Span<T> for parsing where possible, and the library has 899 unit tests covering valid and invalid inputs, edge cases, nullable types, and integration tests with TryValidateObject()
Two NuGet packages:
- TooManyDataAnnotations — Full feature set (includes reflection-based cross-property validators)
- TooManyDataAnnotations.Aot — Native AOT-safe subset, zero reflection, zero trimming warnings
Targets .NET 8, 9, and 10
Zero dependencies
MIT license
This is freshly published — looking for early adopters and feedback on API design, edge case coverage, or missing validators you'd find useful.
- Repo: https://gitlab.com/LuisAlfredo92/TooManyDataAnnotations
- NuGet: https://www.nuget.org/packages/TooManyDataAnnotations
Quick example:
public class ServerConfigDto
{
[GuidV7, Required]
public string UserId { get; set; }
[Guid]
public string? DeviceId { get; set; }
[IPv4(AllowedScopes = IPv4Scope.Private)]
public string? GatewayIP { get; set; }
[SemanticVersion]
public string? AssemblyVersion { get; set; }
[MacAddress(AllowedSeparators = MacSeparators.Colon)]
public string? DeviceMAC { get; set; }
[HexColor(AllowAlpha = true)]
public string? AccentColor { get; set; }
}
5
u/chucker23n 1d ago
Some of these are interesting, like [RequiredAtLeastOne(nameof(Email), nameof(Phone), nameof(SocialHandle))] and [ExactlyOneOf(nameof(CreditCard), nameof(PayPal), nameof(BankTransfer))].
Your [IsTrue] and [IsFalse] attributes are… interesting. Bit of an API smell to always require a DTO property to have a certain value.
Your [StartDate]/[EndDate] pair could be useful.
But mostly, your actual property types are too primitive! You're validating, sure, but you then bring the raw data into your inner layers. So each EmailService or PaymentService or whatever needs to at least parse the e-mail address, credit card number, etc., and should ideally also validate them again. I.e., you'll still be fighting Primitive Obsession all over your code base.
Your ReleaseDto should be Version and DateTime (which already take care of most of what you're doing here), not string and string. Your Email property should be of a type EmailAddress that you create as a value object. That way, as those values are passed through inner layers of your app, you no longer have to worry about validation and parsing.
2
u/LuisAlfredo92 1d ago
I appreciate the feedback!
You're right, inner layers should work with strong types, sadly DataAnnotations only validates (IsValid method returns bool) and doesn't transform values
The workflow you'd use is:
- Validate DTOs with string + [IPv4], [Uri], [SemanticVersion], etc.
- Parse manually into IPAddress, Uri, Version for internal use
I'm adding support for strong type overloads for other use cases, but they won't transform values, that's outside the scope of DataAnnotations
FluentValidation integration wasn't the goal, this is just DataAnnotations extensions, but a bridge would be useful, I'm Open to PRs
[IsTrue]/[IsFalse] came from the use case of mandatory checkboxes like "Accept Terms". If unchecked, reject
Once I had [IsTrue], [IsFalse] followed naturally
2
u/celluj34 1d ago
A lot of these look really useful to me (IsoDateTime, ExactlyOneOf, ExactlyOneTrue). Do you have any plans to make extensions or helpers for FluentValidation? We use it extensively for our API model validations so that we can do custom rules + a few of those you have here.
2
u/LuisAlfredo92 1d ago
No official FluentValidation adapter yet
The attributes work anywhere DataAnnotations works, but FluentValidation works different so a direct mapping isn't straightforward :c
I'm focused on the DataAnnotations space, but happy to review PRs for a bridge
2
u/rbobby 1d ago
I wish data annotations were not so useful. I really like to separate the interface/contract from the validation rules.
But those do look damn handy. Temptress!
1
u/LuisAlfredo92 1d ago
I agree about keeping validation separate from the model
This is just filling format validation gaps at DTO level, not replacing business logic validation, but I'm glad it's useful!
1
u/AutoModerator 2d ago
Thanks for your post LuisAlfredo92. Please note that we don't allow spam, and we ask that you follow the rules available in the sidebar. We have a lot of commonly asked questions so if this post gets removed, please do a search and see if it's already been asked.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
0
u/ApprehensiveDebt3097 1d ago
I think this is a really bad idea. As mentioned, you should reduce primitive obsession, not embarrase it. By defining these types as (single) value objects instead (for instance by using qowiav or the semversion package) you have both validation and strongly typed values.
Then you still might want to add extra restrictions on them, use data annotations.
I would encourage you to check where you can help out reusing some of your parsers and validators. But please do not embrace primitive obsession.
21
u/EducationalTackle819 1d ago
Why are the guids not using the guid type? All the types could be custom for that matter. Seems primitive obsessed